diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index 18cff7054..000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,78 +0,0 @@ -version: "{build} ~ {branch}" - -os: Visual Studio 2017 - -branches: - only: - - staging - - trying - - master - -environment: - matrix: - - CHANNEL: stable - ARCH: x86_64 - ABI: msvc - TARGET: x86_64-pc-windows-msvc - -cache: - - 'C:\Users\appveyor\.cargo' - - target - -install: - # Install LLVM - - mkdir C:\projects\deps - - cd C:\projects\deps - - appveyor DownloadFile https://github.com/wasmerio/windows-llvm-build/releases/download/v8.0.0/llvm-8.0.0-install.zip -FileName llvm-8.0.0-install.zip - - 7z x llvm-8.0.0-install.zip - - C:\projects\deps\llvm-8.0.0-install\bin\llvm-config.exe --version - - set "LLVM_SYS_80_PREFIX=C:\projects\deps\llvm-8.0.0-install" - - cd "%APPVEYOR_BUILD_FOLDER%" - - # Install Rust - # uncomment these lines if the cache is cleared, or if we must re-install rust for some reason - - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - rustup-init.exe -yv --default-host %target% - - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - - rustup default stable-%target% - - rustup update - - rustc -vV - - cargo -vV - - # Install InnoSetup - - appveyor-retry appveyor DownloadFile https://s3-us-west-1.amazonaws.com/rust-lang-ci2/rust-ci-mirror/2017-08-22-is.exe - - 2017-08-22-is.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- - - set PATH="C:\Program Files (x86)\Inno Setup 5";%PATH% -# uncomment to RDP to appveyor -# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1')) - -build_script: - - cargo build --release --verbose --features backend-llvm - - cargo build --release --manifest-path lib/runtime-c-api/Cargo.toml - -test_script: - - cargo test --manifest-path lib/spectests/Cargo.toml --release --features clif -- --nocapture - -before_deploy: - - appveyor PushArtifact target\release\wasmer_runtime_c_api.dll - - git submodule init - - git submodule update - - cargo build --release --manifest-path wapm-cli/Cargo.toml --features "telemetry update-notifications" - - cd ./src/installer - - iscc wasmer.iss - - copy /y .\WasmerInstaller.exe ..\..\WasmerInstaller-%APPVEYOR_REPO_TAG_NAME%.exe - - appveyor PushArtifact ..\..\WasmerInstaller-%APPVEYOR_REPO_TAG_NAME%.exe - - cd ..\..\ - -matrix: - fast_finish: true - -deploy: - description: 'WasmerInstaller' - artifact: /.*\.exe/ - auth_token: - secure: BbreGNDJy20922za7OhJG5TERzfX+dJSBQwttNTJkLvszbqMov6hhAtRb3P45hpf - provider: GitHub - on: - branch: master - appveyor_repo_tag: true diff --git a/.azure/install-cmake.yml b/.azure/install-cmake.yml new file mode 100644 index 000000000..707905b55 --- /dev/null +++ b/.azure/install-cmake.yml @@ -0,0 +1,43 @@ +# This template installs CMake (if doesn't exist in the systems) + +steps: + - bash: | + set -ex + if [ -x "`command -v cmake`" ]; then + echo `command -v cmake` `cmake --version` installed + else + curl -O https://cmake.org/files/v3.4/cmake-3.4.1-Darwin-x86_64.tar.gz + tar xf cmake-3.4.1-Darwin-x86_64.tar.gz + export CMAKE_BIN_PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin" + echo "##vso[task.prependpath]$CMAKE_BIN_PATH" + fi + displayName: "Install CMake (macOS)" + condition: eq(variables['Agent.OS'], 'Darwin') + + - bash: | + set -ex + if [ -x "`command -v cmake`" ]; then + echo `command -v cmake` `cmake --version` installed + else + curl -O https://cmake.org/files/v3.4/cmake-3.4.1-Linux-x86_64.tar.gz + tar xf cmake-3.4.1-Linux-x86_64.tar.gz + export CMAKE_BIN_PATH="`pwd`/cmake-3.4.1-Linux-x86_64/CMake.app/Contents/bin" + echo "##vso[task.prependpath]$CMAKE_BIN_PATH" + fi + displayName: "Install CMake (Linux)" + condition: eq(variables['Agent.OS'], 'Linux') + + - bash: | + set -ex + if [ -x "`command -v cmake`" ]; then + echo `command -v cmake` `cmake --version` installed + else + chocolatey install cmake --installargs 'ADD_CMAKE_TO_PATH=System' + fi + displayName: "Install CMake (Windows)" + condition: eq(variables['Agent.OS'], 'Windows_NT') + + - bash: | + set -ex + cmake --version + displayName: CMake version diff --git a/.azure/install-innosetup.yml b/.azure/install-innosetup.yml new file mode 100644 index 000000000..bd7e2980e --- /dev/null +++ b/.azure/install-innosetup.yml @@ -0,0 +1,18 @@ +# This template installs InnoSetup + +steps: + - bash: | + set -ex + if [ -x "`command -v iscc`" ]; then + echo `command -v iscc` `iscc -?` installed + else + choco install innosetup -y + fi + displayName: Install InnoSetup - Windows + condition: eq(variables['Agent.OS'], 'Windows_NT') + + # - bash: | + # set -ex + # iscc + # displayName: InnoSetup + # condition: eq(variables['Agent.OS'], 'Windows_NT') diff --git a/.azure/install-llvm.yml b/.azure/install-llvm.yml new file mode 100644 index 000000000..5b01ee7b9 --- /dev/null +++ b/.azure/install-llvm.yml @@ -0,0 +1,53 @@ +# This template installs LLVM (if doesn't exist in the systems) + +steps: + - bash: | + set -ex + if [ -x "`command -v llvm-config`" ]; then + echo `command -v llvm-config` `llvm-config --version` installed + else + curl -O https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz + tar xf clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz + export LLVM_PATH="`pwd`/clang+llvm-8.0.0-x86_64-apple-darwin/" + echo "##vso[task.prependpath]$LLVM_PATH/bin" + echo "##vso[task.setvariable variable=LLVM_SYS_80_PREFIX]$LLVM_PATH" + fi + displayName: "Install LLVM (macOS)" + condition: eq(variables['Agent.OS'], 'Darwin') + + - bash: | + set -ex + if [ -x "`command -v llvm-config`" ]; then + echo `command -v llvm-config` `llvm-config --version` installed + else + curl -O https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz + tar xf clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz + export LLVM_PATH="`pwd`/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04/" + echo "##vso[task.prependpath]$LLVM_PATH/bin" + echo "##vso[task.setvariable variable=LLVM_SYS_80_PREFIX]$LLVM_PATH" + fi + displayName: "Install LLVM (Linux)" + condition: eq(variables['Agent.OS'], 'Linux') + + - bash: | + set -ex + curl -OL https://github.com/wasmerio/windows-llvm-build/releases/download/v8.0.0/llvm-8.0.0-install.zip + 7z x llvm-8.0.0-install.zip + llvm=`pwd`/llvm-8.0.0-install + echo "##vso[task.prependpath]$llvm/bin" + echo "##vso[task.setvariable variable=LLVM_SYS_80_PREFIX;]$llvm" + displayName: "Install LLVM (Windows)" + condition: eq(variables['Agent.OS'], 'Windows_NT') + + # Just to make sure the paths and vars are set properly + - powershell: | + Write-Host "##vso[task.prependpath]$pwd/llvm-8.0.0-install/bin" + Write-Host "##vso[task.setvariable variable=LLVM_SYS_80_PREFIX;]$pwd/llvm-8.0.0-install/" + displayName: Install LLVM (Windows) + condition: eq(variables['Agent.OS'], 'Windows_NT') + + - bash: | + set -ex + env + llvm-config --version + displayName: LLVM version diff --git a/.azure/install-rust.yml b/.azure/install-rust.yml new file mode 100644 index 000000000..243cb7bf9 --- /dev/null +++ b/.azure/install-rust.yml @@ -0,0 +1,52 @@ +# This template installs Rust (if doesn't exist in the systems) +# Also installs the desired Rust toolchain + +# Template inspired by Tokio and wasm-bindgen templates +# Tokio template: https://github.com/tokio-rs/tokio/blob/master/ci/azure-install-rust.yml +# Wasm-bindgen template: https://github.com/rustwasm/wasm-bindgen/blob/master/ci/azure-install-rust.yml + +steps: + # - bash: | + # set -ex + # brew install openssl@1.1 curl + # brew link openssl@1.1 --force + # echo "##vso[task.prependpath]/usr/local/opt/openssl/bin" + # echo "##vso[task.setvariable variable=LDFLAGS;]-L/usr/local/opt/openssl/lib" + # echo "##vso[task.setvariable variable=CPPFLAGS;]-I/usr/local/opt/openssl/include" + # displayName: "Fix Cargo SSL (macOS)" + # condition: eq(variables['Agent.OS'], 'Darwin') + - bash: | + set -ex + if [ -x "`command -v rustup`" ]; then + echo `command -v rustup` `rustup -V` installed + else + curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $RUST_TOOLCHAIN + echo "##vso[task.prependpath]$HOME/.cargo/bin" + fi + displayName: "Install Rust (Linux, macOS)" + condition: ne(variables['Agent.OS'], 'Windows_NT') + + # - bash: | + # set -ex + # if [ -x "`command -v rustup`" ]; then + # echo `command -v rustup` `rustup -V` installed + # else + # choco install rust -y + # # curl -sSf -o rustup-init.exe https://win.rustup.rs + # # ./rustup-init.exe -y --default-toolchain $RUST_TOOLCHAIN + # # echo "##vso[task.prependpath]$USERPROFILE/.cargo/bin" + # fi + # displayName: "Install Rust (Windows)" + # condition: eq(variables['Agent.OS'], 'Windows_NT') + + - bash: | + set -ex + rustup update --no-self-update $RUST_TOOLCHAIN + rustup default $RUST_TOOLCHAIN + + rustc -Vv + cargo -V + displayName: Install Rust + + - bash: echo "##vso[task.setvariable variable=RUSTC_VERSION;]`rustc --version`" + displayName: Set rustc version in env var diff --git a/.azure/install-sccache.yml b/.azure/install-sccache.yml new file mode 100644 index 000000000..091dcc572 --- /dev/null +++ b/.azure/install-sccache.yml @@ -0,0 +1,57 @@ +# This template installs sccache (Shared Compilation Cache) +# More info: https://github.com/mozilla/sccache + +# Template originally from wasm-bindgen +# https://github.com/rustwasm/wasm-bindgen/blob/master/ci/azure-install-sccache.yml +steps: + - bash: | + set -ex + curl -L https://github.com/mozilla/sccache/releases/download/0.2.10/sccache-0.2.10-x86_64-unknown-linux-musl.tar.gz | tar xzf - + sccache=`pwd`/sccache-0.2.10-x86_64-unknown-linux-musl/sccache + echo "##vso[task.setvariable variable=RUSTC_WRAPPER;]$sccache" + displayName: Install sccache - Linux + condition: eq( variables['Agent.OS'], 'Linux' ) + - bash: | + set -ex + brew install openssl@1.1 + curl -L https://github.com/mozilla/sccache/releases/download/0.2.10/sccache-0.2.10-x86_64-apple-darwin.tar.gz | tar xzf - + sccache=`pwd`/sccache-0.2.10-x86_64-apple-darwin/sccache + echo "##vso[task.setvariable variable=RUSTC_WRAPPER;]$sccache" + displayName: Install sccache - Darwin + condition: eq( variables['Agent.OS'], 'Darwin' ) + - powershell: | + Invoke-WebRequest https://github.com/mozilla/sccache/releases/download/0.2.10/sccache-0.2.10-x86_64-pc-windows-msvc.tar.gz -OutFile sccache.tar.gz + tar xzf sccache.tar.gz + Write-Host "##vso[task.setvariable variable=RUSTC_WRAPPER;]$pwd/sccache-0.2.10-x86_64-pc-windows-msvc/sccache.exe" + displayName: Install sccache - Windows + condition: eq( variables['Agent.OS'], 'Windows_NT' ) + - bash: | + set -ex + env + SCCACHE_ERROR_LOG=`pwd`/sccache.log RUST_LOG=debug $RUSTC_WRAPPER --start-server + $RUSTC_WRAPPER -s + cat sccache.log + displayName: "start sccache" + condition: not(eq( variables['Agent.OS'], 'Windows_NT' )) + env: + SCCACHE_AZURE_CONNECTION_STRING: $(SCCACHE_AZURE_CONNECTION_STRING) + SCCACHE_AZURE_BLOB_CONTAINER: $(SCCACHE_AZURE_BLOB_CONTAINER) + + # Only use Azure pipelines cache in Windows + - bash: | + set -ex + env + mkdir -p $SCCACHE_DIR + SCCACHE_ERROR_LOG=`pwd`/sccache.log RUST_LOG=debug $RUSTC_WRAPPER --start-server + $RUSTC_WRAPPER -s + cat sccache.log + displayName: "start sccache" + condition: eq( variables['Agent.OS'], 'Windows_NT' ) + env: + SCCACHE_DIR: $(Pipeline.Workspace)/.sccache + - task: CacheBeta@0 + inputs: + key: sccache | $(Agent.OS) | Cargo.lock + path: $(Pipeline.Workspace)/.sccache + displayName: Cache Cargo Target + condition: eq( variables['Agent.OS'], 'Windows_NT' ) diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 2bd08d330..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,442 +0,0 @@ -run_with_build_env_vars: &run_with_build_env_vars - environment: - LLVM_SYS_80_PREFIX: /home/circleci/project/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04/ - -run_install_dependencies: &run_install_dependencies - run: - name: install dependencies - command: | - sudo apt-get install -y cmake - curl -O https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz - tar xf clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz - -version: 2 -jobs: - changelog: - docker: - - image: docker:stable-git - steps: - - checkout - - run: - command: ! git diff --exit-code CHANGELOG.md - - # Job used for testing - lint: - docker: - - image: circleci/rust:latest - <<: *run_with_build_env_vars - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule update --init - - restore_cache: - keys: - - v8-lint-{{ arch }}-{{ checksum "Cargo.lock" }} - - <<: *run_install_dependencies - - run: - name: Install lint deps - command: | - git config --global --unset url."ssh://git@github.com".insteadOf || true - # rustup toolchain install nightly-2019-06-10 - # rustup default nightly-2019-06-10 - rustup component add rustfmt - rustup component add clippy || cargo install --git https://github.com/rust-lang/rust-clippy/ --force clippy - - run: - name: Execute lints - command: | - cargo fmt --all -- --check - - save_cache: - paths: - - /usr/local/cargo/registry - - target/debug/.fingerprint - - target/debug/build - - target/debug/deps - key: v8-lint-{{ arch }}-{{ checksum "Cargo.lock" }} - - test-stable: - docker: - - image: circleci/rust:1.36 - <<: *run_with_build_env_vars - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule update --init - - restore_cache: - keys: - - v8-test-cargo-cache-linux-stable-{{ arch }}-{{ checksum "Cargo.lock" }} - - <<: *run_install_dependencies - - run: - name: Test everything (except singlepass) - command: | - make cranelift - make llvm - make test-rest - - run: - name: Integration Tests - command: make integration-tests - - save_cache: - paths: - - /usr/local/cargo/registry - - target/release/.fingerprint - - target/release/build - - target/release/deps - key: v8-test-cargo-cache-linux-stable-{{ arch }}-{{ checksum "Cargo.lock" }} - - test: - docker: - - image: circleci/rust:latest - <<: *run_with_build_env_vars - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule update --init - - restore_cache: - keys: - - v8-test-cargo-cache-linux-nightly-{{ arch }}-{{ checksum "Cargo.lock" }} - - <<: *run_install_dependencies - - run: rustup default nightly-2019-06-10 - - run: - name: Tests - command: make test - - run: - name: Debug flag checked - command: | - cargo check --features "debug" --release - - run: - name: Check - command: | - make check - make compile-bench-singlepass - make compile-bench-llvm - # TODO: add compile-bench-clif when it works - - run: - name: Integration Tests - command: make integration-tests - - save_cache: - paths: - - /usr/local/cargo/registry - - target/release/.fingerprint - - target/release/build - - target/release/deps - key: v8-test-cargo-cache-linux-nightly-{{ arch }}-{{ checksum "Cargo.lock" }} - - test-rust-example: - docker: - - image: circleci/rust:latest - <<: *run_with_build_env_vars - steps: - - checkout - - run: - name: "Check Wasmer Rust example" - command: | - git clone https://github.com/wasmerio/wasmer-rust-example - rustup default stable - rustup target add wasm32-unknown-unknown - cd wasmer-rust-example - cd wasm-sample-app - cargo build --release - cd .. - sed -i 's/wasmer-runtime.*/wasmer-runtime = \{ path = "..\/lib\/runtime" \}/g' Cargo.toml - cargo run - cargo test - - test-macos: - macos: - xcode: "9.0" - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule update --init - - restore_cache: - keys: - - v8-cargo-cache-darwin-nightly-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Install crate dependencies - command: | - # Installing cmake outside of brew to improve speed - curl -O https://cmake.org/files/v3.4/cmake-3.4.1-Darwin-x86_64.tar.gz - tar xf cmake-3.4.1-Darwin-x86_64.tar.gz - export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" - # Installing LLVM outside of brew - curl -O https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz - tar xf clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz - - run: - name: Install Rust - command: | - curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly-2019-06-10 - export PATH="$HOME/.cargo/bin:$PATH" - cargo --version - - run: - name: Tests - command: | - export PATH="$HOME/.cargo/bin:$PATH" - export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" - export LLVM_SYS_80_PREFIX="`pwd`/clang+llvm-8.0.0-x86_64-apple-darwin/" - # We increase the ulimit for fixing cargo unclosed files in mac - ulimit -n 8000 - sudo sysctl -w kern.maxfiles=655360 kern.maxfilesperproc=327680 - make test - - run: - name: Check - command: | - export PATH="$HOME/.cargo/bin:$PATH" - export LLVM_SYS_80_PREFIX="`pwd`/clang+llvm-8.0.0-x86_64-apple-darwin/" - make check - - run: - name: Integration Tests - command: | - export PATH="$HOME/.cargo/bin:$PATH" - export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" - export LLVM_SYS_80_PREFIX="`pwd`/clang+llvm-8.0.0-x86_64-apple-darwin/" - make integration-tests - - save_cache: - paths: - - ~/.cargo/registry/ - - target/release/.fingerprint - - target/release/build - - target/release/deps - key: v8-cargo-cache-darwin-nightly-{{ arch }}-{{ checksum "Cargo.lock" }} - - test-and-build: - docker: - - image: circleci/rust:latest - steps: - - checkout - - run: - name: "Pull Submodules" - command: | - git submodule update --init - - run: - name: "Pull dependencies" - command: | - git submodule init - git submodule update - - restore_cache: - keys: - - v8-cargo-cache-linux-nightly-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Install dependencies - command: | - sudo apt-get install -y cmake - curl -O https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz - tar xf clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz - - run: rustup default nightly-2019-06-10 - - run: - name: Tests - command: | - export LLVM_SYS_80_PREFIX="`pwd`/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04/" - make test - - run: - name: Release Build - command: | - export LLVM_SYS_80_PREFIX="`pwd`/clang+llvm-8.0.0-x86_64-linux-gnu-ubuntu-16.04/" - make release - cargo build --release --manifest-path wapm-cli/Cargo.toml --features "telemetry update-notifications" - mkdir -p artifacts - VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2) - # GIT_VERSION=$(git describe --exact-match --tags) - echo "${VERSION}" >> artifacts/version - echo "${CIRCLE_TAG}" >> artifacts/git_version - make build-install - cp ./wasmer.tar.gz ./artifacts/$(./scripts/binary-name.sh) - - run: - name: Dynamic library - command: | - cargo build --release --manifest-path lib/runtime-c-api/Cargo.toml - cp target/release/libwasmer_runtime_c_api.so ./artifacts - - persist_to_workspace: - root: . - paths: - - artifacts - - save_cache: - paths: - - /usr/local/cargo/registry - - target/release/.fingerprint - - target/release/build - - target/release/deps - - wapm-cli/target/release/.fingerprint - - wapm-cli/target/release/build - - wapm-cli/target/release/deps - key: v8-cargo-cache-linux-nightly-{{ arch }}-{{ checksum "Cargo.lock" }} - - test-and-build-macos: - macos: - xcode: "9.0" - steps: - - checkout - - run: - name: "Pull dependencies" - command: | - git submodule init - git submodule update - - restore_cache: - keys: - - v8-cargo-cache-darwin-nightly-{{ arch }}-{{ checksum "Cargo.lock" }} - - run: - name: Install crate dependencies - command: | - # Installing cmake outside of brew to improve speed - curl -O https://cmake.org/files/v3.4/cmake-3.4.1-Darwin-x86_64.tar.gz - tar xf cmake-3.4.1-Darwin-x86_64.tar.gz - export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" - # Installing LLVM outside of brew - curl -O https://releases.llvm.org/8.0.0/clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz - tar xf clang+llvm-8.0.0-x86_64-apple-darwin.tar.xz - - run: - name: Install Rust - command: | - curl -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain nightly-2019-06-10 - export PATH="$HOME/.cargo/bin:$PATH" - cargo --version - - run: - name: Tests - command: | - export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" - export PATH="$HOME/.cargo/bin:$PATH" - export LLVM_SYS_80_PREFIX="`pwd`/clang+llvm-8.0.0-x86_64-apple-darwin/" - # We increase the ulimit for fixing cargo unclosed files in mac - ulimit -n 8000 - sudo sysctl -w kern.maxfiles=655360 kern.maxfilesperproc=327680 - - make test - - run: - name: Release Build - command: | - export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" - export PATH="$HOME/.cargo/bin:$PATH" - export LLVM_SYS_80_PREFIX="`pwd`/clang+llvm-8.0.0-x86_64-apple-darwin/" - make release - cargo build --release --manifest-path wapm-cli/Cargo.toml --features "telemetry update-notifications" - mkdir -p artifacts - make build-install - cp ./wasmer.tar.gz ./artifacts/$(./scripts/binary-name.sh) - # VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2) - # echo "${VERSION}" >> artifacts/version - - run: - name: Generate dynamic library for the runtime C API - command: | - export PATH="$HOME/.cargo/bin:$PATH" - cargo build --release --manifest-path lib/runtime-c-api/Cargo.toml - install_name_tool -id "@rpath/libwasmer_runtime_c_api.dylib" target/release/libwasmer_runtime_c_api.dylib - cp target/release/libwasmer_runtime_c_api.dylib ./artifacts - - persist_to_workspace: - root: . - paths: - - artifacts - - save_cache: - paths: - - ~/.cargo/registry/ - - target/release/.fingerprint - - target/release/build - - target/release/deps - - wapm-cli/target/release/.fingerprint - - wapm-cli/target/release/build - - wapm-cli/target/release/deps - key: v8-cargo-cache-darwin-nightly-{{ arch }}-{{ checksum "Cargo.lock" }} - - publish-github-release: - docker: - - image: cibuilds/github - steps: - - attach_workspace: - at: . - - run: - name: "Publish Release on GitHub" - command: | - # go get github.com/tcnksm/ghr - # VERSION=$(git log -1 --pretty=%B) - # VERSION=$(./artifacts/ --version) - VERSION=$(cat ./artifacts/version) - # VERSION_TAG=${CIRCLE_TAG} - VERSION_TAG=$(cat ./artifacts/git_version) - LATEST_VERSION_PUBLISHED_ON_CRATES=$(curl -s https://raw.githubusercontent.com/rust-lang/crates.io-index/master/wa/sm/wasmer-runtime | tail -n 1 | sed 's/.*"vers":"\([^"]*\)".*/\1/') - if ( [ $VERSION_TAG -ne $LATEST_VERSION_PUBLISHED_ON_CRATES ] ) then { echo "Could not detect version published to crates.io; make sure we've published the crates before publishing the Wasmer binary"; exit 1; } else { true; } fi - rm ./artifacts/version - rm ./artifacts/git_version - # VERSION_TAG=$(git describe --exact-match --tags) - #if [ "$VERSION" == "$VERSION_TAG" ]; then - # echo "Versions match, publishing to Github" - ghr -t ${GITHUB_TOKEN} -u ${CIRCLE_PROJECT_USERNAME} -r ${CIRCLE_PROJECT_REPONAME} -c ${CIRCLE_SHA1} ${VERSION} ./artifacts/ || true - #else - # echo "Versions don't match. Wasmer output version (wasmer --version) is ${VERSION} while Git tag is ${VERSION_TAG}" - # exit 1 - #fi - trigger-benchmark-build: - docker: - - image: circleci/rust:latest - steps: - - run: - name: "Trigger Benchmark Build" - command: | - if [[ -z "${CIRCLE_API_USER_TOKEN}" ]]; then - echo "CIRCLE_API_USER_TOKEN environment variable not set" - exit 1 - else - echo "Triggering benchmark build" - curl -u ${CIRCLE_API_USER_TOKEN} \ - -d build_parameters[CIRCLE_JOB]=bench \ - https://circleci.com/api/v1.1/project/github/wasmerio/wasmer-bench/tree/master - fi -workflows: - version: 2 - main: - jobs: - - changelog - - lint - - test: - filters: - branches: - only: - - trying - - staging - - test-rust-example: - filters: - branches: - only: - - trying - - staging - - test-macos: - filters: - branches: - only: - - trying - - staging - - test-and-build: - filters: - branches: - only: - - master - - test-and-build-macos: - filters: - branches: - only: - - master - - test-stable: - filters: - branches: - only: - - trying - - staging - - publish-github-release: - requires: - - lint - - test-and-build - - test-and-build-macos - filters: - branches: - only: master - - trigger-benchmark-build: - requires: - - test-and-build - - lint - filters: - branches: - only: master diff --git a/.dockerignore b/.dockerignore index e861ea7b6..29fd6ce6b 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,5 +2,6 @@ ** !lib/** !src/** +!examples/** !Cargo.toml !Cargo.lock \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 3426f1dee..4cc803e80 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ lib/emscripten/emtests/* linguist-vendored lib/spectests/spectests/* linguist-vendored +CHANGELOG.md merge=union diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index f10837c34..847de4fd8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,7 +8,6 @@ lib/llvm-backend @nlewycky @losfair # Runtime lib/runtime-core @Hywan @bjfish -lib/runtime-abi @MarkMcCaskey lib/runtime @MarkMcCaskey @Hywan @bjfish lib/runtime-c-api @bjfish @Hywan lib/win-exception-handler @bjfish @losfair diff --git a/.github/ISSUE_TEMPLATE/---bug-report.md b/.github/ISSUE_TEMPLATE/---bug-report.md index 7a7e83e12..470f5ea9b 100644 --- a/.github/ISSUE_TEMPLATE/---bug-report.md +++ b/.github/ISSUE_TEMPLATE/---bug-report.md @@ -7,37 +7,44 @@ assignees: '' --- -Thanks for the bug report! + ### Describe the bug + -```bash +```sh echo "`wasmer -V` | `rustc -V` | `uname -m`" ``` -### Steps to reproduce +### Steps to reproduce + ### Expected behavior - -A clear and concise description of what you expected to happen. + ### Actual behavior + ### Additional context - -Add any other context about the problem here. + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..b29669d33 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ + + +# Description + + +# Review + +- [ ] Add a short description of the the change to the CHANGELOG.md file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..4dbbd44d8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,65 @@ +arch: + - arm64 + +language: rust +rust: + - nightly-2019-08-15 + +cache: + directories: + - /home/travis/.sccache/ + - /home/travis/.cargo/bin/ + +script: + # Sccache + # - curl -L https://github.com/mozilla/sccache/releases/download/0.2.10/sccache-0.2.10-x86_64-unknown-linux-musl.tar.gz | tar xzf - + # - export RUSTC_WRAPPER=`pwd`/sccache-0.2.10-x86_64-unknown-linux-musl/sccache + - test -f /home/travis/.cargo/bin/sccache || cargo install sccache + - export RUSTC_WRAPPER=/home/travis/.cargo/bin/sccache + - mkdir -p /home/travis/.sccache/ + - export SCCACHE_DIR="/home/travis/.sccache/" + - SCCACHE_ERROR_LOG=`pwd`/sccache.log RUST_LOG=debug $RUSTC_WRAPPER --start-server + - $RUSTC_WRAPPER -s + + # Tests + - make spectests-singlepass + +before_deploy: + # Release + - make release-singlepass + - make wapm + - make build-install + - mkdir -p artifacts + - cp ./wasmer.tar.gz ./artifacts/$(./scripts/binary-name.sh) + +# before_deploy: +# # Set up git user name and tag this commit +# - git config --local user.name "Syrus Akbary" +# - git config --local user.email "syrus@wasmer.io" +# - export TRAVIS_TAG="0.10.2" +# # - git tag $TRAVIS_TAG + +deploy: + provider: releases + file_glob: true + file: artifacts/* + api_key: $GITHUB_OAUTH_TOKEN + # This is set to the previous artifacts are not deleted by travis + skip_cleanup: true + on: + tags: true + # branch: feature/singlepass-aarch64 + +addons: + apt: + packages: + - cmake + +branches: + only: + - master + - staging + - trying + # Making sure Travis runs on new Tags + - /^\d+\.\d+(\.\d+)?(-\S*)?$/ + - /^v\d+\.\d+(\.\d+)?(-\S*)?$/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 62aa45ac7..4b5b8a80a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,113 @@ # Changelog -All PRs to the Wasmer repository must add to this file. - -Blocks of changes will separated by version increments. - ## **[Unreleased]** +- [#1029](https://github.com/wasmerio/wasmer/pull/1029) Add the “floating” `WasiVersion::Latest` version. +- [#1006](https://github.com/wasmerio/wasmer/pull/1006) Fix minor panic issue when `wasmer::compile_with` called with llvm backend +- [#1009](https://github.com/wasmerio/wasmer/pull/1009) Enable LLVM verifier for all tests, add new llvm-backend-tests crate. +- [#1022](https://github.com/wasmerio/wasmer/pull/1022) Add caching support for Singlepass backend. +- [#1004](https://github.com/wasmerio/wasmer/pull/1004) Add the Auto backend to enable to adapt backend usage depending on wasm file executed. + +## 0.11.0 - 2019-11-22 + +- [#713](https://github.com/wasmerio/wasmer/pull/713) Add AArch64 support for singlepass. +- [#995](https://github.com/wasmerio/wasmer/pull/995) Detect when a global is read without being initialized (emit a proper error instead of panicking) +- [#996](https://github.com/wasmerio/wasmer/pull/997) Refactored spectests, emtests and wasitests to use default compiler logic +- [#992](https://github.com/wasmerio/wasmer/pull/992) Updates WAPM version to 0.4.1, fix arguments issue introduced in #990 +- [#990](https://github.com/wasmerio/wasmer/pull/990) Default wasmer CLI to `run`. Wasmer will now attempt to parse unrecognized command line options as if they were applied to the run command: `wasmer mywasm.wasm --dir=.` now works! +- [#987](https://github.com/wasmerio/wasmer/pull/987) Fix `runtime-c-api` header files when compiled by gnuc. +- [#957](https://github.com/wasmerio/wasmer/pull/957) Change the meaning of `wasmer_wasi::is_wasi_module` to detect any type of WASI module, add support for new wasi snapshot_preview1 +- [#934](https://github.com/wasmerio/wasmer/pull/934) Simplify float expressions in the LLVM backend. + +## 0.10.2 - 2019-11-18 + +- [#968](https://github.com/wasmerio/wasmer/pull/968) Added `--invoke` option to the command +- [#964](https://github.com/wasmerio/wasmer/pull/964) Enable cross-compilation for specific target +- [#971](https://github.com/wasmerio/wasmer/pull/971) In LLVM backend, use unaligned loads and stores for non-atomic accesses to wasmer memory. +- [#960](https://github.com/wasmerio/wasmer/pull/960) Fix `runtime-c-api` header files when compiled by clang. +- [#925](https://github.com/wasmerio/wasmer/pull/925) Host functions can be closures with a captured environment. +- [#917](https://github.com/wasmerio/wasmer/pull/917) Host functions (aka imported functions) may not have `&mut vm::Ctx` as first argument, i.e. the presence of the `&mut vm::Ctx` argument is optional. +- [#915](https://github.com/wasmerio/wasmer/pull/915) All backends share the same definition of `Trampoline` (defined in `wasmer-runtime-core`). + +## 0.10.1 - 2019-11-11 + +- [#952](https://github.com/wasmerio/wasmer/pull/952) Use C preprocessor to properly hide trampoline functions on Windows and non-x86_64 targets. + +## 0.10.0 - 2019-11-11 + +Special thanks to [@newpavlov](https://github.com/newpavlov) and [@Maxgy](https://github.com/Maxgy) for their contributions! + +- [#942](https://github.com/wasmerio/wasmer/pull/942) Deny missing docs in runtime core and add missing docs +- [#939](https://github.com/wasmerio/wasmer/pull/939) Fix bug causing attempts to append to files with WASI to delete the contents of the file +- [#940](https://github.com/wasmerio/wasmer/pull/940) Update supported Rust version to 1.38+ +- [#923](https://github.com/wasmerio/wasmer/pull/923) Fix memory leak in the C API caused by an incorrect cast in `wasmer_trampoline_buffer_destroy` +- [#921](https://github.com/wasmerio/wasmer/pull/921) In LLVM backend, annotate all memory accesses with TBAA metadata. +- [#883](https://github.com/wasmerio/wasmer/pull/883) Allow floating point operations to have arbitrary inputs, even including SNaNs. +- [#856](https://github.com/wasmerio/wasmer/pull/856) Expose methods in the runtime C API to get a WASI import object + +## 0.9.0 - 2019-10-23 + +Special thanks to @alocquet for their contributions! + +- [#898](https://github.com/wasmerio/wasmer/pull/898) State tracking is now disabled by default in the LLVM backend. It can be enabled with `--track-state`. +- [#861](https://github.com/wasmerio/wasmer/pull/861) Add descriptions to `unimplemented!` macro in various places +- [#897](https://github.com/wasmerio/wasmer/pull/897) Removes special casing of stdin, stdout, and stderr in WASI. Closing these files now works. Removes `stdin`, `stdout`, and `stderr` from `WasiFS`, replaced by the methods `stdout`, `stdout_mut`, and so on. +- [#863](https://github.com/wasmerio/wasmer/pull/863) Fix min and max for cases involving NaN and negative zero when using the LLVM backend. + +## 0.8.0 - 2019-10-02 + +Special thanks to @jdanford for their contributions! + +- [#850](https://github.com/wasmerio/wasmer/pull/850) New `WasiStateBuilder` API. small, add misc. breaking changes to existing API (for example, changing the preopen dirs arg on `wasi::generate_import_object` from `Vec` to `Vec`) +- [#852](https://github.com/wasmerio/wasmer/pull/852) Make minor grammar/capitalization fixes to README.md +- [#841](https://github.com/wasmerio/wasmer/pull/841) Slightly improve rustdoc documentation and small updates to outdated info in readme files +- [#836](https://github.com/wasmerio/wasmer/pull/836) Update Cranelift fork version to `0.44.0` +- [#839](https://github.com/wasmerio/wasmer/pull/839) Change supported version to stable Rust 1.37+ +- [#834](https://github.com/wasmerio/wasmer/pull/834) Fix panic when unwraping `wasmer` arguments +- [#835](https://github.com/wasmerio/wasmer/pull/835) Add parallel execution example (independent instances created from the same `ImportObject` and `Module` run with rayon) +- [#834](https://github.com/wasmerio/wasmer/pull/834) Fix panic when parsing numerical arguments for no-ABI targets run with the wasmer binary +- [#833](https://github.com/wasmerio/wasmer/pull/833) Add doc example of using ImportObject's new `maybe_with_namespace` method +- [#832](https://github.com/wasmerio/wasmer/pull/832) Delete unused runtime ABI +- [#809](https://github.com/wasmerio/wasmer/pull/809) Fix bugs leading to panics in `LocalBacking`. +- [#831](https://github.com/wasmerio/wasmer/pull/831) Add support for atomic operations, excluding wait and notify, to singlepass. +- [#822](https://github.com/wasmerio/wasmer/pull/822) Update Cranelift fork version to `0.43.1` +- [#829](https://github.com/wasmerio/wasmer/pull/829) Fix deps on `make bench-*` commands; benchmarks don't compile other backends now +- [#807](https://github.com/wasmerio/wasmer/pull/807) Implement Send for `Instance`, breaking change on `ImportObject`, remove method `get_namespace` replaced with `with_namespace` and `maybe_with_namespace` +- [#817](https://github.com/wasmerio/wasmer/pull/817) Add document for tracking features across backends and language integrations, [docs/feature_matrix.md] +- [#823](https://github.com/wasmerio/wasmer/issues/823) Improved Emscripten / WASI integration +- [#821](https://github.com/wasmerio/wasmer/issues/821) Remove patch version on most deps Cargo manifests. This gives Wasmer library users more control over which versions of the deps they use. +- [#820](https://github.com/wasmerio/wasmer/issues/820) Remove null-pointer checks in `WasmPtr` from runtime-core, re-add them in Emscripten +- [#803](https://github.com/wasmerio/wasmer/issues/803) Add method to `Ctx` to invoke functions by their `TableIndex` +- [#790](https://github.com/wasmerio/wasmer/pull/790) Fix flaky test failure with LLVM, switch to large code model. +- [#788](https://github.com/wasmerio/wasmer/pull/788) Use union merge on the changelog file. +- [#785](https://github.com/wasmerio/wasmer/pull/785) Include Apache license file for spectests. +- [#786](https://github.com/wasmerio/wasmer/pull/786) In the LLVM backend, lower atomic wasm operations to atomic machine instructions. +- [#784](https://github.com/wasmerio/wasmer/pull/784) Fix help string for wasmer run. + +## 0.7.0 - 2019-09-12 + Special thanks to @YaronWittenstein @penberg for their contributions. +- [#776](https://github.com/wasmerio/wasmer/issues/776) Allow WASI preopened fds to be closed +- [#774](https://github.com/wasmerio/wasmer/issues/774) Add more methods to the `WasiFile` trait +- [#772](https://github.com/wasmerio/wasmer/issues/772) [#770](https://github.com/wasmerio/wasmer/issues/770) Handle more internal failures by passing back errors +- [#756](https://github.com/wasmerio/wasmer/issues/756) Allow NULL parameter and 0 arity in `wasmer_export_func_call` C API +- [#747](https://github.com/wasmerio/wasmer/issues/747) Return error instead of panicking on traps when using the Wasmer binary +- [#741](https://github.com/wasmerio/wasmer/issues/741) Add validate Wasm fuzz target +- [#733](https://github.com/wasmerio/wasmer/issues/733) Remove dependency on compiler backends for `middleware-common` +- [#732](https://github.com/wasmerio/wasmer/issues/732) [#731](https://github.com/wasmerio/wasmer/issues/731) WASI bug fixes and improvements +- [#726](https://github.com/wasmerio/wasmer/issues/726) Add serialization and deserialization for Wasi State +- [#716](https://github.com/wasmerio/wasmer/issues/716) Improve portability of install script +- [#714](https://github.com/wasmerio/wasmer/issues/714) Add Code of Conduct +- [#708](https://github.com/wasmerio/wasmer/issues/708) Remove unconditional dependency on Cranelift in the C API +- [#703](https://github.com/wasmerio/wasmer/issues/703) Fix compilation on AArch64 Linux +- [#702](https://github.com/wasmerio/wasmer/issues/702) Add SharedMemory to Wasmer. Add `--enable-threads` flag, add partial implementation of atomics to LLVM backend. +- [#698](https://github.com/wasmerio/wasmer/issues/698) [#690](https://github.com/wasmerio/wasmer/issues/690) [#687](https://github.com/wasmerio/wasmer/issues/690) Fix panics in Emscripten +- [#689](https://github.com/wasmerio/wasmer/issues/689) Replace `wasmer_runtime_code::memory::Atomic` with `std::sync::atomic` atomics, changing its interface +- [#680](https://github.com/wasmerio/wasmer/issues/680) [#673](https://github.com/wasmerio/wasmer/issues/673) [#669](https://github.com/wasmerio/wasmer/issues/669) [#660](https://github.com/wasmerio/wasmer/issues/660) [#659](https://github.com/wasmerio/wasmer/issues/659) Misc. runtime and singlepass fixes +- [#677](https://github.com/wasmerio/wasmer/issues/677) [#675](https://github.com/wasmerio/wasmer/issues/675) [#674](https://github.com/wasmerio/wasmer/issues/674) LLVM backend fixes and improvements +- [#671](https://github.com/wasmerio/wasmer/issues/671) Implement fs polling in `wasi::poll_oneoff` for Unix-like platforms +- [#656](https://github.com/wasmerio/wasmer/issues/656) Move CI to Azure Pipelines - [#650](https://github.com/wasmerio/wasmer/issues/650) Implement `wasi::path_rename`, improve WASI FS public api, and allow open files to exist even when the underlying file is deleted - [#643](https://github.com/wasmerio/wasmer/issues/643) Implement `wasi::path_symlink` and improve WASI FS public api IO error reporting - [#608](https://github.com/wasmerio/wasmer/issues/608) Implement wasi syscalls `fd_allocate`, `fd_sync`, `fd_pread`, `path_link`, `path_filestat_set_times`; update WASI fs API in a WIP way; reduce coupling of WASI code to host filesystem; make debug messages from WASI more readable; improve rights-checking when calling syscalls; implement reference counting on inodes; misc bug fixes and improvements @@ -81,7 +181,7 @@ Special thanks to @YaronWittenstein @penberg for their contributions. - [#493](https://github.com/wasmerio/wasmer/pull/493) `wasmer_module_instantiate` has better error messages in the runtime C API - [#474](https://github.com/wasmerio/wasmer/pull/474) Set the install name of the dylib to `@rpath` - [#490](https://github.com/wasmerio/wasmer/pull/490) Add MiddlewareChain and StreamingCompiler to runtime -- [#487](https://github.com/wasmerio/wasmer/pull/487) Fix stack offset check in singlepass backend +- [#487](https://github.com/wasmerio/wasmer/pull/487) Fix stack offset check in singlepass backend - [#450](https://github.com/wasmerio/wasmer/pull/450) Added Metering - [#481](https://github.com/wasmerio/wasmer/pull/481) Added context trampoline into runtime - [#484](https://github.com/wasmerio/wasmer/pull/484) Fix bugs in emscripten socket syscalls diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1c2f0d31e..1685f4722 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -3,12 +3,15 @@ Thank you for your interest in contributing to Wasmer. This document outlines some recommendations on how to contribute. ## Issues & Feature Requests + Please use the issue template and provide a failing example if possible to help us recreate the issue. ## Pull Requests -For large changes, please try reaching the Wasmer using Github Issues or Spectrum Chat to ensure we can accept the change once it is ready. + +For large changes, please try reaching communicating with the Wasmer maintainers via GitHub Issues or Spectrum Chat to ensure we can accept the change once it is ready. We recommend trying the following commands before sending a pull request to ensure code quality: + - `cargo fmt --all` Ensures all code is correctly formatted. - Run `cargo test` in the crates that you are modifying. - Run `cargo build --all` (nightly) or `cargo build --all --exclude wasmer-singlepass-backend` @@ -17,17 +20,14 @@ A comprehensive CI test suite will be run by a Wasmer team member after the PR h ### Common Build Issues -**LLVM Dependency** +#### LLVM Dependency -The LLVM backend requires LLVM to be installed to compile. +`Didn't find usable system-wide LLVM` -So, you may run into the following error: -``` -Didn't find usable system-wide LLVM. -No suitable version of LLVM was found system-wide or pointed -``` +Building Wasmer with the LLVM backend requires LLVM to be installed -**Singlepass Nightly Only** +#### Singlepass Nightly Only -The singlepass crate depends on nightly so you may need to add the `+nightly` cargo flag to compile this crate. `error[E0554]: #![feature] may not be used on the stable release channel` + +Building Wasmer with the singlepass backend requires the nightly version of Rust diff --git a/Cargo.lock b/Cargo.lock index d2888b979..bbdc62b68 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,10 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "adler32" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "aho-corasick" version = "0.7.6" @@ -13,14 +18,9 @@ name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "approx" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "arrayref" version = "0.3.5" @@ -40,7 +40,7 @@ version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -68,9 +68,17 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "base64" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "bincode" -version = "1.1.4" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", @@ -78,28 +86,6 @@ dependencies = [ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "bindgen" -version = "0.51.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cexpr 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", - "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)", - "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "bitflags" version = "1.1.0" @@ -131,6 +117,16 @@ name = "byteorder" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "bytes" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "c2-chacha" version = "0.2.2" @@ -140,30 +136,14 @@ dependencies = [ "ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "capstone" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "capstone-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "capstone-sys" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cargo_toml" -version = "0.6.4" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", - "toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)", + "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -192,39 +172,11 @@ name = "cc" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "cexpr" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "cfg-if" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "cgmath" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "clang-sys" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "clap" version = "2.33.0" @@ -255,63 +207,110 @@ dependencies = [ "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "colored" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "constant_time_eq" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "cranelift-bforest" -version = "0.31.0" +name = "cookie" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cranelift-entity 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cookie_store" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "publicsuffix 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "core-foundation-sys" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cranelift-bforest" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cranelift-entity 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-codegen" -version = "0.31.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cranelift-bforest 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-codegen-meta 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-entity 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-bforest 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen-meta 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen-shared 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "cranelift-codegen-meta" -version = "0.31.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cranelift-entity 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen-shared 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "cranelift-codegen-shared" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cranelift-entity" -version = "0.31.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cranelift-native" -version = "0.31.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cranelift-codegen 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", "raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -331,8 +330,8 @@ dependencies = [ "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", @@ -352,21 +351,21 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.6.3" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "crossbeam-epoch" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)", + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -380,6 +379,14 @@ dependencies = [ "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-queue" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-utils" version = "0.6.6" @@ -389,6 +396,16 @@ dependencies = [ "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-utils" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "csv" version = "1.1.1" @@ -409,6 +426,15 @@ dependencies = [ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ctor" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "digest" version = "0.8.1" @@ -417,28 +443,32 @@ dependencies = [ "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "dtoa" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "dynasm" -version = "0.3.2" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "dynasmrt" -version = "0.3.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", - "take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -447,24 +477,19 @@ version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "enum-methods" -version = "0.0.8" +name = "encoding_rs" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "env_logger" -version = "0.6.2" +name = "erased-serde" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)", - "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -474,7 +499,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -486,6 +511,15 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "error-chain" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", + "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "failure" version = "0.1.5" @@ -507,7 +541,31 @@ dependencies = [ ] [[package]] -name = "field-offset" +name = "flate2" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fnv" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -517,11 +575,31 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "fxhash" -version = "0.2.1" +name = "fuchsia-zircon" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "futures-cpupool" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -544,6 +622,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -564,6 +643,16 @@ dependencies = [ "wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ghost" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "glob" version = "0.2.11" @@ -584,6 +673,23 @@ dependencies = [ "scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "h2" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "heck" version = "0.3.1" @@ -598,46 +704,155 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "humantime" -version = "1.2.0" +name = "http" +version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "http-body" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "httparse" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hyper" +version = "0.12.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "hyper-tls" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "indexmap" -version = "1.1.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "inkwell" version = "0.1.0" -source = "git+https://github.com/wasmerio/inkwell?branch=llvm8-0#8c386c8eeccaea68681710e3822c40153a8030a3" +source = "git+https://github.com/TheDan64/inkwell?rev=781620e9fa30e51a6e03bd0d49b5f5bb7a782520#781620e9fa30e51a6e03bd0d49b5f5bb7a782520" dependencies = [ "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "enum-methods 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)", - "inkwell_internal_macros 0.1.0 (git+https://github.com/wasmerio/inkwell?branch=llvm8-0)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "inkwell_internals 0.1.0 (git+https://github.com/TheDan64/inkwell?rev=781620e9fa30e51a6e03bd0d49b5f5bb7a782520)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "llvm-sys 80.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "inkwell_internal_macros" +name = "inkwell_internals" version = "0.1.0" -source = "git+https://github.com/wasmerio/inkwell?branch=llvm8-0#8c386c8eeccaea68681710e3822c40153a8030a3" +source = "git+https://github.com/TheDan64/inkwell?rev=781620e9fa30e51a6e03bd0d49b5f5bb7a782520#781620e9fa30e51a6e03bd0d49b5f5bb7a782520" dependencies = [ - "cargo_toml 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cargo_toml 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", + "reqwest 0.9.22 (registry+https://github.com/rust-lang/crates.io-index)", "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "inventory" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "ghost 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "inventory-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "inventory-impl" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "itertools" version = "0.8.0" @@ -679,15 +894,6 @@ name = "libc" version = "0.2.62" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "libloading" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "llvm-sys" version = "80.1.1" @@ -716,6 +922,16 @@ dependencies = [ "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "matches" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "md5" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "memchr" version = "2.2.1" @@ -724,22 +940,13 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "memmap" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "memmap" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -750,6 +957,20 @@ dependencies = [ "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mime" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "mime_guess" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "minifb" version = "0.11.2" @@ -765,6 +986,70 @@ dependencies = [ "x11-dl 2.14.0 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "miniz_oxide" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mio" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "native-tls" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", + "schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "net2" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nix" version = "0.15.0" @@ -782,15 +1067,6 @@ name = "nodrop" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "nom" -version = "4.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num" version = "0.1.42" @@ -820,14 +1096,6 @@ dependencies = [ "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "num-traits" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "num-traits" version = "0.2.8" @@ -844,6 +1112,41 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "once_cell" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl" +version = "0.10.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "openssl-probe" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "openssl-sys" +version = "0.9.53" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", + "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "orbclient" version = "0.3.27" @@ -855,7 +1158,7 @@ dependencies = [ [[package]] name = "owning_ref" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -871,6 +1174,24 @@ dependencies = [ "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parallel" +version = "0.1.0" +dependencies = [ + "rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime 0.11.0", + "wasmer-runtime-core 0.11.0", +] + +[[package]] +name = "parallel-guest" +version = "0.1.0" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "md5 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot" version = "0.9.0" @@ -892,12 +1213,17 @@ dependencies = [ "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "peeking_take_while" -version = "0.1.2" +name = "percent-encoding" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "percent-encoding" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -919,6 +1245,16 @@ name = "ppv-lite86" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro-error" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "proc-macro2" version = "0.4.30" @@ -936,14 +1272,16 @@ dependencies = [ ] [[package]] -name = "quick-error" -version = "1.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "quote" -version = "0.3.15" +name = "publicsuffix" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)", + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "quote" @@ -961,18 +1299,6 @@ dependencies = [ "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "rand" version = "0.6.5" @@ -988,7 +1314,7 @@ dependencies = [ "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1073,7 +1399,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1086,7 +1412,7 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1127,22 +1453,22 @@ dependencies = [ [[package]] name = "rayon" -version = "1.1.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "rayon-core" -version = "1.5.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", - "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1194,13 +1520,41 @@ name = "remove_dir_all" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "rgb" -version = "0.8.14" +name = "reqwest" +version = "0.9.22" source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)", + "hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", + "mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)", + "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "rustc-demangle" @@ -1228,6 +1582,15 @@ dependencies = [ "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "schannel" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "scopeguard" version = "1.0.0" @@ -1274,6 +1637,25 @@ dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "security-framework" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", + "security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "security-framework-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "semver" version = "0.9.0" @@ -1333,8 +1715,19 @@ dependencies = [ ] [[package]] -name = "shlex" -version = "0.1.1" +name = "serde_urlencoded" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "slab" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] @@ -1342,11 +1735,24 @@ name = "smallvec" version = "0.6.10" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "smallvec" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "stable_deref_trait" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "string" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "strsim" version = "0.8.0" @@ -1354,32 +1760,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "structopt" -version = "0.2.18" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "structopt-derive" -version = "0.2.18" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)", - "quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "syn" -version = "0.11.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1402,14 +1799,6 @@ dependencies = [ "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "synom" -version = "0.11.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "synstructure" version = "0.10.2" @@ -1421,14 +1810,9 @@ dependencies = [ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", ] -[[package]] -name = "take_mut" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "target-lexicon" -version = "0.4.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1446,15 +1830,7 @@ dependencies = [ "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "termcolor" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1480,7 +1856,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1493,11 +1869,126 @@ dependencies = [ ] [[package]] -name = "toml" -version = "0.4.10" +name = "tokio" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-buf" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-current-thread" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-executor" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-io" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-reactor" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-sync" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-tcp" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-threadpool" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "tokio-timer" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)", + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1508,11 +1999,70 @@ dependencies = [ "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "try-lock" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "try_from" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "typenum" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "typetag" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)", + "inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "typetag-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "typetag-impl" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "unicode-segmentation" version = "1.3.0" @@ -1523,11 +2073,6 @@ name = "unicode-width" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "unicode-xid" -version = "0.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" - [[package]] name = "unicode-xid" version = "0.1.0" @@ -1538,6 +2083,26 @@ name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "url" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "url" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "user32-sys" version = "0.1.3" @@ -1547,6 +2112,19 @@ dependencies = [ "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "uuid" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "vcpkg" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "vec_map" version = "0.8.1" @@ -1557,6 +2135,11 @@ name = "version_check" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "version_check" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -1589,10 +2172,20 @@ version = "2.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "want" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)", + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "wasi" version = "0.5.0" @@ -1600,108 +2193,112 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "wasmer" -version = "0.6.0" +version = "0.11.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.6.0", - "wasmer-dev-utils 0.6.0", - "wasmer-emscripten 0.6.0", - "wasmer-emscripten-tests 0.6.0", + "wasmer-clif-backend 0.11.0", + "wasmer-dev-utils 0.11.0", + "wasmer-emscripten 0.11.0", + "wasmer-emscripten-tests 0.11.0", "wasmer-kernel-loader 0.1.0", - "wasmer-llvm-backend 0.6.0", - "wasmer-middleware-common 0.6.0", - "wasmer-runtime 0.6.0", - "wasmer-runtime-core 0.6.0", - "wasmer-singlepass-backend 0.6.0", - "wasmer-wasi 0.6.0", - "wasmer-wasi-framebuffer 0.6.0", - "wasmer-wasi-tests 0.6.0", + "wasmer-llvm-backend 0.11.0", + "wasmer-middleware-common 0.11.0", + "wasmer-middleware-common-tests 0.11.0", + "wasmer-runtime 0.11.0", + "wasmer-runtime-core 0.11.0", + "wasmer-singlepass-backend 0.11.0", + "wasmer-wasi 0.11.0", + "wasmer-wasi-framebuffer 0.11.0", + "wasmer-wasi-tests 0.11.0", ] [[package]] name = "wasmer-clif-backend" -version = "0.6.0" +version = "0.11.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-codegen 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-entity 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-native 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-native 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "serde-bench 0.0.7 (registry+https://github.com/rust-lang/crates.io-index)", "serde_bytes 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-fork-frontend 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-fork-wasm 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.6.0", - "wasmer-win-exception-handler 0.6.0", - "wasmparser 0.35.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-clif-fork-frontend 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-clif-fork-wasm 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime-core 0.11.0", + "wasmer-win-exception-handler 0.11.0", + "wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmer-clif-fork-frontend" -version = "0.33.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cranelift-codegen 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", + "target-lexicon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmer-clif-fork-wasm" -version = "0.33.0" +version = "0.44.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cranelift-codegen 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", - "cranelift-entity 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-codegen 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cranelift-entity 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-fork-frontend 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.35.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-clif-fork-frontend 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmer-dev-utils" -version = "0.6.0" +version = "0.11.0" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmer-emscripten" -version = "0.6.0" +version = "0.11.0" dependencies = [ "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.6.0", + "wasmer-runtime-core 0.11.0", ] [[package]] name = "wasmer-emscripten-tests" -version = "0.6.0" +version = "0.11.0" dependencies = [ "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.6.0", - "wasmer-dev-utils 0.6.0", - "wasmer-emscripten 0.6.0", - "wasmer-llvm-backend 0.6.0", - "wasmer-runtime-core 0.6.0", - "wasmer-singlepass-backend 0.6.0", + "wasmer-clif-backend 0.11.0", + "wasmer-dev-utils 0.11.0", + "wasmer-emscripten 0.11.0", + "wasmer-llvm-backend 0.11.0", + "wasmer-runtime 0.11.0", + "wasmer-singlepass-backend 0.11.0", ] [[package]] @@ -1709,17 +2306,17 @@ name = "wasmer-kernel-loader" version = "0.1.0" dependencies = [ "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.6.0", + "wasmer-runtime-core 0.11.0", ] [[package]] name = "wasmer-llvm-backend" -version = "0.6.0" +version = "0.11.0" dependencies = [ - "capstone 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", "goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)", - "inkwell 0.1.0 (git+https://github.com/wasmerio/inkwell?branch=llvm8-0)", + "inkwell 0.1.0 (git+https://github.com/TheDan64/inkwell?rev=781620e9fa30e51a6e03bd0d49b5f5bb7a782520)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1728,61 +2325,78 @@ dependencies = [ "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.6.0", - "wasmparser 0.35.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime-core 0.11.0", + "wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasmer-llvm-backend-tests" +version = "0.10.2" +dependencies = [ + "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-llvm-backend 0.11.0", + "wasmer-runtime 0.11.0", + "wasmer-runtime-core 0.11.0", ] [[package]] name = "wasmer-middleware-common" -version = "0.6.0" +version = "0.11.0" +dependencies = [ + "wasmer-runtime-core 0.11.0", +] + +[[package]] +name = "wasmer-middleware-common-tests" +version = "0.11.0" dependencies = [ "criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.6.0", - "wasmer-llvm-backend 0.6.0", - "wasmer-runtime-core 0.6.0", - "wasmer-singlepass-backend 0.6.0", + "wasmer-clif-backend 0.11.0", + "wasmer-llvm-backend 0.11.0", + "wasmer-middleware-common 0.11.0", + "wasmer-runtime-core 0.11.0", + "wasmer-singlepass-backend 0.11.0", ] [[package]] name = "wasmer-runtime" -version = "0.6.0" +version = "0.11.0" dependencies = [ "criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.6.0", - "wasmer-llvm-backend 0.6.0", - "wasmer-runtime-core 0.6.0", - "wasmer-singlepass-backend 0.6.0", + "wasmer-clif-backend 0.11.0", + "wasmer-llvm-backend 0.11.0", + "wasmer-runtime-core 0.11.0", + "wasmer-singlepass-backend 0.11.0", ] [[package]] name = "wasmer-runtime-c-api" -version = "0.6.0" +version = "0.11.0" dependencies = [ "cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime 0.6.0", - "wasmer-runtime-core 0.6.0", + "wasmer-runtime 0.11.0", + "wasmer-runtime-core 0.11.0", + "wasmer-wasi 0.11.0", ] [[package]] name = "wasmer-runtime-core" -version = "0.6.0" +version = "0.11.0" dependencies = [ - "bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)", "cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)", - "colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", "errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", - "field-offset 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "indexmap 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1794,101 +2408,107 @@ dependencies = [ "serde_bytes 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmparser 0.35.3 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "wasmer-runtime-core-tests" +version = "0.11.0" +dependencies = [ + "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-clif-backend 0.11.0", + "wasmer-llvm-backend 0.11.0", + "wasmer-runtime-core 0.11.0", + "wasmer-singlepass-backend 0.11.0", ] [[package]] name = "wasmer-singlepass-backend" -version = "0.6.0" +version = "0.11.0" dependencies = [ + "bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", - "dynasm 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", - "dynasmrt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "dynasm 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", + "dynasmrt 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.6.0", - "wasmparser 0.35.3 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime-core 0.11.0", ] [[package]] name = "wasmer-spectests" -version = "0.6.0" +version = "0.11.0" dependencies = [ "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", "wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.6.0", - "wasmer-llvm-backend 0.6.0", - "wasmer-runtime-core 0.6.0", - "wasmer-singlepass-backend 0.6.0", + "wasmer-clif-backend 0.11.0", + "wasmer-llvm-backend 0.11.0", + "wasmer-runtime 0.11.0", + "wasmer-singlepass-backend 0.11.0", ] [[package]] name = "wasmer-wasi" -version = "0.6.0" +version = "0.11.0" dependencies = [ + "bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "generational-arena 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "getrandom 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", - "rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.6.0", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime-core 0.11.0", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmer-wasi-framebuffer" -version = "0.6.0" +version = "0.11.0" dependencies = [ "minifb 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)", "ref_thread_local 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.6.0", - "wasmer-wasi 0.6.0", + "serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)", + "typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime-core 0.11.0", + "wasmer-wasi 0.11.0", ] [[package]] name = "wasmer-wasi-tests" -version = "0.6.0" +version = "0.11.0" dependencies = [ "glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-clif-backend 0.6.0", - "wasmer-dev-utils 0.6.0", - "wasmer-llvm-backend 0.6.0", - "wasmer-runtime-core 0.6.0", - "wasmer-singlepass-backend 0.6.0", - "wasmer-wasi 0.6.0", + "wasmer-clif-backend 0.11.0", + "wasmer-dev-utils 0.11.0", + "wasmer-llvm-backend 0.11.0", + "wasmer-runtime 0.11.0", + "wasmer-singlepass-backend 0.11.0", + "wasmer-wasi 0.11.0", ] [[package]] name = "wasmer-win-exception-handler" -version = "0.6.0" +version = "0.11.0" dependencies = [ - "bindgen 0.51.0 (registry+https://github.com/rust-lang/crates.io-index)", "cmake 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", - "regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "wasmer-runtime-core 0.6.0", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "wasmer-runtime-core 0.11.0", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "wasmparser" -version = "0.35.3" +version = "0.39.3" source = "registry+https://github.com/rust-lang/crates.io-index" -[[package]] -name = "which" -version = "2.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "winapi" version = "0.2.8" @@ -1896,7 +2516,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "winapi" -version = "0.3.7" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1918,7 +2538,7 @@ name = "winapi-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1927,23 +2547,20 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] -name = "wincolor" -version = "1.0.2" +name = "winreg" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] -name = "winconsole" -version = "0.10.0" +name = "ws2_32-sys" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", - "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)", - "rgb 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1957,117 +2574,149 @@ dependencies = [ ] [metadata] +"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2" "checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d" "checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -"checksum approx 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "08abcc3b4e9339e33a3d0a5ed15d84a687350c05689d825e0f6655eef9e76a94" "checksum arrayref 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "0d382e583f07208808f6b1249e60848879ba3543f57c32277bf52d69c2f0f0ee" "checksum arrayvec 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b8d73f9beda665eaa98ab9e4f7442bd4e7de6652587de55b2525e52e29c1b0ba" "checksum atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)" = "1803c647a3ec87095e7ae7acfca019e98de5ec9a7d01343f611cf3152ed71a90" "checksum autocfg 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "b671c8fb71b457dd4ae18c4ba1e59aa81793daacc361d82fcd410cef0d491875" "checksum backtrace 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)" = "1371048253fa3bac6704bfd6bbfc922ee9bdcee8881330d40f308b81cc5adc55" "checksum backtrace-sys 0.1.31 (registry+https://github.com/rust-lang/crates.io-index)" = "82a830b4ef2d1124a711c71d263c5abdc710ef8e907bd508c88be475cebc422b" -"checksum bincode 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9f04a5e50dc80b3d5d35320889053637d15011aed5e66b66b37ae798c65da6f7" -"checksum bindgen 0.51.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18270cdd7065ec045a6bb4bdcd5144d14a78b3aedb3bc5111e688773ac8b9ad0" +"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" +"checksum bincode 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b8ab639324e3ee8774d296864fbc0dbbb256cf1a41c490b94cba90c082915f92" "checksum bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3d155346769a6855b86399e9bc3814ab343cd3d62c7e985113d46a0ec3c281fd" "checksum blake2b_simd 0.5.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bf775a81bb2d464e20ff170ac20316c7b08a43d11dbc72f0f82e8e8d3d6d0499" "checksum bstr 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "94cdf78eb7e94c566c1f5dbe2abf8fc70a548fc902942a48c4b3a98b48ca9ade" "checksum byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c3dd8985a7111efc5c80b44e23ecdd8c007de8ade3b96595387e812b957cf5" +"checksum bytes 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "206fdffcfa2df7cbe15601ef46c813fce0965eb3286db6b56c583b814b51c81c" "checksum c2-chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7d64d04786e0f528460fc884753cf8dddcc466be308f6026f8e355c41a0e4101" -"checksum capstone 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "031ba51c39151a1d6336ec859646153187204b0147c7b3f6fe2de636f1b8dbb3" -"checksum capstone-sys 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fae25eddcb80e24f98c35952c37a91ff7f8d0f60dbbdafb9763e8d5cc566b8d7" -"checksum cargo_toml 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "097f5ce64ba566a83d9d914fd005de1e5937fdd57d8c5d99a7593040955d75a9" +"checksum cargo_toml 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e7877b00aaf997d7ed66a81281d3a8b9f9da5361df05b72785b985349979a0f3" "checksum cast 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "926013f2860c46252efceabb19f4a6b308197505082c609025aa6706c011d427" "checksum cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0e7e19db9a3892c88c74cbbdcd218196068a928f1b60e736c448b13a1e81f277" "checksum cc 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "b548a4ee81fccb95919d4e22cfea83c7693ebfd78f0495493178db20b3139da7" -"checksum cexpr 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "a7fa24eb00d5ffab90eaeaf1092ac85c04c64aaf358ea6f84505b8116d24c6af" "checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" -"checksum cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)" = "64a4b57c8f4e3a2e9ac07e0f6abc9c24b6fc9e1b54c3478cfb598f3d0023e51c" -"checksum clang-sys 0.28.1 (registry+https://github.com/rust-lang/crates.io-index)" = "81de550971c976f176130da4b2978d3b524eaa0fd9ac31f3ceb5ae1231fb4853" "checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" "checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" "checksum cmake 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "3c84c596dcf125d6781f58e3f4254677ec2a6d8aa56e8501ac277100990b3229" -"checksum colored 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6cdb90b60f2927f8d76139c72dbde7e10c3a2bc47c8594c9c7a66529f2687c03" "checksum constant_time_eq 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "995a44c877f9212528ccc74b21a232f66ad69001e40ede5bcee2ac9ef2657120" -"checksum cranelift-bforest 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "409f92af3dd276e112b72873a3ef02613e3c5f55b81d5d5d04f3157d4f8b8c54" -"checksum cranelift-codegen 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b303542a56fba4cbaeea099fb30ed078b50de0e765fd69f7f5f410adbe31d95b" -"checksum cranelift-codegen-meta 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fee15ed430092a6d14b7c6d627540bef732509b8097ae31e4e35526edaa129c" -"checksum cranelift-entity 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "739222c3740e4a8d6f0375bd95caba9b6c11afcba9f0e1d4f944d6bd99f84600" -"checksum cranelift-native 0.31.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce451162d18b7d82118e23ea7e12f7d8b98557549404bd71215548de79eb0736" +"checksum cookie 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "888604f00b3db336d2af898ec3c1d5d0ddf5e6d462220f2ededc33a87ac4bbd5" +"checksum cookie_store 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "46750b3f362965f197996c4448e4a0935e791bf7d6631bfce9ee0af3d24c919c" +"checksum core-foundation 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "25b9e03f145fd4f2bf705e07b900cd41fc636598fe5dc452fd0db1441c3f496d" +"checksum core-foundation-sys 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e7ca8a5221364ef15ce201e8ed2f609fc312682a8f4e0e3d4aa5879764e0fa3b" +"checksum cranelift-bforest 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fff04f4ad82c9704a22e753c6268cc6a89add76f094b837cefbba1c665411451" +"checksum cranelift-codegen 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff4a221ec1b95df4b1d20a99fec4fe92a28bebf3a815f2eca72b26f9a627485" +"checksum cranelift-codegen-meta 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dd47f665e2ee8f177b97d1f5ce2bd70f54d3b793abb26d92942bfaa4a381fe9f" +"checksum cranelift-codegen-shared 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05bb95945fd940bd5fc2616b063ce69e55de3d9449a32fa40f6bb99a927085bf" +"checksum cranelift-entity 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e8753f15d9bde04988834705d437b6f6e4b4da0527968b8d40d7342262d43052" +"checksum cranelift-native 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd16b58e95af9ee837218cf41e70306becc1fc7d7dada55dac42df5130a4a4ba" +"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" "checksum criterion 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "0363053954f3e679645fc443321ca128b7b950a6fe288cf5f9335cc22ee58394" "checksum criterion-plot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "76f9212ddf2f4a9eb2d401635190600656a1f88a932ef53d06e7fa4c7e02fb8e" -"checksum crossbeam-deque 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "05e44b8cf3e1a625844d1750e1f7820da46044ff6d28f4d43e455ba3e5bb2c13" -"checksum crossbeam-epoch 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fedcd6772e37f3da2a9af9bf12ebe046c0dfe657992377b4df982a2b54cd37a9" +"checksum crossbeam-deque 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3aa945d63861bfe624b55d153a39684da1e8c0bc8fba932f7ee3a3c16cea3ca" +"checksum crossbeam-epoch 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5064ebdbf05ce3cb95e45c8b086f72263f4166b29b97f6baff7ef7fe047b55ac" "checksum crossbeam-queue 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7c979cd6cfe72335896575c6b5688da489e420d36a27a0b9eb0c73db574b4a4b" +"checksum crossbeam-queue 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dfd6515864a82d2f877b42813d4553292c6659498c9a2aa31bab5a15243c2700" "checksum crossbeam-utils 0.6.6 (registry+https://github.com/rust-lang/crates.io-index)" = "04973fa96e96579258a5091af6003abde64af786b860f18622b82e026cca60e6" +"checksum crossbeam-utils 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ce446db02cdc3165b94ae73111e570793400d0794e46125cc4056c81cbb039f4" "checksum csv 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "37519ccdfd73a75821cac9319d4fce15a81b9fcf75f951df5b9988aa3a0af87d" "checksum csv-core 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "9b5cadb6b25c77aeff80ba701712494213f4a8418fcda2ee11b6560c3ad0bf4c" +"checksum ctor 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc" "checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" -"checksum dynasm 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f36d49ab6f8ecc642d2c6ee10fda04ba68003ef0277300866745cdde160e6b40" -"checksum dynasmrt 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a4c408a211e7f5762829f5e46bdff0c14bc3b1517a21a4bb781c716bf88b0c68" +"checksum dtoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "ea57b42383d091c85abcc2706240b94ab2a8fa1fc81c10ff23c4de06e2a90b5e" +"checksum dynasm 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "42a814e1edeb85dd2a3c6fc0d6bf76d02ca5695d438c70ecee3d90774f3259c5" +"checksum dynasmrt 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8a393aaeb4441a48bcf47b5b6155971f82cc1eb77e22855403ccc0415ac8328d" "checksum either 1.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "5527cfe0d098f36e3f8839852688e63c8fff1c90b2b405aef730615f9a7bcf7b" -"checksum enum-methods 0.0.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7798e7da2d4cb0d6d6fc467e8d6b5bf247e9e989f786dde1732d79899c32bb10" -"checksum env_logger 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aafcde04e90a5226a6443b7aabdb016ba2f8307c847d524724bd9b346dd1a2d3" +"checksum encoding_rs 0.8.20 (registry+https://github.com/rust-lang/crates.io-index)" = "87240518927716f79692c2ed85bfe6e98196d18c6401ec75355760233a7e12e9" +"checksum erased-serde 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "3beee4bc16478a1b26f2e80ad819a52d24745e292f521a63c16eea5f74b7eb60" "checksum errno 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" "checksum errno-dragonfly 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +"checksum error-chain 0.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3ab49e9dcb602294bc42f9a7dfc9bc6e936fca4418ea300dbfb84fe16de0b7d9" "checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2" "checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1" -"checksum field-offset 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "64e9bc339e426139e02601fa69d101e96a92aee71b58bc01697ec2a63a5c9e68" +"checksum flate2 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "ad3c5233c9a940c8719031b423d7e6c16af66e031cb0420b0896f5245bf181d3" +"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" +"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" "checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" -"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum futures 0.1.29 (registry+https://github.com/rust-lang/crates.io-index)" = "1b980f2816d6ee8673b6517b52cb0e808a180efc92e5c19d02cdda79066703ef" +"checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" "checksum gcc 0.3.55 (registry+https://github.com/rust-lang/crates.io-index)" = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" "checksum gdi32-sys 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e3eb92c1107527888f86b6ebb0b7f82794777dbf172a932998660a0a2e26c11" "checksum generational-arena 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4024f96ffa0ebaaf36aa589cd41f2fd69f3a5e6fd02c86e11e12cdf41d5b46a3" "checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" "checksum getrandom 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "6171a6cc63fbabbe27c2b5ee268e8b7fe5dc1eb0dd2dfad537c1dfed6f69117e" +"checksum ghost 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2a36606a68532b5640dc86bb1f33c64b45c4682aad4c50f3937b317ea387f3d6" "checksum glob 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" "checksum glob 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" "checksum goblin 0.0.24 (registry+https://github.com/rust-lang/crates.io-index)" = "e3fa261d919c1ae9d1e4533c4a2f99e10938603c4208d56c05bec7a872b661b0" +"checksum h2 0.1.26 (registry+https://github.com/rust-lang/crates.io-index)" = "a5b34c246847f938a410a03c5458c7fee2274436675e76d8b903c08efc29c462" "checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" -"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114" -"checksum indexmap 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4d6d89e0948bf10c08b9ecc8ac5b83f07f857ebe2c0cbe38de15b4e4f510356" -"checksum inkwell 0.1.0 (git+https://github.com/wasmerio/inkwell?branch=llvm8-0)" = "" -"checksum inkwell_internal_macros 0.1.0 (git+https://github.com/wasmerio/inkwell?branch=llvm8-0)" = "" +"checksum http 0.1.21 (registry+https://github.com/rust-lang/crates.io-index)" = "d6ccf5ede3a895d8856620237b2f02972c1bbc78d2965ad7fe8838d4a0ed41f0" +"checksum http-body 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" +"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9" +"checksum hyper 0.12.35 (registry+https://github.com/rust-lang/crates.io-index)" = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6" +"checksum hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3a800d6aa50af4b5850b2b0f659625ce9504df908e9733b635720483be26174f" +"checksum idna 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "38f09e0f0b1fb55fdee1f17470ad800da77af5186a1a76c026b679358b7e844e" +"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +"checksum indexmap 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712d7b3ea5827fcb9d4fda14bf4da5f136f0db2ae9c8f4bd4e2d1c6fde4e6db2" +"checksum inkwell 0.1.0 (git+https://github.com/TheDan64/inkwell?rev=781620e9fa30e51a6e03bd0d49b5f5bb7a782520)" = "" +"checksum inkwell_internals 0.1.0 (git+https://github.com/TheDan64/inkwell?rev=781620e9fa30e51a6e03bd0d49b5f5bb7a782520)" = "" +"checksum inventory 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f4cece20baea71d9f3435e7bbe9adf4765f091c5fe404975f844006964a71299" +"checksum inventory-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c2869bf972e998977b1cb87e60df70341d48e48dca0823f534feb91ea44adaf9" +"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" "checksum itertools 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5b8467d9c1cebe26feb08c640139247fac215782d35371ade9a2136ed6085358" "checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" "checksum lazy_static 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "76f033c7ad61445c5b347c7382dd1237847eb1bce590fe50365dcb33d546be73" "checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" "checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" -"checksum libloading 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f2b111a074963af1d37a139918ac6d49ad1d0d5e47f72fd55388619691a7d753" "checksum llvm-sys 80.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2110cd4daf9cd8e39dd3b933b1a2a2ac7315e91f7c92b3a20beab526c63b5978" "checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc" "checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +"checksum md5 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e6bcd6433cff03a4bfc3d9834d504467db1f1cf6d0ea765d37d330249ed629d" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" -"checksum memmap 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e2ffa2c986de11a9df78620c01eeaaf27d94d3ff02bf81bfcca953102dd0c6ff" "checksum memmap 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6585fd95e7bb50d6cc31e20d4cf9afb4e2ba16c5846fc76793f11218da9c475b" "checksum memoffset 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ce6075db033bbbb7ee5a0bbd3a3186bbae616f57fb001c485c7ff77955f8177f" +"checksum mime 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)" = "dd1d63acd1b78403cc0c325605908475dd9b9a3acbf65ed8bcab97e27014afcf" +"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599" "checksum minifb 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "85c2cedede43aad485232acf318a3e191ee5a3c2250ca8a3556b849a48e8b901" +"checksum miniz_oxide 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6f3f74f726ae935c3f514300cc6773a0c9492abc5e972d42ba0c0ebb88757625" +"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f" +"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" +"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" -"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6" "checksum num 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "4703ad64153382334aa8db57c637364c322d3372e097840c72000dabdcf6156e" "checksum num-integer 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "b85e541ef8255f6cf42bbfe4ef361305c6c135d10919ecc26126c4e5ae94bc09" "checksum num-iter 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "76bd5272412d173d6bf9afdf98db8612bbabc9a7a830b7bfc9c188911716132e" -"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" "checksum num-traits 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "6ba9a427cfca2be13aa6f6403b0b7e7368fe982bfa16fccc450ce74c46cd9b32" "checksum num_cpus 1.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "bcef43580c035376c0705c42792c294b66974abbfd2789b511784023f71f3273" +"checksum once_cell 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "891f486f630e5c5a4916c7e16c4b24a53e78c860b646e9f8e005e4f16847bfed" +"checksum openssl 0.10.26 (registry+https://github.com/rust-lang/crates.io-index)" = "3a3cc5799d98e1088141b8e01ff760112bbd9f19d850c124500566ca6901a585" +"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de" +"checksum openssl-sys 0.9.53 (registry+https://github.com/rust-lang/crates.io-index)" = "465d16ae7fc0e313318f7de5cecf57b2fbe7511fd213978b457e1c96ff46736f" "checksum orbclient 0.3.27 (registry+https://github.com/rust-lang/crates.io-index)" = "f8b18f57ab94fbd058e30aa57f712ec423c0bb7403f8493a6c58eef0c36d9402" -"checksum owning_ref 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "cdf84f41639e037b484f93433aa3897863b561ed65c6e59c7073d7c561710f37" +"checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum page_size 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f89ef58b3d32420dbd1a43d2f38ae92f6239ef12bb556ab09ca55445f5a67242" "checksum parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f842b1982eb6c2fe34036a4fbfb06dd185a3f5c8edfaacdf7d1ea10b07de6252" "checksum parking_lot_core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b876b1b9e7ac6e1a74a6da34d25c42e17e8862aa409cbbbdcfc8d86c6f3bc62b" -"checksum peeking_take_while 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" +"checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" +"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" "checksum pkg-config 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7c1d2cfa5a714db3b5f24f0915e74fcdf91d09d496ba61329705dda7774d2af" "checksum plain 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" "checksum ppv-lite86 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e3cbf9f658cdb5000fcf6f362b8ea2ba154b9f146a61c7a20d647034c6b6561b" +"checksum proc-macro-error 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aeccfe4d5d8ea175d5f0e4a2ad0637e0f4121d63bd99d356fb1f39ab2e7c6097" "checksum proc-macro2 0.4.30 (registry+https://github.com/rust-lang/crates.io-index)" = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" "checksum proc-macro2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c5c2380ae88876faae57698be9e9775e3544decad214599c3a6266cca6ac802" -"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0" -"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" +"checksum publicsuffix 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9bf259a81de2b2eb9850ec990ec78e6a25319715584fd7652b9b26f96fcb1510" "checksum quote 0.6.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" "checksum quote 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" -"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" "checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" "checksum rand 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d47eab0e83d9693d40f825f86948aa16eff6750ead4bdffc4ab95b8b3a7f052c" "checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" @@ -2084,8 +2733,8 @@ dependencies = [ "checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" "checksum rand_xoshiro 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "03b418169fb9c46533f326efd6eed2576699c44ca92d3052a066214a8d828929" "checksum raw-cpuid 6.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "30a9d219c32c9132f7be513c18be77c9881c7107d2ab5569d205a6a0f0e6dc7d" -"checksum rayon 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4b0186e22767d5b9738a05eab7c6ac90b15db17e5b5f9bd87976dd7d89a10a4" -"checksum rayon-core 1.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ebbe0df8435ac0c397d467b6cad6d25543d06e8a019ef3f6af3c384597515bd2" +"checksum rayon 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "43739f8831493b276363637423d3622d4bd6394ab6f0a9c4a552e208aeb7fddd" +"checksum rayon-core 1.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8bf17de6f23b05473c437eb958b9c850bfc8af0961fe17b4cc92d5a627b4791" "checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" "checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" "checksum ref_thread_local 0.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d813022b2e00774a48eaf43caaa3c20b45f040ba8cbf398e2e8911a06668dbe6" @@ -2093,16 +2742,19 @@ dependencies = [ "checksum regex-automata 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "92b73c2a1770c255c240eaa4ee600df1704a38dc3feaa6e949e7fcd4f8dc09f9" "checksum regex-syntax 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b143cceb2ca5e56d5671988ef8b15615733e7ee16cd348e064333b251b89343f" "checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" -"checksum rgb 0.8.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2089e4031214d129e201f8c3c8c2fe97cd7322478a0d1cdf78e7029b0042efdb" +"checksum reqwest 0.9.22 (registry+https://github.com/rust-lang/crates.io-index)" = "2c2064233e442ce85c77231ebd67d9eca395207dec2127fe0bbedde4bd29a650" "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" "checksum ryu 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c92464b447c0ee8c4fb3824ecc8383b81717b9f1e74ba2e72540aef7b9f82997" "checksum same-file 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "585e8ddcedc187886a30fa705c47985c3fa88d06624095856b36ca0b82ff4421" +"checksum schannel 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "87f550b06b6cba9c8b8be3ee73f391990116bf527450d2556e9b9ce263b9a021" "checksum scopeguard 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b42e15e59b18a828bbf5c58ea01debb36b9b096346de35d941dcb89009f24a0d" "checksum scroll 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2f84d114ef17fd144153d608fba7c446b0145d038985e7a8cc5d08bb0ce20383" "checksum scroll_derive 0.9.5 (registry+https://github.com/rust-lang/crates.io-index)" = "8f1aa96c45e7f5a91cb7fabe7b279f02fea7126239fc40b732316e8b6a2d0fcb" "checksum sdl2 0.32.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d051a07231e303f5f719da78cb6f7394f6d5b54f733aef5b0b447804a83edd7b" "checksum sdl2-sys 0.32.6 (registry+https://github.com/rust-lang/crates.io-index)" = "34e71125077d297d57e4c1acfe8981b5bdfbf5a20e7b589abfdcb33bf1127f86" +"checksum security-framework 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8ef2429d7cefe5fd28bd1d2ed41c944547d4ff84776f5935b456da44593a16df" +"checksum security-framework-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e31493fc37615debb8c5090a7aeb4a9730bc61e77ab10b9af59f1a202284f895" "checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" "checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" "checksum serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "fec2851eb56d010dc9a21b89ca53ee75e6528bab60c11e89d38390904982da9f" @@ -2110,51 +2762,70 @@ dependencies = [ "checksum serde_bytes 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)" = "45af0182ff64abaeea290235eb67da3825a576c5d53e642c4d5b652e12e6effc" "checksum serde_derive 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)" = "cb4dc18c61206b08dc98216c98faa0232f4337e1e1b8574551d5bad29ea1b425" "checksum serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)" = "051c49229f282f7c6f3813f8286cc1e3323e8051823fce42c7ea80fe13521704" -"checksum shlex 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" +"checksum serde_urlencoded 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)" = "642dd69105886af2efd227f75a520ec9b44a820d65bc133a9131f7d229fd165a" +"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" "checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7" +"checksum smallvec 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ecf3b85f68e8abaa7555aa5abdb1153079387e60b718283d732f03897fcfc86" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" +"checksum string 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d24114bfcceb867ca7f71a0d3fe45d45619ec47a6fbfa98cb14e14250bfa5d6d" "checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -"checksum structopt 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "16c2cdbf9cc375f15d1b4141bc48aeef444806655cd0e904207edc8d68d86ed7" -"checksum structopt-derive 0.2.18 (registry+https://github.com/rust-lang/crates.io-index)" = "53010261a84b37689f9ed7d395165029f9cc7abb9f56bbfe86bee2597ed25107" -"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" +"checksum structopt 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "30b3a3e93f5ad553c38b3301c8a0a0cec829a36783f6a0c467fc4bf553a5f5bf" +"checksum structopt-derive 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea692d40005b3ceba90a9fe7a78fa8d4b82b0ce627eebbffc329aab850f3410e" "checksum syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)" = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" "checksum syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "158521e6f544e7e3dcfc370ac180794aa38cb34a1b1e07609376d4adcf429b93" -"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" "checksum synstructure 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "02353edf96d6e4dc81aea2d8490a7e9db177bf8acb0e951c24940bf866cb313f" -"checksum take_mut 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" -"checksum target-lexicon 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1b0ab4982b8945c35cc1c46a83a9094c414f6828a099ce5dcaa8ee2b04642dcb" +"checksum target-lexicon 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7975cb2c6f37d77b190bc5004a2bb015971464756fde9514651a525ada2a741a" "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" -"checksum termcolor 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "96d6098003bde162e4277c70665bd87c326f5a0c3f3fbfb285787fa482d54e6e" "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" "checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" "checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" "checksum tinytemplate 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4574b75faccaacddb9b284faecdf0b544b80b6b294f3d062d325c5726a209c20" -"checksum toml 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)" = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" +"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6" +"checksum tokio-buf 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" +"checksum tokio-current-thread 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "d16217cad7f1b840c5a97dfb3c43b0c871fef423a6e8d2118c604e843662a443" +"checksum tokio-executor 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "ca6df436c42b0c3330a82d855d2ef017cd793090ad550a6bc2184f4b933532ab" +"checksum tokio-io 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5090db468dad16e1a7a54c8c67280c5e4b544f3d3e018f0b913b400261f85926" +"checksum tokio-reactor 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "6732fe6b53c8d11178dcb77ac6d9682af27fc6d4cb87789449152e5377377146" +"checksum tokio-sync 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "d06554cce1ae4a50f42fba8023918afa931413aded705b560e29600ccf7c6d76" +"checksum tokio-tcp 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" +"checksum tokio-threadpool 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c32ffea4827978e9aa392d2f743d973c1dfa3730a2ed3f22ce1e6984da848c" +"checksum tokio-timer 0.2.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1739638e364e558128461fc1ad84d997702c8e31c2e6b18fb99842268199e827" "checksum toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c7aabe75941d914b72bf3e5d3932ed92ce0664d49d8432305a8b547c37227724" +"checksum try-lock 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" +"checksum try_from 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "283d3b89e1368717881a9d51dad843cc435380d8109c9e47d38780a324698d8b" "checksum typenum 1.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "612d636f949607bdf9b123b4a6f6d966dedf3ff669f7f045890d3a4a73948169" +"checksum typetag 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ebb2c484029d695fb68a06d80e1536c68d491b3e0cf874c66abed255e831cfe" +"checksum typetag-impl 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b63fd4799e4d0ec5cf0b055ebb8e2c3a657bbf76a84f6edc77ca60780e000204" +"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +"checksum unicode-normalization 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "b561e267b2326bb4cebfc0ef9e68355c7abe6c6f522aeac2f5bf95d56c59bdcf" "checksum unicode-segmentation 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1967f4cdfc355b37fd76d2a954fb2ed3871034eb4f26d60537d88795cfc332a9" "checksum unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7007dbd421b92cc6e28410fe7362e2e0a2503394908f417b68ec8d1c364c4e20" -"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" +"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a" +"checksum url 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "75b414f6c464c879d7f9babf951f23bc3743fb7313c081b2e6ca719067ea9d61" "checksum user32-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e6b719983b952c04198829b51653c06af36f0e44c967fcc1a2bb397ceafbf80a" +"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a" +"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168" "checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" +"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum wabt 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "94b5f5d6984ca42df66280baa8a15ac188a173ddaf4580b574a98931c01920e7" "checksum wabt-sys 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b064c81821100adb4b71923cecfc67fef083db21c3bbd454b0162c7ffe63eeaa" "checksum walkdir 2.2.9 (registry+https://github.com/rust-lang/crates.io-index)" = "9658c94fa8b940eab2250bd5a457f9c48b748420d71293b165c8cdbe2f55f71e" +"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" "checksum wasi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fd5442abcac6525a045cc8c795aedb60da7a2e5e89c7bf18a0d5357849bb23c7" -"checksum wasmer-clif-fork-frontend 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3bd6bec1587a3b11222f4ff129fd119785713c41de413f54f99d3c03743812f4" -"checksum wasmer-clif-fork-wasm 0.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a8f323e612fe2549391255a09f89c927d7feb0ec4bf0c2cad9e3c089bdca42fc" -"checksum wasmparser 0.35.3 (registry+https://github.com/rust-lang/crates.io-index)" = "099aaf77635ffad3d9ab57253c29b16a46e93755c3e54cfe33fb8cf4b54f760b" -"checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164" +"checksum wasmer-clif-fork-frontend 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0cf2f552a9c1fda0555087170424bd8fedc63a079a97bb5638a4ef9b0d9656aa" +"checksum wasmer-clif-fork-wasm 0.44.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0073b512e1af5948d34be7944b74c747bbe735ccff2e2f28c26ed4c90725de8e" +"checksum wasmparser 0.39.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c702914acda5feeeffbc29e4d953e5b9ce79d8b98da4dbf18a77086e116c5470" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" -"checksum winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "f10e386af2b13e47c89e7236a7a14a086791a2b88ebad6df9bf42040195cf770" +"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" "checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9" "checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -"checksum wincolor 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "96f5016b18804d24db43cebf3c77269e7569b8954a8464501c216cc5e070eaa9" -"checksum winconsole 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ef84b96d10db72dd980056666d7f1e7663ce93d82fa33b63e71c966f4cf5032" +"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" +"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" "checksum x11-dl 2.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "326c500cdc166fd7c70dd8c8a829cd5c0ce7be5a5d98c25817de2b9bdc67faf8" diff --git a/Cargo.toml b/Cargo.toml index e91dcf459..eb6019280 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,13 @@ [package] name = "wasmer" -version = "0.6.0" +version = "0.11.0" authors = ["The Wasmer Engineering Team "] edition = "2018" repository = "https://github.com/wasmerio/wasmer" publish = true description = "High-Performance WebAssembly JIT interpreter" license = "MIT" +default-run = "wasmer" include = [ "examples/**/*", "src/**/*", @@ -19,15 +20,14 @@ include = [ ] [dependencies] -byteorder = "1.3.2" -errno = "0.2.4" -structopt = "0.2.18" +byteorder = "1.3" +errno = "0.2" +structopt = "0.3" wabt = "0.9.1" -wasmer-clif-backend = { path = "lib/clif-backend" } +wasmer-clif-backend = { path = "lib/clif-backend", optional = true } wasmer-singlepass-backend = { path = "lib/singlepass-backend", optional = true } wasmer-middleware-common = { path = "lib/middleware-common" } wasmer-runtime = { path = "lib/runtime" } -# wasmer-runtime-abi = { path = "lib/runtime-abi", optional = true } wasmer-runtime-core = { path = "lib/runtime-core" } wasmer-emscripten = { path = "lib/emscripten" } wasmer-llvm-backend = { path = "lib/llvm-backend", optional = true } @@ -35,6 +35,7 @@ wasmer-wasi = { path = "lib/wasi", optional = true } wasmer-kernel-loader = { path = "lib/kernel-loader", optional = true } wasmer-dev-utils = { path = "lib/dev-utils", optional = true } wasmer-wasi-tests = { path = "lib/wasi-tests", optional = true } +wasmer-middleware-common-tests = { path = "lib/middleware-common-tests", optional = true } wasmer-emscripten-tests = { path = "lib/emscripten-tests", optional = true } wasmer-wasi-framebuffer = { path = "lib/wasi-framebuffer", optional = true } @@ -43,13 +44,14 @@ members = [ "lib/clif-backend", "lib/singlepass-backend", "lib/runtime", - # "lib/runtime-abi", "lib/runtime-core", + "lib/runtime-core-tests", "lib/emscripten", "lib/spectests", "lib/win-exception-handler", "lib/runtime-c-api", "lib/llvm-backend", + "lib/llvm-backend-tests", "lib/wasi", "lib/middleware-common", "lib/kernel-loader", @@ -58,43 +60,56 @@ members = [ "lib/wasi-framebuffer", "lib/wasi-tests", "lib/emscripten-tests", - "examples/plugin-for-example" + "lib/middleware-common-tests", + "examples/parallel", + "examples/plugin-for-example", + "examples/parallel-guest", ] [build-dependencies] wabt = "0.9.1" -glob = "0.3.0" -rustc_version = "0.2.3" +glob = "0.3" +rustc_version = "0.2" + +[dev-dependencies] +serde = { version = "1", features = ["derive"] } # used by the plugin example +typetag = "0.1" # used by the plugin example [features] -default = ["fast-tests", "wasi", "backend-cranelift"] +default = ["fast-tests", "wasi"] "loader-kernel" = ["wasmer-kernel-loader"] debug = ["wasmer-runtime-core/debug"] trace = ["wasmer-runtime-core/trace"] +docs = ["wasmer-runtime/docs"] extra-debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"] # This feature will allow cargo test to run much faster fast-tests = [] backend-cranelift = [ + "wasmer-clif-backend", "wasmer-runtime-core/backend-cranelift", "wasmer-runtime/cranelift", - "wasmer-middleware-common/clif" + "wasmer-middleware-common-tests/clif", ] backend-llvm = [ "wasmer-llvm-backend", "wasmer-runtime-core/backend-llvm", "wasmer-runtime/llvm", - "wasmer-middleware-common/llvm" + "wasmer-middleware-common-tests/llvm", ] backend-singlepass = [ "wasmer-singlepass-backend", "wasmer-runtime-core/backend-singlepass", "wasmer-runtime/singlepass", - "wasmer-middleware-common/singlepass" + "wasmer-middleware-common-tests/singlepass", ] wasi = ["wasmer-wasi"] -# vfs = ["wasmer-runtime-abi"] experimental-framebuffer = ["wasmer-wasi-framebuffer"] +managed = ["backend-singlepass", "wasmer-runtime-core/managed"] [[example]] name = "plugin" crate-type = ["bin"] + +[[example]] +name = "callback" +crate-type = ["bin"] diff --git a/Dockerfile b/Dockerfile index 43fcda359..73507be30 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM circleci/rust:1.33.0-stretch as wasmer-build-env +FROM circleci/rust:1.38.0-stretch as wasmer-build-env RUN sudo apt-get update && \ sudo apt-get install -y --no-install-recommends \ cmake \ @@ -17,9 +17,9 @@ FROM wasmer-build-env AS wasmer-build WORKDIR /home/circleci/wasmer COPY . /home/circleci/wasmer RUN sudo chmod -R 777 . -RUN cargo build --release +RUN cargo build --release --features backend-cranelift FROM debian:stretch AS wasmer WORKDIR /root/ COPY --from=wasmer-build /home/circleci/wasmer/target/release/wasmer . -ENTRYPOINT ["./wasmer"] \ No newline at end of file +ENTRYPOINT ["./wasmer"] diff --git a/Makefile b/Makefile index a12d14fe5..4acde7925 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,15 @@ -.PHONY: spectests emtests clean build install lint precommit +.PHONY: spectests emtests clean build install lint precommit docs examples # Generate files generate-spectests: - WASMER_RUNTIME_GENERATE_SPECTESTS=1 cargo build -p wasmer-runtime-core --release + WASMER_RUNTIME_GENERATE_SPECTESTS=1 cargo build -p wasmer-runtime-core --release \ + && echo "formatting" \ + && cargo fmt generate-emtests: - WASM_EMSCRIPTEN_GENERATE_EMTESTS=1 cargo build -p wasmer-emscripten-tests --release + WASM_EMSCRIPTEN_GENERATE_EMTESTS=1 cargo build -p wasmer-emscripten-tests --release \ + && echo "formatting" \ + && cargo fmt generate-wasitests: wasitests-setup WASM_WASI_GENERATE_WASITESTS=1 cargo build -p wasmer-wasi-tests --release -vv \ @@ -21,7 +25,7 @@ generate: generate-spectests generate-emtests generate-wasitests # Spectests spectests-singlepass: - cargo test --manifest-path lib/spectests/Cargo.toml --release --features singlepass -- --nocapture + cargo test --manifest-path lib/spectests/Cargo.toml --release --features singlepass -- --nocapture --test-threads 1 spectests-cranelift: cargo test --manifest-path lib/spectests/Cargo.toml --release --features clif -- --nocapture @@ -50,13 +54,13 @@ emtests: emtests-unit emtests-singlepass emtests-cranelift emtests-llvm # Middleware tests middleware-singlepass: - cargo test --manifest-path lib/middleware-common/Cargo.toml --release --features singlepass + cargo test --manifest-path lib/middleware-common-tests/Cargo.toml --release --features singlepass middleware-cranelift: - cargo test --manifest-path lib/middleware-common/Cargo.toml --release --features clif + cargo test --manifest-path lib/middleware-common-tests/Cargo.toml --release --features clif middleware-llvm: - cargo test --manifest-path lib/middleware-common/Cargo.toml --release --features llvm + cargo test --manifest-path lib/middleware-common-tests/Cargo.toml --release --features llvm middleware: middleware-singlepass middleware-cranelift middleware-llvm @@ -70,12 +74,13 @@ wasitests-singlepass: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features singlepass -- --test-threads=1 wasitests-cranelift: wasitests-setup - cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 + cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 --nocapture wasitests-llvm: wasitests-setup cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features llvm -- --test-threads=1 -wasitests-unit: +wasitests-unit: wasitests-setup + cargo test --manifest-path lib/wasi-tests/Cargo.toml --release --features clif -- --test-threads=1 --nocapture cargo test --manifest-path lib/wasi/Cargo.toml --release wasitests: wasitests-unit wasitests-singlepass wasitests-cranelift wasitests-llvm @@ -84,22 +89,43 @@ wasitests: wasitests-unit wasitests-singlepass wasitests-cranelift wasitests-llv # Backends singlepass: spectests-singlepass emtests-singlepass middleware-singlepass wasitests-singlepass cargo test -p wasmer-singlepass-backend --release + cargo test -p wasmer-runtime-core-tests --release --no-default-features --features backend-singlepass cranelift: spectests-cranelift emtests-cranelift middleware-cranelift wasitests-cranelift cargo test -p wasmer-clif-backend --release + cargo test -p wasmer-runtime-core-tests --release llvm: spectests-llvm emtests-llvm wasitests-llvm cargo test -p wasmer-llvm-backend --release + cargo test -p wasmer-llvm-backend-tests --release + cargo test -p wasmer-runtime-core-tests --release --no-default-features --features backend-llvm # All tests capi: - cargo build --release + cargo build --release --features backend-cranelift cargo build -p wasmer-runtime-c-api --release + +test-capi: capi cargo test -p wasmer-runtime-c-api --release -test-rest: capi - cargo test --release --all --exclude wasmer-runtime-c-api --exclude wasmer-emscripten --exclude wasmer-spectests --exclude wasmer-wasi --exclude wasmer-middleware-common --exclude wasmer-singlepass-backend --exclude wasmer-clif-backend --exclude wasmer-llvm-backend --exclude wasmer-wasi-tests --exclude wasmer-emscripten-tests +capi-test: test-capi + +test-rest: + cargo test --release \ + --all \ + --exclude wasmer-runtime-c-api \ + --exclude wasmer-emscripten \ + --exclude wasmer-spectests \ + --exclude wasmer-wasi \ + --exclude wasmer-middleware-common \ + --exclude wasmer-middleware-common-tests \ + --exclude wasmer-singlepass-backend \ + --exclude wasmer-clif-backend \ + --exclude wasmer-llvm-backend \ + --exclude wasmer-wasi-tests \ + --exclude wasmer-emscripten-tests \ + --exclude wasmer-runtime-core-tests circleci-clean: @if [ ! -z "${CIRCLE_JOB}" ]; then rm -f /home/circleci/project/target/debug/deps/libcranelift_wasm* && rm -f /Users/distiller/project/target/debug/deps/libcranelift_wasm*; fi; @@ -108,12 +134,16 @@ test: spectests emtests middleware wasitests circleci-clean test-rest # Integration tests -integration-tests: release-clif +integration-tests: release-clif examples echo "Running Integration Tests" ./integration_tests/lua/test.sh ./integration_tests/nginx/test.sh ./integration_tests/cowsay/test.sh +examples: + cargo run --example plugin + cargo run --example callback + # Utils lint: @@ -122,22 +152,82 @@ lint: precommit: lint test debug: - cargo build --release --features backend-singlepass,debug,trace + cargo build --release --features backend-cranelift,backend-singlepass,debug,trace install: cargo install --path . -check: - cargo check --release --features backend-singlepass,backend-llvm,loader-kernel +# Checks +check-bench-singlepass: + cargo check --benches --all --no-default-features --features "backend-singlepass" \ + --exclude wasmer-clif-backend --exclude wasmer-llvm-backend --exclude wasmer-kernel-loader +check-bench-clif: + cargo check --benches --all --no-default-features --features "backend-cranelift" \ + --exclude wasmer-singlepass-backend --exclude wasmer-llvm-backend --exclude wasmer-kernel-loader \ + --exclude wasmer-middleware-common-tests +check-bench-llvm: + cargo check --benches --all --no-default-features --features "backend-llvm" \ + --exclude wasmer-singlepass-backend --exclude wasmer-clif-backend --exclude wasmer-kernel-loader +check-bench: check-bench-singlepass check-bench-llvm + +# TODO: We wanted `--workspace --exclude wasmer-runtime`, but can't due +# to https://github.com/rust-lang/cargo/issues/6745 . +NOT_RUNTIME_CRATES = -p wasmer-clif-backend -p wasmer-singlepass-backend -p wasmer-middleware-common -p wasmer-runtime-core -p wasmer-emscripten -p wasmer-llvm-backend -p wasmer-wasi -p wasmer-kernel-loader -p wasmer-dev-utils -p wasmer-wasi-tests -p wasmer-middleware-common-tests -p wasmer-emscripten-tests +RUNTIME_CHECK = cargo check --manifest-path lib/runtime/Cargo.toml --no-default-features +check: check-bench + cargo check $(NOT_RUNTIME_CRATES) + cargo check --release $(NOT_RUNTIME_CRATES) + cargo check --all-features $(NOT_RUNTIME_CRATES) + cargo check --release --all-features $(NOT_RUNTIME_CRATES) + # wasmer-runtime doesn't work with all backends enabled at once. + # + # We test using manifest-path directly so as to disable the default. + # `--no-default-features` only disables the default features in the + # current package, not the package specified by `-p`. This is + # intentional. + # + # Test default features, test 'debug' feature only in non-release + # builds, test as many combined features as possible with each backend + # as default, and test a minimal set of features with only one backend + # at a time. + cargo check --manifest-path lib/runtime/Cargo.toml + cargo check --release --manifest-path lib/runtime/Cargo.toml + + $(RUNTIME_CHECK) \ + --features=cranelift,cache,debug,llvm,singlepass,default-backend-singlepass + $(RUNTIME_CHECK) --release \ + --features=cranelift,cache,llvm,singlepass,default-backend-singlepass + $(RUNTIME_CHECK) \ + --features=cranelift,cache,debug,llvm,singlepass,default-backend-cranelift + $(RUNTIME_CHECK) --release \ + --features=cranelift,cache,llvm,singlepass,default-backend-cranelift + $(RUNTIME_CHECK) \ + --features=cranelift,cache,debug,llvm,singlepass,default-backend-llvm + $(RUNTIME_CHECK) --release \ + --features=cranelift,cache,llvm,singlepass,default-backend-llvm + $(RUNTIME_CHECK) \ + --features=singlepass,default-backend-singlepass,debug + $(RUNTIME_CHECK) --release \ + --features=singlepass,default-backend-singlepass + $(RUNTIME_CHECK) \ + --features=cranelift,default-backend-cranelift,debug + $(RUNTIME_CHECK) --release \ + --features=cranelift,default-backend-cranelift + $(RUNTIME_CHECK) \ + --features=llvm,default-backend-llvm,debug + $(RUNTIME_CHECK) --release \ + --features=llvm,default-backend-llvm + +# Release release: - cargo build --release --features backend-singlepass,backend-llvm,loader-kernel + cargo build --release --features backend-singlepass,backend-cranelift,backend-llvm,loader-kernel # Only one backend (cranelift) release-clif: - # If you are in OS-X, you will need mingw-w64 for cross compiling to windows + # If you are on macOS, you will need mingw-w64 for cross compiling to Windows # brew install mingw-w64 - cargo build --release + cargo build --release --features backend-cranelift release-singlepass: cargo build --release --features backend-singlepass @@ -146,20 +236,15 @@ release-llvm: cargo build --release --features backend-llvm bench-singlepass: - cargo bench --all --no-default-features --features "backend-singlepass" + cargo bench --all --no-default-features --features "backend-singlepass" \ + --exclude wasmer-clif-backend --exclude wasmer-llvm-backend --exclude wasmer-kernel-loader bench-clif: - cargo bench --all --no-default-features --features "backend-cranelift" + cargo bench --all --no-default-features --features "backend-cranelift" \ + --exclude wasmer-singlepass-backend --exclude wasmer-llvm-backend --exclude wasmer-kernel-loader \ + --exclude wasmer-middleware-common-tests bench-llvm: - cargo bench --all --no-default-features --features "backend-llvm" - -# compile but don't run the benchmarks -compile-bench-singlepass: - cargo bench --all --no-run --no-default-features --features "backend-singlepass" -compile-bench-clif: - cargo bench --all --no-run --no-default-features --features "backend-cranelift" -compile-bench-llvm: - cargo bench --all --no-run --no-default-features --features "backend-llvm" - + cargo bench --all --no-default-features --features "backend-llvm" \ + --exclude wasmer-singlepass-backend --exclude wasmer-clif-backend --exclude wasmer-kernel-loader # Build utils build-install: @@ -178,4 +263,10 @@ publish-release: # cargo install cargo-deps # must install graphviz for `dot` dep-graph: - cargo deps --optional-deps --filter wasmer-wasi wasmer-wasi-tests wasmer-kernel-loader wasmer-dev-utils wasmer-llvm-backend wasmer-emscripten wasmer-emscripten-tests wasmer-runtime-core wasmer-runtime wasmer-middleware-common wasmer-singlepass-backend wasmer-clif-backend wasmer --manifest-path Cargo.toml | dot -Tpng > wasmer_depgraph.png + cargo deps --optional-deps --filter wasmer-wasi wasmer-wasi-tests wasmer-kernel-loader wasmer-dev-utils wasmer-llvm-backend wasmer-emscripten wasmer-emscripten-tests wasmer-runtime-core wasmer-runtime wasmer-middleware-common wasmer-middleware-common-tests wasmer-singlepass-backend wasmer-clif-backend wasmer --manifest-path Cargo.toml | dot -Tpng > wasmer_depgraph.png + +docs: + cargo doc --features=backend-singlepass,backend-cranelift,backend-llvm,docs,wasi,managed + +wapm: + cargo build --release --manifest-path wapm-cli/Cargo.toml --features "telemetry update-notifications" diff --git a/README.md b/README.md index 5afafdf0e..0391bbd9b 100644 --- a/README.md +++ b/README.md @@ -5,42 +5,53 @@

- - Build Status + + Build Status + + + Documentation - License + License Join the Wasmer Community - Follow @wasmerio on Twitter + Follow @wasmerio on Twitter

## Introduction -[Wasmer](https://wasmer.io/) is a standalone JIT WebAssembly runtime, aiming to be fully compatible with [WASI](https://github.com/WebAssembly/WASI) and [Emscripten](https://emscripten.org/). +[Wasmer](https://wasmer.io/) is a standalone WebAssembly runtime for running WebAssembly [outside of the browser](https://webassembly.org/docs/non-web/), supporting [WASI](https://github.com/WebAssembly/WASI) and [Emscripten](https://emscripten.org/). Wasmer can be used standalone (via the CLI) and embedded in different languages, running in x86 and [ARM devices](https://medium.com/wasmer/running-webassembly-on-arm-7d365ed0e50c). -Install Wasmer with: +Install the Wasmer and [WAPM](https://wapm.io) cli with: ```sh curl https://get.wasmer.io -sSfL | sh ``` -> Note: *Wasmer is also available on Windows. Download the [`WasmerInstaller.exe` from the Github Releases](https://github.com/wasmerio/wasmer/releases) page.* +> Note: *Wasmer is also [available on Windows](https://github.com/wasmerio/wasmer/releases)* -Wasmer runtime can also be embedded in different languages, so you can use WebAssembly anywhere ✨: +### Languages -* [**🦀 Rust**](https://github.com/wasmerio/wasmer-rust-example) -* [**🔗 C/C++**](https://github.com/wasmerio/wasmer-c-api) -* [**#️⃣ C#**](https://github.com/migueldeicaza/WasmerSharp) -* [**🐘 PHP**](https://github.com/wasmerio/php-ext-wasm) -* [**🐍 Python**](https://github.com/wasmerio/python-ext-wasm) -* [**💎 Ruby**](https://github.com/wasmerio/ruby-ext-wasm) -* [**🐹 Go**](https://github.com/wasmerio/go-ext-wasm) -* [**R**](https://github.com/dirkschumacher/wasmr) +Wasmer runtime can be used as a library embedded in different languages, so you can use WebAssembly anywhere: + +|   | Language | Author(s) | Maintenance | Release | Stars | +|-|-|-|-|-|-| +| ![Rust logo](./docs/assets/languages/rust.svg) | [**Rust**](https://github.com/wasmerio/wasmer-rust-example) | Wasmer | actively developed | ![last release](https://img.shields.io/crates/v/wasmer-runtime?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/wasmerio/wasmer?style=flat-square) | +| ![C logo](./docs/assets/languages/c.svg) | [**C/C++**](https://github.com/wasmerio/wasmer-c-api) | Wasmer | actively developed | ![last release](https://img.shields.io/github/v/release/wasmerio/wasmer?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/wasmerio/wasmer?style=flat-square) | +| ![Python logo](./docs/assets/languages/python.svg) | [**Python**](https://github.com/wasmerio/python-ext-wasm) | Wasmer | actively developed | ![last release](https://img.shields.io/pypi/v/wasmer?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/wasmerio/python-ext-wasm?style=flat-square) | +| ![Go logo](./docs/assets/languages/go.svg) | [**Go**](https://github.com/wasmerio/go-ext-wasm) | Wasmer | actively developed | ![last release](https://img.shields.io/github/v/release/wasmerio/go-ext-wasm?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/wasmerio/go-ext-wasm?style=flat-square) | +| ![PHP logo](./docs/assets/languages/php.svg) | [**PHP**](https://github.com/wasmerio/php-ext-wasm) | Wasmer | actively developed | ![last release](https://img.shields.io/github/v/release/wasmerio/php-ext-wasm?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/wasmerio/php-ext-wasm?style=flat-square) | +| ![Ruby logo](./docs/assets/languages/ruby.svg) | [**Ruby**](https://github.com/wasmerio/ruby-ext-wasm) | Wasmer | actively developed | ![last release](https://img.shields.io/gem/v/wasmer?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/wasmerio/ruby-ext-wasm?style=flat-square) | +| ![Postgres logo](./docs/assets/languages/postgres.svg) | [**Postgres**](https://github.com/wasmerio/postgres-ext-wasm) | Wasmer | actively developed | ![last release](https://img.shields.io/github/v/release/wasmerio/postgres-ext-wasm?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/wasmerio/postgres-ext-wasm?style=flat-square) | +| ![JS Logo](./docs/assets/languages/js.svg) | [**JavaScript**](https://github.com/wasmerio/wasmer-js) | Wasmer | actively developed | ![last release](https://img.shields.io/npm/v/@wasmer/wasi?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/wasmerio/wasmer-js?style=flat-square) | +| ![C# logo](./docs/assets/languages/csharp.svg) | [**C#/.Net**](https://github.com/migueldeicaza/WasmerSharp) | [Miguel de Icaza](https://github.com/migueldeicaza) | actively developed | ![last release](https://img.shields.io/nuget/v/WasmerSharp?style=flat-square) | ![number of Github stars](https://img.shields.io/github/stars/migueldeicaza/WasmerSharp?style=flat-square) | +| ![R logo](./docs/assets/languages/r.svg) | [**R**](https://github.com/dirkschumacher/wasmr) | [Dirk Schumacher](https://github.com/dirkschumacher) | actively developed | | ![number of Github stars](https://img.shields.io/github/stars/dirkschumacher/wasmr?style=flat-square) | +| ![Swift logo](./docs/assets/languages/swift.svg) | [**Swift**](https://github.com/markmals/swift-ext-wasm) | [Mark Malström](https://github.com/markmals/) | passively maintained | | ![number of Github stars](https://img.shields.io/github/stars/markmals/swift-ext-wasm?style=flat-square) | +| ❓ | [your language is missing?](https://github.com/wasmerio/wasmer/issues/new?assignees=&labels=%F0%9F%8E%89+enhancement&template=---feature-request.md&title=) | | | | ### Usage @@ -51,28 +62,15 @@ Once installed, you will be able to run any WebAssembly files (_including Lua, P ```sh # Run Lua -wasmer run examples/lua.wasm +wasmer examples/lua.wasm ``` *You can find more `wasm/wat` examples in the [examples](./examples) directory.* -#### With WAPM +### Docs -Installing Wasmer through `wasmer.io` includes -[`wapm`](https://github.com/wasmerio/wapm-cli), the [WebAssembly Package Manager](https://wapm.io/). +Wasmer documentation lives on [docs.wasmer.io](https://docs.wasmer.io). -Wapm allows you to easily download, run, and distribute WebAssembly binaries. - -```sh -# Install cowsay globally -wapm install -g cowsay - -# Run cowsay -wapm run cowsay "Hello, world!" -``` - -For more information about wapm, check out the [website](https://www.wapm.io) -and this [example program](https://github.com/wapm-packages/rust-wasi-example). ## Code Structure @@ -80,7 +78,7 @@ Wasmer is structured into different directories: - [`src`](./src): code related to the Wasmer executable itself - [`lib`](./lib): modularized libraries that Wasmer uses under the hood -- [`examples`](./examples): some useful examples to getting started with Wasmer +- [`examples`](./examples): some useful examples for getting started with Wasmer ## Dependencies @@ -111,7 +109,7 @@ If you have [Homebrew](https://brew.sh/) installed: brew install cmake ``` -Or, in case you have [MacPorts](https://www.macports.org/install.php): +Or, if you have [MacPorts](https://www.macports.org/install.php): ```sh sudo port install cmake @@ -129,6 +127,7 @@ sudo port install cmake ```sh sudo apt install cmake pkg-config libssl-dev ``` +

@@ -141,6 +140,7 @@ sudo apt install cmake pkg-config libssl-dev ```sh pkg install cmake ``` +

@@ -150,40 +150,40 @@ pkg install cmake #### Windows (MSVC) -Windows support is _experimental_. WASI is fully supported, but Emscripten support is on the works (this means +Windows support is _experimental_. WASI is fully supported, but Emscripten support is in the works (this means nginx and Lua do not work on Windows - you can track the progress on [this issue](https://github.com/wasmerio/wasmer/issues/176)). 1. Install [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15) 2. Install [Rust for Windows](https://win.rustup.rs) -3. Install [Python for Windows](https://www.python.org/downloads/release/python-2714/). The Windows x86-64 MSI installer is fine. - Make sure to enable "Add python.exe to Path" during installation. - -4. Install [Git for Windows](https://git-scm.com/download/win). Allow it to add `git.exe` to your PATH (default +3. Install [Git for Windows](https://git-scm.com/download/win). Allow it to add `git.exe` to your PATH (default settings for the installer are fine). -5. Install [CMake](https://cmake.org/download/). Ensure CMake is in your PATH. +4. Install [CMake](https://cmake.org/download/). Ensure CMake is in your PATH. -6. Install [LLVM 8.0](https://prereleases.llvm.org/win-snapshots/LLVM-8.0.0-r351033-win64.exe) -

- +5. Install [LLVM 8.0](https://prereleases.llvm.org/win-snapshots/LLVM-8.0.0-r351033-win64.exe) +

+ ## Building -[![Rustc Version 1.36+](https://img.shields.io/badge/rustc-1.36+-red.svg)](https://blog.rust-lang.org/2019/07/04/Rust-1.36.0.html) + +[![Rustc Version 1.38+](https://img.shields.io/badge/rustc-1.37+-red.svg?style=flat-square)](https://blog.rust-lang.org/2019/09/26/Rust-1.38.0.html) Wasmer is built with [Cargo](https://crates.io/), the Rust package manager. The Singlepass backend requires nightly, so if you want to use it, Set Rust Nightly: + ``` rustup default nightly ``` -Otherwise an up to date (see badge above) verison of stable Rust will work. +Otherwise an up to date (see badge above) version of stable Rust will work. And install Wasmer + ```sh # checkout code git clone https://github.com/wasmerio/wasmer.git @@ -225,11 +225,10 @@ Each integration can be tested separately: * Spec tests: `make spectests` * Emscripten: `make emtests` -* WASI: `make wasi` +* WASI: `make wasitests` * Middleware: `make middleware` * C API: `make capi` - ## Benchmarking Benchmarks can be run with: @@ -250,8 +249,8 @@ Below are some of the goals of this project (in order of priority): - [x] It should be 100% compatible with the [WebAssembly spec tests](https://github.com/wasmerio/wasmer/tree/master/lib/spectests/spectests) - [x] It should be fast _(partially achieved)_ - [x] Support WASI - released in [0.3.0](https://github.com/wasmerio/wasmer/releases/tag/0.3.0) -- [x] Support Emscripten calls _(in the works)_ -- [ ] Support Go js ABI calls +- [x] Support Emscripten calls +- [ ] Support Go JS ABI calls _(in the works)_ ## Architecture diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 000000000..2409dadda --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,271 @@ +name: $(Build.SourceBranch)-$(date:yyyyMMdd)$(rev:.r) + +# The Different jobs (lint, test, build to run) +jobs: + - job: changelog + steps: + - bash: | + git --no-pager diff --name-only HEAD $(git merge-base HEAD master) --exit-code CHANGELOG.md + displayName: Changelog Updated + + - job: lint + pool: + vmImage: "macos-10.14" + steps: + - checkout: self + submodules: true + - template: .azure/install-rust.yml + - script: | + rustup component add rustfmt + rustup component add clippy || cargo install --git https://github.com/rust-lang/rust-clippy/ --force clippy + displayName: Lint dependencies + - script: cargo fmt --all -- --check + displayName: Lint + variables: + rust_toolchain: '1.38.0' + + - job: Test + strategy: + matrix: + linux: + imageName: "ubuntu-16.04" + rust_toolchain: nightly-2019-08-15 + mac: + imageName: "macos-10.14" + rust_toolchain: nightly-2019-08-15 + # By default schannel checks revocation of certificates unlike some other SSL + # backends, but we've historically had problems on CI where a revocation + # server goes down presumably. See #43333 for more info + CARGO_HTTP_CHECK_REVOKE: false + windows: + imageName: "vs2017-win2016" + rust_toolchain: '1.38.0' + pool: + vmImage: $(imageName) + condition: in(variables['Build.SourceBranch'], 'refs/heads/master', 'refs/heads/staging', 'refs/heads/trying') + steps: + - checkout: self + submodules: true + - template: .azure/install-rust.yml + - template: .azure/install-llvm.yml + - template: .azure/install-sccache.yml + - template: .azure/install-cmake.yml + - bash: | + hostname + uname -a + displayName: System info (*nix) + condition: and(succeeded(), not(eq(variables['Agent.OS'], 'Windows_NT'))) + - bash: | + cat /proc/cpuinfo + cat /proc/meminfo + displayName: System info - Extended (Linux) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) + - bash: | + sysctl -a | grep machdep.cpu + displayName: System info - Extended (Mac) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) + - bash: make test + displayName: Tests (*nix) + condition: and(succeeded(), not(eq(variables['Agent.OS'], 'Windows_NT'))) + - bash: make spectests-cranelift + displayName: Tests (Windows) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + + - job: Check + pool: + vmImage: "ubuntu-16.04" + variables: + rust_toolchain: nightly-2019-08-15 + condition: in(variables['Build.SourceBranch'], 'refs/heads/master', 'refs/heads/staging', 'refs/heads/trying') + steps: + - checkout: self + submodules: true + - template: .azure/install-rust.yml + - template: .azure/install-llvm.yml + - template: .azure/install-sccache.yml + - template: .azure/install-cmake.yml + - bash: make check + displayName: Check with Flags + condition: and(succeeded(), not(eq(variables['Agent.OS'], 'Windows_NT'))) + + - job: Build_CLI + strategy: + matrix: + linux: + imageName: "ubuntu-16.04" + rust_toolchain: nightly-2019-08-15 + mac: + imageName: "macos-10.14" + rust_toolchain: nightly-2019-08-15 + MACOSX_DEPLOYMENT_TARGET: 10.10 + windows: + imageName: "vs2017-win2016" + rust_toolchain: '1.38.0' + # RUSTFLAGS: -Ctarget-feature=+crt-static + pool: + vmImage: $(imageName) + condition: | + or( + in(variables['Build.SourceBranch'], 'refs/heads/master', 'refs/heads/staging', 'refs/heads/trying'), + startsWith(variables['Build.SourceBranch'], 'refs/tags') + ) + steps: + - checkout: self + submodules: true + - template: .azure/install-rust.yml + - template: .azure/install-llvm.yml + - template: .azure/install-sccache.yml + - template: .azure/install-cmake.yml + - template: .azure/install-innosetup.yml + - bash: | + mkdir -p artifacts + displayName: Create Artifacts Dir + - bash: make release + displayName: Build (*nix) + condition: and(succeeded(), not(eq(variables['Agent.OS'], 'Windows_NT'))) + - bash: make release-llvm + displayName: Build (Windows) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + - bash: | + make wapm + displayName: Build WAPM + condition: | + startsWith(variables['Build.SourceBranch'], 'refs/tags') + - bash: | + make build-install + cp ./wasmer.tar.gz ./artifacts/$(./scripts/binary-name.sh) + displayName: Build Distribution (*nix) + condition: | + and( + succeeded(), + startsWith(variables['Build.SourceBranch'], 'refs/tags'), + not(eq(variables['Agent.OS'], 'Windows_NT')) + ) + - bash: | + cd ./src/installer + iscc wasmer.iss + cp WasmerInstaller.exe ../../artifacts/wasmer-windows.exe + displayName: Build Distribution (Windows) + condition: | + and( + succeeded(), + startsWith(variables['Build.SourceBranch'], 'refs/tags'), + eq(variables['Agent.OS'], 'Windows_NT') + ) + - publish: $(System.DefaultWorkingDirectory)/artifacts + artifact: cli-$(Agent.OS) + + - job: Build_Library + strategy: + matrix: + linux: + imageName: "ubuntu-16.04" + rust_toolchain: nightly-2019-08-15 + mac: + imageName: "macos-10.14" + rust_toolchain: nightly-2019-08-15 + MACOSX_DEPLOYMENT_TARGET: 10.10 + windows: + imageName: "vs2017-win2016" + rust_toolchain: '1.38.0' + # RUSTFLAGS: -Ctarget-feature=+crt-static + pool: + vmImage: $(imageName) + condition: | + or( + in(variables['Build.SourceBranch'], 'refs/heads/master', 'refs/heads/staging', 'refs/heads/trying'), + startsWith(variables['Build.SourceBranch'], 'refs/tags') + ) + steps: + - checkout: self + submodules: true + - template: .azure/install-rust.yml + # - template: .azure/install-llvm.yml + - template: .azure/install-sccache.yml + - template: .azure/install-cmake.yml + - bash: | + mkdir -p artifacts + displayName: Create Artifacts Dir + - bash: | + make capi + make test-capi + cp target/release/libwasmer_runtime_c_api.so ./artifacts + displayName: Build c-api (Linux) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux')) + - bash: | + make capi + make test-capi + install_name_tool -id "@rpath/libwasmer_runtime_c_api.dylib" target/release/libwasmer_runtime_c_api.dylib + cp target/release/libwasmer_runtime_c_api.dylib ./artifacts + displayName: Build c-api (Darwin) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Darwin')) + - bash: | + make capi + # Tests are failing on Windows, comment for now + # make test-capi + cp target/release/wasmer_runtime_c_api.dll ./artifacts + displayName: Build c-api (Windows) + condition: and(succeeded(), eq(variables['Agent.OS'], 'Windows_NT')) + - publish: $(System.DefaultWorkingDirectory)/artifacts + artifact: library-$(Agent.OS) + + - job: Publish + dependsOn: + - Build_CLI + - Build_Library + condition: | + startsWith(variables['Build.SourceBranch'], 'refs/tags') + steps: + # - download: current + - task: DownloadPipelineArtifact@1 + inputs: + targetPath: $(Build.ArtifactStagingDirectory) + - bash: | + ls $ARTIFACT_STAGING_DIRECTORY + displayName: List Artifacts + env: + ARTIFACT_STAGING_DIRECTORY: $(Build.ArtifactStagingDirectory) + - script: VERSION_TAG=`git describe --tags` && echo "##vso[task.setvariable variable=VERSION_TAG]$VERSION_TAG" + displayName: Set the tag name as an environment variable + - task: GithubRelease@0 + displayName: "Create GitHub Release" + condition: and(succeeded(), startsWith(variables['Build.SourceBranch'], 'refs/tags')) + inputs: + gitHubConnection: 'wasmer' + repositoryName: 'wasmerio/wasmer' + action: 'create' + target: '$(Build.SourceVersion)' + title: '$(VERSION_TAG)' + addChangeLog: false + tagSource: 'auto' + # TODO: automate it all by getting the release notes from somewhere else and using the `releaseNotesFile` key + isDraft: false + isPreRelease: false + assets: '$(Build.ArtifactStagingDirectory)/**' + + - job: Docs + pool: + vmImage: "ubuntu-16.04" + variables: + rust_toolchain: nightly-2019-08-15 + steps: + - checkout: self + submodules: true + - template: .azure/install-rust.yml + - template: .azure/install-llvm.yml + - template: .azure/install-sccache.yml + - template: .azure/install-cmake.yml + - bash: | + make docs + displayName: Build documentation + +# We only run the pipelines on PRs to Master +pr: + - master + +# Otherwise, we test in any of this branches (master or bors related) +trigger: + - master + - staging + - trying + - refs/tags/* diff --git a/bors.toml b/bors.toml index 5fadbdfa0..1003d9678 100644 --- a/bors.toml +++ b/bors.toml @@ -1,10 +1,5 @@ status = [ - "ci/circleci: lint", - "ci/circleci: test", - "ci/circleci: test-macos", - "ci/circleci: test-stable", - "ci/circleci: test-rust-example", - "continuous-integration/appveyor/branch" + "wasmerio.wasmer" ] required_approvals = 1 timeout_sec = 7200 diff --git a/docs/assets/languages/c.svg b/docs/assets/languages/c.svg new file mode 100755 index 000000000..e2492e6b5 --- /dev/null +++ b/docs/assets/languages/c.svg @@ -0,0 +1 @@ +c-logo-colored \ No newline at end of file diff --git a/docs/assets/languages/cpp.svg b/docs/assets/languages/cpp.svg new file mode 100755 index 000000000..84035b8b6 --- /dev/null +++ b/docs/assets/languages/cpp.svg @@ -0,0 +1 @@ +c++-logo-colored \ No newline at end of file diff --git a/docs/assets/languages/csharp.svg b/docs/assets/languages/csharp.svg new file mode 100755 index 000000000..28c059f1e --- /dev/null +++ b/docs/assets/languages/csharp.svg @@ -0,0 +1 @@ +csharp-logo-colored \ No newline at end of file diff --git a/docs/assets/languages/go.svg b/docs/assets/languages/go.svg new file mode 100755 index 000000000..e9eda28d1 --- /dev/null +++ b/docs/assets/languages/go.svg @@ -0,0 +1 @@ +go-logo-colored \ No newline at end of file diff --git a/docs/assets/languages/js.svg b/docs/assets/languages/js.svg new file mode 100755 index 000000000..6a186f383 --- /dev/null +++ b/docs/assets/languages/js.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/docs/assets/languages/php.svg b/docs/assets/languages/php.svg new file mode 100755 index 000000000..8d883e309 --- /dev/null +++ b/docs/assets/languages/php.svg @@ -0,0 +1 @@ +php-logo \ No newline at end of file diff --git a/docs/assets/languages/postgres.svg b/docs/assets/languages/postgres.svg new file mode 100644 index 000000000..65149a5fd --- /dev/null +++ b/docs/assets/languages/postgres.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/docs/assets/languages/python.svg b/docs/assets/languages/python.svg new file mode 100755 index 000000000..9b7eee497 --- /dev/null +++ b/docs/assets/languages/python.svg @@ -0,0 +1 @@ +python-logo-colored \ No newline at end of file diff --git a/docs/assets/languages/r.svg b/docs/assets/languages/r.svg new file mode 100644 index 000000000..b464dd337 --- /dev/null +++ b/docs/assets/languages/r.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/docs/assets/languages/ruby.svg b/docs/assets/languages/ruby.svg new file mode 100755 index 000000000..4e5ef4b60 --- /dev/null +++ b/docs/assets/languages/ruby.svg @@ -0,0 +1 @@ +ruby-logo-colored \ No newline at end of file diff --git a/docs/assets/languages/rust.svg b/docs/assets/languages/rust.svg new file mode 100755 index 000000000..4d04ee684 --- /dev/null +++ b/docs/assets/languages/rust.svg @@ -0,0 +1 @@ +rust-logo \ No newline at end of file diff --git a/docs/assets/languages/swift.svg b/docs/assets/languages/swift.svg new file mode 100644 index 000000000..c04de9bce --- /dev/null +++ b/docs/assets/languages/swift.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/docs/debugging.md b/docs/debugging.md new file mode 100644 index 000000000..8d49d5217 --- /dev/null +++ b/docs/debugging.md @@ -0,0 +1,50 @@ +# Debugging Wasmer + +## When is this document useful? + +If you're developing wasmer or running into issues, this document will explain to useful techniques and common errors. + +## Tracing syscalls + +To trace syscalls, compile with the `debug` feature (`cargo build --features "debug"`). For even more verbose messages, use the `trace` flag. + +## Tracing calls + +TODO: did we disable tracing calls? if not talk about how to enable it +TODO: someone with more context on the backends mention which backends this works for + +If you'd like to see all calls and you're using emscripten, you can use a symbol map to get better error output with the `em-symbol-map` flag. + +## Common things that can go wrong + +### Missing imports + +If, when attempting to run a wasm module, you get an error about missing imports there are a number of things that could be going wrong. + +The most likely is that we haven't implemented those imports for your ABI. If you're targeting emscripten, this is probably the issue. + +However if that's not the case, then there's a chance that you're using an unsupported ABI (let us know!) or that the wasm is invalid for the detected ABI. (TODO: link to wasm contracts or something) + +### Hitting `undefined` + +If this happens it's because wasmer does not have full support for whatever feature you tried to use. Running with tracing on can help clarify the issue if it's not clear from the message. + +To fix this, file an issue letting us know that wasmer is missing a feature that's important to you. If you'd like, you can try to implement it yourself and send us a PR. + +### No output + +If you're seeing no output from running the wasm module then it may be that: +- this is the intended behavior of the wasm module +- or it's very slow to compile (try compiling with a faster backend like cranelift (the default) or singlepass (requires nightly)) + +### Segfault + +If you're seeing a segfault while developing wasmer, chances are that it's a cache issue. We reset the cache on every version bump, but if you're running it from source then the cache may become invalid, which can lead to segfaults. + +To fix this delete the cache with `wasmer cache clean` or run the command with the `disable-cache` flag (`wasmer run some.wasm --disable-cache`) + +If you're seeing a segfault with a released version of wasmer, please file an issue so we can ship an updated version as soon as possible. + +### Something else + +If none of this has helped with your issue, let us know and we'll do our best to help. diff --git a/docs/feature_matrix.md b/docs/feature_matrix.md new file mode 100644 index 000000000..18d303270 --- /dev/null +++ b/docs/feature_matrix.md @@ -0,0 +1,42 @@ +# Feature Table + +## Compiler Backend + +|   | Singlepass | Cranelift | LLVM | +| - | :-: | :-: | :-: | +| Caching | ✅ | ✅ | ✅ | +| Emscripten | ✅ | ✅ | ✅ | +| Metering | ✅ | ⬜ | ✅ | +| Multi-value return | ⬜ | ⬜ | ⬜ | +| OSR | 🔄 | ⬜ | 🔄 | +| SIMD | ⬜ | ⬜ | ✅ | +| WASI | ✅ | ✅ | ✅ | +| WASMER_BACKTRACE | ✅ | ⬜ | ⬜ | + +## Operating System +|   | GNU Linux | Mac OSX | Windows NT | +| - | :-: | :-: | :-: | +| Cranelift Backend | ✅ | ✅ | ✅ | +| LLVM Backend | ✅ | ✅ | ✅ | +| Singlepass Backend | ✅ | ✅ | [#347](https://github.com/wasmerio/wasmer/issues/347) | +| WASI | ✅ | ✅ | ✅* | + +* `poll_fd` is not fully implemented for Windows yet + +## Language integration + +TODO: define a set of features that are relevant and mark them here + +Current ideas: + +- Callbacks +- Metering +- Caching + +> TODO: expand this table, it's focused on new features that we haven't implemented yet and doesn't list all language integrations + +|   | Rust | C / C++ | Go | Python | Ruby | +| - | :-: | :-: | :-: | :-: | :-: | +| Terminate in host call | ✅ | ⬜ | ⬜ | ⬜ | ⬜ | +| WASI | ✅ | ✅ | 🔄 | ⬜ | ⬜ | +| WASI FS API | ✅ | ⬜ | ⬜ | ⬜ | ⬜ | diff --git a/examples/callback-guest/README.md b/examples/callback-guest/README.md new file mode 100644 index 000000000..6439ef00d --- /dev/null +++ b/examples/callback-guest/README.md @@ -0,0 +1,5 @@ +# Call back guest + +This is part of the `callback` example. This Wasm module passes host imports and its own functions to the Wasm host to execute. + +See `examples/callback.rs` for the host diff --git a/examples/callback-guest/callback-guest.rs b/examples/callback-guest/callback-guest.rs new file mode 100644 index 000000000..129ad05c8 --- /dev/null +++ b/examples/callback-guest/callback-guest.rs @@ -0,0 +1,24 @@ +extern "C" { + fn call_guest_fn(f: u32) -> u32; + fn call_guest_fn2(f: u32) -> u32; + fn host_callback() -> u32; +} + +#[no_mangle] +fn test_callback() -> u32 { + 42 +} + +#[no_mangle] +fn test_callback2() -> u32 { + 45 +} + +fn main() { + unsafe { call_guest_fn(test_callback as usize as u32) }; + unsafe { call_guest_fn(host_callback as usize as u32) }; + unsafe { call_guest_fn(test_callback2 as usize as u32) }; + unsafe { call_guest_fn2(test_callback2 as usize as u32) }; + unsafe { call_guest_fn2(test_callback as usize as u32) }; + unsafe { call_guest_fn2(host_callback as usize as u32) }; +} diff --git a/examples/callback-guest/callback-guest.wasm b/examples/callback-guest/callback-guest.wasm new file mode 100755 index 000000000..055582975 Binary files /dev/null and b/examples/callback-guest/callback-guest.wasm differ diff --git a/examples/callback.rs b/examples/callback.rs new file mode 100644 index 000000000..e5969fec9 --- /dev/null +++ b/examples/callback.rs @@ -0,0 +1,46 @@ +/// This example demonstrates the use of callbacks: calling functions (Host and Wasm) +/// passed to us from the Wasm via hostcall +use wasmer_runtime::{compile_with, compiler_for_backend, func, imports, Backend, Ctx}; +use wasmer_runtime_core::{structures::TypedIndex, types::TableIndex}; + +static WASM: &'static str = "examples/callback-guest/callback-guest.wasm"; + +/// This function matches our arbitrarily decided callback signature +/// in this example we'll only call functions that take no arguments and return one value +fn host_callback(_ctx: &mut Ctx) -> u32 { + 55 +} + +fn call_guest_fn(ctx: &mut Ctx, guest_fn: u32) -> u32 { + // We get a TableIndex from our raw value passed in + let guest_fn_typed = TableIndex::new(guest_fn as usize); + // and use it to call the corresponding function + let result = ctx.call_with_table_index(guest_fn_typed, &[]).unwrap(); + + println!("Guest fn {} returned {:?}", guest_fn, result); + + 0 +} + +fn main() { + let wasm_bytes = + std::fs::read(WASM).expect(&format!("Could not read in WASM plugin at {}", WASM)); + + let imports = imports! { + "env" => { + "call_guest_fn" => func!(call_guest_fn), + "call_guest_fn2" => func!(call_guest_fn), + "host_callback" => func!(host_callback), + }, + }; + + let compiler = compiler_for_backend(Backend::default()).unwrap(); + let module = compile_with(&wasm_bytes[..], compiler.as_ref()).unwrap(); + let instance = module + .instantiate(&imports) + .expect("failed to instantiate wasm module"); + + let entry_point = instance.func::<(u32, u32), u32>("main").unwrap(); + + entry_point.call(0, 0).expect("START"); +} diff --git a/examples/fib.wat b/examples/fib.wat new file mode 100644 index 000000000..a797fdae4 --- /dev/null +++ b/examples/fib.wat @@ -0,0 +1,20 @@ +(module + (func $main (result i32) + (call $fib (i32.const 40)) + ) + + (func $fib (param $n i32) (result i32) + (if (i32.eq (get_local $n) (i32.const 0)) + (then (return (i32.const 1))) + ) + (if (i32.eq (get_local $n) (i32.const 1)) + (then (return (i32.const 1))) + ) + (i32.add + (call $fib (i32.sub (get_local $n) (i32.const 1))) + (call $fib (i32.sub (get_local $n) (i32.const 2))) + ) + ) + + (export "main" (func $main)) +) diff --git a/examples/hello_world/Cargo.toml b/examples/hello_world/Cargo.toml new file mode 100644 index 000000000..a71d719ed --- /dev/null +++ b/examples/hello_world/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "hello_world" +version = "0.1.0" +authors = ["losfair "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/examples/hello_world/src/main.rs b/examples/hello_world/src/main.rs new file mode 100644 index 000000000..ca24b9cea --- /dev/null +++ b/examples/hello_world/src/main.rs @@ -0,0 +1,7 @@ +fn main() { + for i in 0..8 { + let s = format!("Hello, {}", i); + println!("{}", s); + } + panic!("OK"); +} diff --git a/examples/iterative_hash/src/main.rs b/examples/iterative_hash/src/main.rs index f25672047..66ed10405 100644 --- a/examples/iterative_hash/src/main.rs +++ b/examples/iterative_hash/src/main.rs @@ -1,7 +1,13 @@ use blake2::{Blake2b, Digest}; +use std::time::{Duration, SystemTime}; fn main() { let mut data: Vec = b"test".to_vec(); + let now = SystemTime::now(); + + let mut last_millis: u128 = 0; + let mut round_count: usize = 0; + let mut record_count: usize = 0; for i in 0.. { let mut hasher = Blake2b::new(); @@ -9,8 +15,15 @@ fn main() { let out = hasher.result(); data = out.to_vec(); - if i % 1000000 == 0 { - println!("Round {}: {:?}", i, data); + if i != 0 && i % 1000 == 0 { + let millis = now.elapsed().unwrap().as_millis(); + let diff = millis - last_millis; + if diff >= 100 { + record_count += 1; + println!("{}", ((i - round_count) as u128) * 1000000 / diff ); + last_millis = millis; + round_count = i; + } } } } diff --git a/examples/many_params.wat b/examples/many_params.wat new file mode 100644 index 000000000..db5e4bb9b --- /dev/null +++ b/examples/many_params.wat @@ -0,0 +1,57 @@ +;; Test case for correctness of reading state with the presence of parameters passed on (machine) stack. +;; Usage: Run with a backend with support for OSR. Interrupt execution randomly. +;; Should see the stack frame for `$foo` to have locals `[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5, [5] = 6, [6] = 7, [7] = 8` with high probability. +;; If the logic for reading stack parameters is broken, it's likely to see `[0] = 1, [1] = 2, [2] = 3, [3] = 4, [4] = 5, [5] = ?, [6] = ?, [7] = ?`. + +(module + (import "wasi_unstable" "proc_exit" (func $__wasi_proc_exit (param i32))) + (func $long_running + (local $count i32) + (loop + (if (i32.eq (get_local $count) (i32.const 1000000)) (then (return))) + (set_local $count (i32.add (i32.const 1) (get_local $count))) + (br 0) + ) + (unreachable) + ) + + (func $foo (param i32) (param i64) (param i32) (param i32) (param i32) (param i64) (param i64) (param i64) (result i32) + (set_local 2 (i32.const 3)) + (call $long_running) + (i32.add + (i32.mul (i32.const 2) (get_local 0)) + (i32.add + (i32.mul (i32.const 3) (i32.wrap/i64 (get_local 1))) + (i32.add + (i32.mul (i32.const 5) (get_local 2)) + (i32.add + (i32.mul (i32.const 7) (get_local 3)) + (i32.add + (i32.mul (i32.const 11) (get_local 4)) + (i32.add + (i32.mul (i32.const 13) (i32.wrap/i64 (get_local 5))) + (i32.add + (i32.mul (i32.const 17) (i32.wrap/i64 (get_local 6))) + (i32.mul (i32.const 19) (i32.wrap/i64 (get_local 7))) + ) + ) + ) + ) + ) + ) + ) + ) + (func $_start (export "_start") + (local $count i32) + (loop + (if (i32.eq (get_local $count) (i32.const 10000)) (then (return))) + (set_local $count (i32.add (i32.const 1) (get_local $count))) + (call $foo (i32.const 1) (i64.const 2) (i32.const 30) (i32.const 4) (i32.const 5) (i64.const 6) (i64.const 7) (i64.const 8)) + (if (i32.ne (i32.const 455)) + (then unreachable) + ) + (br 0) + ) + (unreachable) + ) +) diff --git a/examples/parallel-guest.wasm b/examples/parallel-guest.wasm new file mode 100755 index 000000000..0f8d24dc7 Binary files /dev/null and b/examples/parallel-guest.wasm differ diff --git a/examples/parallel-guest/Cargo.toml b/examples/parallel-guest/Cargo.toml new file mode 100644 index 000000000..14e94e4e9 --- /dev/null +++ b/examples/parallel-guest/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "parallel-guest" +version = "0.1.0" +authors = ["The Wasmer Engineering Team "] +license = "MIT" +edition = "2018" +publish = false + +[dependencies] +md5 = "0.6" +lazy_static = "1" diff --git a/examples/parallel-guest/src/main.rs b/examples/parallel-guest/src/main.rs new file mode 100644 index 000000000..ead45932d --- /dev/null +++ b/examples/parallel-guest/src/main.rs @@ -0,0 +1,82 @@ +#[macro_use] +extern crate lazy_static; + +extern "C" { + fn get_hashed_password(ptr: u32, len: u32) -> u32; + fn print_char(c: u32); +} + +fn print_str(s: &str) { + for c in s.chars() { + unsafe { print_char(c as u32) }; + } + unsafe { print_char(b'\n' as u32) }; +} + +fn load_hashed_password() -> Option { + let mut buffer = String::with_capacity(32); + for _ in 0..32 { + buffer.push(0 as char); + } + let result = + unsafe { get_hashed_password(buffer.as_mut_ptr() as u32, buffer.capacity() as u32) }; + + if result == 0 { + Some(buffer) + } else { + None + } +} + +lazy_static! { + static ref HASHED_PASSWORD: String = load_hashed_password().unwrap(); +} + +static PASSWORD_CHARS: &'static [u8] = b"abcdefghijklmnopqrstuvwxyz0123456789"; + +// for simplicty we define a scheme for mapping numbers onto passwords +fn num_to_password(mut num: u64) -> String { + let mut extra_zero = num == 0; + let mut out = String::new(); + while num > 0 { + out.push(PASSWORD_CHARS[num as usize % PASSWORD_CHARS.len()] as char); + extra_zero = extra_zero || num == PASSWORD_CHARS.len() as u64; + num /= PASSWORD_CHARS.len() as u64; + } + + if extra_zero { + out.push(PASSWORD_CHARS[0] as char); + } + + out +} + +#[repr(C)] +struct RetStr { + ptr: u32, + len: u32, +} + +// returns a (pointer, len) to the password or null +#[no_mangle] +fn check_password(from: u64, to: u64) -> u64 { + for i in from..to { + let password = num_to_password(i); + let digest = md5::compute(&password); + + let hash_as_str = format!("{:x}", digest); + if hash_as_str == *HASHED_PASSWORD { + let ret = RetStr { + ptr: password.as_ptr() as usize as u32, + len: password.len() as u32, + }; + // leak the data so ending the function doesn't corrupt it, if we cared the host could free it after + std::mem::forget(password); + return unsafe { std::mem::transmute(ret) }; + } + } + + return 0; +} + +fn main() {} diff --git a/examples/parallel/Cargo.toml b/examples/parallel/Cargo.toml new file mode 100644 index 000000000..2353cf232 --- /dev/null +++ b/examples/parallel/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "parallel" +version = "0.1.0" +authors = ["The Wasmer Engineering Team "] +edition = "2018" +repository = "https://github.com/wasmerio/wasmer" +publish = false +license = "MIT" + +[dependencies] +rayon = "1.2" +time = "0.1" +wasmer-runtime = { path = "../../lib/runtime" } +wasmer-runtime-core = { path = "../../lib/runtime-core" } diff --git a/examples/parallel/README.md b/examples/parallel/README.md new file mode 100644 index 000000000..fd4d1ad70 --- /dev/null +++ b/examples/parallel/README.md @@ -0,0 +1,5 @@ +# Parallel Wasmer example + +This example shows executing independent code from multiple threads on an "embarassingly parallel" problem + +This is a toy example of cracking md5 hashes. This is not a benchmark. This example is not optimized, it will compare poorly to an implementation that is. diff --git a/examples/parallel/src/main.rs b/examples/parallel/src/main.rs new file mode 100644 index 000000000..b68b0eaeb --- /dev/null +++ b/examples/parallel/src/main.rs @@ -0,0 +1,137 @@ +use rayon::prelude::*; +use wasmer_runtime::{compile_with, compiler_for_backend, func, imports, instantiate, Backend}; +use wasmer_runtime_core::{ + memory::ptr::{Array, WasmPtr}, + vm::Ctx, +}; + +static PLUGIN_LOCATION: &'static str = "../parallel-guest.wasm"; + +fn get_hashed_password(ctx: &mut Ctx, ptr: WasmPtr, len: u32) -> u32 { + // "hard" password - 7 characters + //let password = b"2ab96390c7dbe3439de74d0c9b0b1767"; + // "easy" password - 5 characters + let password = b"ab56b4d92b40713acc5af89985d4b786"; + let memory = ctx.memory(0); + if let Some(writer) = ptr.deref(memory, 0, len) { + for (i, byte) in password.iter().enumerate() { + writer[i].set(*byte) + } + + 0 + } else { + u32::max_value() + } +} + +#[repr(C)] +struct RetStr { + ptr: u32, + len: u32, +} + +fn print_char(_cxt: &mut Ctx, c: u32) { + print!("{}", c as u8 as char); +} + +fn main() { + let wasm_bytes = std::fs::read(PLUGIN_LOCATION).expect(&format!( + "Could not read in WASM plugin at {}", + PLUGIN_LOCATION + )); + + let imports = imports! { + "env" => { + "get_hashed_password" => func!(get_hashed_password), + "print_char" => func!(print_char), + }, + }; + let compiler = compiler_for_backend(Backend::default()).unwrap(); + let module = compile_with(&wasm_bytes[..], compiler.as_ref()).unwrap(); + + println!("Parallel"); + let start_ts = time::SteadyTime::now(); + for outer in 0..1000u64 { + let start = outer * 1000; + let end = start + 1000; + let out = (start..=end) + .into_par_iter() + .filter_map(|i| { + let instance = module + .clone() + .instantiate(&imports) + .expect("failed to instantiate wasm module"); + let check_password = instance.func::<(u64, u64), u64>("check_password").unwrap(); + let j = i * 10000; + let result = check_password.call(j, j + 10000).unwrap(); + print!("."); + use std::io::Write; + std::io::stdout().flush().unwrap(); + if result != 0 { + let res: RetStr = unsafe { std::mem::transmute(result) }; + + let ctx = instance.context(); + let memory = ctx.memory(0); + let wasm_ptr: WasmPtr = WasmPtr::new(res.ptr); + let password_str = wasm_ptr + .get_utf8_string(memory, res.len) + .unwrap() + .to_string(); + Some(password_str) + } else { + None + } + }) + .find_first(|_: &String| true); + if out.is_some() { + let end_ts = time::SteadyTime::now(); + let delta = end_ts - start_ts; + println!( + "Password cracked: \"{}\" in {}.{:03}", + out.unwrap(), + delta.num_seconds(), + (delta.num_milliseconds() % 1000), + ); + break; + } + } + + println!("Serial:"); + let start_ts = time::SteadyTime::now(); + let instance = + instantiate(&wasm_bytes[..], &imports).expect("failed to instantiate wasm module"); + + let check_password = instance.func::<(u64, u64), u64>("check_password").unwrap(); + + let mut out: Option = None; + for i in (0..=u64::max_value()).step_by(10000) { + let result = check_password.call(i, i + 10000).unwrap(); + print!("."); + use std::io::Write; + std::io::stdout().flush().unwrap(); + if result != 0 { + out = Some(unsafe { std::mem::transmute(result) }); + break; + } + } + println!(""); + + if let Some(res) = out { + let ctx = instance.context(); + let memory = ctx.memory(0); + let wasm_ptr: WasmPtr = WasmPtr::new(res.ptr); + + let password_str = wasm_ptr.get_utf8_string(memory, res.len).unwrap(); + + let end_ts = time::SteadyTime::now(); + let delta = end_ts - start_ts; + println!( + "Password cracked: \"{}\" in {}.{:03}", + password_str, + delta.num_seconds(), + (delta.num_milliseconds() % 1000), + ); + } else { + println!("Password not found!"); + } +} diff --git a/examples/plugin.rs b/examples/plugin.rs index c4f1d2de3..1f50108af 100644 --- a/examples/plugin.rs +++ b/examples/plugin.rs @@ -1,8 +1,9 @@ +use serde::{Deserialize, Serialize}; use wasmer_runtime::{func, imports, instantiate}; use wasmer_runtime_core::vm::Ctx; use wasmer_wasi::{ generate_import_object, - state::{self, WasiFile}, + state::{self, WasiFile, WasiFsError}, types, }; @@ -13,7 +14,7 @@ fn it_works(_ctx: &mut Ctx) -> i32 { 5 } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct LoggingWrapper { pub wasm_module_name: String, } @@ -86,6 +87,8 @@ impl std::io::Write for LoggingWrapper { } // the WasiFile methods aren't relevant for a write-only Stdout-like implementation +// we must use typetag and serde so that our trait objects can be safely Serialized and Deserialized +#[typetag::serde] impl WasiFile for LoggingWrapper { fn last_accessed(&self) -> u64 { 0 @@ -99,6 +102,16 @@ impl WasiFile for LoggingWrapper { fn size(&self) -> u64 { 0 } + fn set_len(&mut self, _len: u64) -> Result<(), WasiFsError> { + Ok(()) + } + fn unlink(&mut self) -> Result<(), WasiFsError> { + Ok(()) + } + fn bytes_available(&self) -> Result { + // return an arbitrary amount + Ok(1024) + } } /// Called by the program when it wants to set itself up diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 5c37cb47e..8d6a38140 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,6 +10,10 @@ cargo-fuzz = true [dependencies] wasmer-runtime = { path = "../lib/runtime" } +wasmer-runtime-core = { path = "../lib/runtime-core" } +wasmer = { path = "../" } +wasmer-llvm-backend = { path = "../lib/llvm-backend" } +wasmer-singlepass-backend = { path = "../lib/singlepass-backend" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } # Prevent this from interfering with workspaces @@ -19,3 +23,11 @@ members = ["."] [[bin]] name = "simple_instantiate" path = "fuzz_targets/simple_instantiate.rs" + +[[bin]] +name = "validate_wasm" +path = "fuzz_targets/validate_wasm.rs" + +[[bin]] +name = "compile_wasm" +path = "fuzz_targets/compile_wasm.rs" diff --git a/fuzz/README.md b/fuzz/README.md index cac0a320a..36f7e0c4c 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -10,12 +10,20 @@ $ cargo install cargo-fuzz `cargo-fuzz` is documented in the [Rust Fuzz Book](https://rust-fuzz.github.io/book/cargo-fuzz.html). -## Running a fuzzer +## Running a fuzzer (simple_instantiate, validate_wasm, compile_wasm) Once `cargo-fuzz` is installed, you can run the `simple_instantiate` fuzzer with ```sh cargo fuzz run simple_instantiate ``` +or the `validate_wasm` fuzzer +```sh +cargo fuzz run validate_wasm +``` +or the `compile_wasm` fuzzer +```sh +cargo fuzz run compile_wasm +``` You should see output that looks something like this: @@ -39,7 +47,7 @@ The fuzzer works best when it has examples of small Wasm files to start with. Us ```sh mkdir spec-test-corpus -for i in lib/spectests/spectests/*.wast; do wast2json $i -o spec-test-corpus/$(basename $i).json; done +for i in lib/spectests/spectests/*.wast; do wast2json --enable-all $i -o spec-test-corpus/$(basename $i).json; done mv spec-test-corpus/*.wasm fuzz/corpus/simple_instantiate/ rm -r spec-test-corpus ``` diff --git a/fuzz/fuzz_targets/compile_wasm.rs b/fuzz/fuzz_targets/compile_wasm.rs new file mode 100644 index 000000000..e36d8c396 --- /dev/null +++ b/fuzz/fuzz_targets/compile_wasm.rs @@ -0,0 +1,25 @@ +#![no_main] +#[macro_use] +extern crate libfuzzer_sys; +extern crate wasmer_runtime; +extern crate wasmer_runtime_core; +extern crate wasmer_llvm_backend; +extern crate wasmer_singlepass_backend; + +use wasmer_runtime::{compile, compile_with}; +use wasmer_runtime_core::backend::Compiler; + +fn get_llvm_compiler() -> impl Compiler { + use wasmer_llvm_backend::LLVMCompiler; + LLVMCompiler::new() +} +fn get_singlepass_compiler() -> impl Compiler { + use wasmer_singlepass_backend::SinglePassCompiler; + SinglePassCompiler::new() +} + +fuzz_target!(|data: &[u8]| { + let _ = compile_with(data, &get_llvm_compiler()); + let _ = compile(data); + let _ = compile_with(data, &get_singlepass_compiler()); +}); diff --git a/fuzz/fuzz_targets/simple_instantiate.rs b/fuzz/fuzz_targets/simple_instantiate.rs index 831bbb1a5..e4912546d 100644 --- a/fuzz/fuzz_targets/simple_instantiate.rs +++ b/fuzz/fuzz_targets/simple_instantiate.rs @@ -1,11 +1,9 @@ #![no_main] -#[macro_use] extern crate libfuzzer_sys; +#[macro_use] +extern crate libfuzzer_sys; extern crate wasmer_runtime; -use wasmer_runtime::{ - instantiate, - imports, -}; +use wasmer_runtime::{imports, instantiate}; fuzz_target!(|data: &[u8]| { let import_object = imports! {}; diff --git a/fuzz/fuzz_targets/validate_wasm.rs b/fuzz/fuzz_targets/validate_wasm.rs new file mode 100644 index 000000000..f386105ec --- /dev/null +++ b/fuzz/fuzz_targets/validate_wasm.rs @@ -0,0 +1,20 @@ +#![no_main] +#[macro_use] +extern crate libfuzzer_sys; + +extern crate wasmer; +extern crate wasmer_runtime_core; + +use wasmer_runtime_core::backend::Features; + +fuzz_target!(|data: &[u8]| { + let _ = wasmer::utils::is_wasm_binary(data); + let _ = wasmer_runtime_core::validate_and_report_errors_with_features( + &data, + Features { + // Modify these values to explore additional parts of wasmer. + simd: false, + threads: false, + }, + ); +}); diff --git a/install.sh b/install.sh index b937505d6..e6c874e2a 100755 --- a/install.sh +++ b/install.sh @@ -1,8 +1,8 @@ #!/bin/sh # This install script is intended to download and install the latest available -# release of the wasmer. -# Installer script inspired from: +# release of Wasmer. +# Installer script inspired by: # 1) https://raw.githubusercontent.com/golang/dep/master/install.sh # 2) https://sh.rustup.rs # 3) https://yarnpkg.com/install.sh @@ -22,7 +22,6 @@ set -e - reset="\033[0m" red="\033[31m" green="\033[32m" @@ -209,9 +208,11 @@ initArch() { printf "$cyan> Using WASMER_ARCH ($WASMER_ARCH).$reset\n" ARCH="$WASMER_ARCH" fi + # If you modify this list, please also modify scripts/binary-name.sh case $ARCH in amd64) ARCH="amd64";; x86_64) ARCH="amd64";; + aarch64) ARCH="arm64";; # i386) ARCH="386";; *) printf "$red> The system architecture (${ARCH}) is not supported by this installation script.$reset\n"; exit 1;; esac @@ -257,10 +258,10 @@ wasmer_install() { printf "${reset}Installing Wasmer and WAPM!$reset\n" if [ "$WASMER_INSTALL_LOG" = "$WASMER_VERBOSE" ]; then printf " -${magenta1} ww -${magenta1} wwwww -${magenta1} ww wwwwww w -${magenta1} wwwww wwwwwwwww +${magenta1} ww +${magenta1} wwwww +${magenta1} ww wwwwww w +${magenta1} wwwww wwwwwwwww ${magenta1}ww wwwwww w wwwwwww ${magenta1}wwwww wwwwwwwwww wwwww ${magenta1}wwwwww w wwwwwww wwwww @@ -269,10 +270,10 @@ ${magenta1}wwwwwwwwwwwwwww wwwww wwwww ${magenta1}wwwwwwwwwwwwwww wwwww wwwww ${magenta1}wwwwwwwwwwwwwww wwwww wwwww ${magenta1}wwwwwwwwwwwwwww wwwww wwww -${magenta1}wwwwwwwwwwwwwww wwwww -${magenta1} wwwwwwwwwwww wwww -${magenta1} wwwwwwww -${magenta1} wwww +${magenta1}wwwwwwwwwwwwwww wwwww +${magenta1} wwwwwwwwwwww wwww +${magenta1} wwwwwwww +${magenta1} wwww ${reset} " fi diff --git a/integration_tests/cowsay/README.md b/integration_tests/cowsay/README.md index f44e6e680..660e457e7 100644 --- a/integration_tests/cowsay/README.md +++ b/integration_tests/cowsay/README.md @@ -1,9 +1,8 @@ # `cowsay` integration test - This starts Wasmer with the Cowsay WASI Wasm file. The test makes assertions on the output of Wasmer. Run test with: -```bash +```sh ./integration_tests/cowsay/test.sh ``` diff --git a/lib/clif-backend/Cargo.toml b/lib/clif-backend/Cargo.toml index d0bf64d7b..1dea62a24 100644 --- a/lib/clif-backend/Cargo.toml +++ b/lib/clif-backend/Cargo.toml @@ -1,41 +1,43 @@ [package] name = "wasmer-clif-backend" -version = "0.6.0" +version = "0.11.0" description = "Wasmer runtime Cranelift compiler backend" license = "MIT" authors = ["The Wasmer Engineering Team "] repository = "https://github.com/wasmerio/wasmer" +keywords = ["wasm", "webassembly", "compiler", "JIT", "AOT"] +categories = ["wasm"] edition = "2018" readme = "README.md" [dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } -cranelift-native = { version = "0.31" } -cranelift-codegen = { version = "0.31" } -cranelift-entity = { version = "0.31" } -cranelift-frontend = { package = "wasmer-clif-fork-frontend", version = "0.33" } -cranelift-wasm = { package = "wasmer-clif-fork-wasm", version = "0.33" } -target-lexicon = "0.4.0" -wasmparser = "0.35.1" +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } +cranelift-native = "0.44.0" +cranelift-codegen = "0.44.0" +cranelift-entity = "0.44.0" +cranelift-frontend = { package = "wasmer-clif-fork-frontend", version = "0.44.0" } +cranelift-wasm = { package = "wasmer-clif-fork-wasm", version = "0.44.0" } +target-lexicon = "0.8.1" +wasmparser = "0.39.1" byteorder = "1.3.2" nix = "0.15.0" libc = "0.2.60" -rayon = "1.1.0" +rayon = "1.1" # Dependencies for caching. [dependencies.serde] -version = "1.0.99" +version = "1.0" features = ["rc"] [dependencies.serde_derive] -version = "1.0.98" +version = "1.0" [dependencies.serde_bytes] -version = "0.11.2" +version = "0.11" [dependencies.serde-bench] version = "0.0.7" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.7", features = ["errhandlingapi", "minwindef", "minwinbase", "winnt"] } -wasmer-win-exception-handler = { path = "../win-exception-handler", version = "0.6.0" } +winapi = { version = "0.3", features = ["errhandlingapi", "minwindef", "minwinbase", "winnt"] } +wasmer-win-exception-handler = { path = "../win-exception-handler", version = "0.11.0" } [features] debug = ["wasmer-runtime-core/debug"] diff --git a/lib/clif-backend/README.md b/lib/clif-backend/README.md index 2f29f4543..9c82c761d 100644 --- a/lib/clif-backend/README.md +++ b/lib/clif-backend/README.md @@ -1,21 +1,21 @@

- Wasmer logo + Wasmer logo

- - Build Status + + Build Status - License + License Join the Wasmer Community - Number of downloads from crates.io + Number of downloads from crates.io Read our API documentation @@ -36,7 +36,7 @@ This crate represents the Cranelift backend integration for Wasmer. If you are using the `wasmer` CLI, you can specify the backend with: -```bash +```sh wasmer run program.wasm --backend=cranelift ``` diff --git a/lib/clif-backend/src/code.rs b/lib/clif-backend/src/code.rs index f5e4b3b7a..d86d19b4f 100644 --- a/lib/clif-backend/src/code.rs +++ b/lib/clif-backend/src/code.rs @@ -54,6 +54,10 @@ impl ModuleCodeGenerator } } + fn new_with_target(_: Option, _: Option, _: Option) -> Self { + unimplemented!("cross compilation is not available for clif backend") + } + fn backend_id() -> Backend { Backend::Cranelift } @@ -128,166 +132,6 @@ impl ModuleCodeGenerator .state .initialize(&builder.func.signature, exit_block); - #[cfg(feature = "debug")] - { - use cranelift_codegen::cursor::{Cursor, FuncCursor}; - use cranelift_codegen::ir::InstBuilder; - let entry_ebb = func.layout.entry_block().unwrap(); - let ebb = func.dfg.make_ebb(); - func.layout.insert_ebb(ebb, entry_ebb); - let mut pos = FuncCursor::new(&mut func).at_first_insertion_point(ebb); - let params = pos.func.dfg.ebb_params(entry_ebb).to_vec(); - - let new_ebb_params: Vec<_> = params - .iter() - .map(|¶m| { - pos.func - .dfg - .append_ebb_param(ebb, pos.func.dfg.value_type(param)) - }) - .collect(); - - let start_debug = { - let signature = pos.func.import_signature(ir::Signature { - call_conv: self.target_config().default_call_conv, - params: vec![ - ir::AbiParam::special(ir::types::I64, ir::ArgumentPurpose::VMContext), - ir::AbiParam::new(ir::types::I32), - ], - returns: vec![], - }); - - let name = ir::ExternalName::testcase("strtdbug"); - - pos.func.import_function(ir::ExtFuncData { - name, - signature, - colocated: false, - }) - }; - - let end_debug = { - let signature = pos.func.import_signature(ir::Signature { - call_conv: self.target_config().default_call_conv, - params: vec![ir::AbiParam::special( - ir::types::I64, - ir::ArgumentPurpose::VMContext, - )], - returns: vec![], - }); - - let name = ir::ExternalName::testcase("enddbug"); - - pos.func.import_function(ir::ExtFuncData { - name, - signature, - colocated: false, - }) - }; - - let i32_print = { - let signature = pos.func.import_signature(ir::Signature { - call_conv: self.target_config().default_call_conv, - params: vec![ - ir::AbiParam::special(ir::types::I64, ir::ArgumentPurpose::VMContext), - ir::AbiParam::new(ir::types::I32), - ], - returns: vec![], - }); - - let name = ir::ExternalName::testcase("i32print"); - - pos.func.import_function(ir::ExtFuncData { - name, - signature, - colocated: false, - }) - }; - - let i64_print = { - let signature = pos.func.import_signature(ir::Signature { - call_conv: self.target_config().default_call_conv, - params: vec![ - ir::AbiParam::special(ir::types::I64, ir::ArgumentPurpose::VMContext), - ir::AbiParam::new(ir::types::I64), - ], - returns: vec![], - }); - - let name = ir::ExternalName::testcase("i64print"); - - pos.func.import_function(ir::ExtFuncData { - name, - signature, - colocated: false, - }) - }; - - let f32_print = { - let signature = pos.func.import_signature(ir::Signature { - call_conv: self.target_config().default_call_conv, - params: vec![ - ir::AbiParam::special(ir::types::I64, ir::ArgumentPurpose::VMContext), - ir::AbiParam::new(ir::types::F32), - ], - returns: vec![], - }); - - let name = ir::ExternalName::testcase("f32print"); - - pos.func.import_function(ir::ExtFuncData { - name, - signature, - colocated: false, - }) - }; - - let f64_print = { - let signature = pos.func.import_signature(ir::Signature { - call_conv: self.target_config().default_call_conv, - params: vec![ - ir::AbiParam::special(ir::types::I64, ir::ArgumentPurpose::VMContext), - ir::AbiParam::new(ir::types::F64), - ], - returns: vec![], - }); - - let name = ir::ExternalName::testcase("f64print"); - - pos.func.import_function(ir::ExtFuncData { - name, - signature, - colocated: false, - }) - }; - - let vmctx = pos - .func - .special_param(ir::ArgumentPurpose::VMContext) - .expect("missing vmctx parameter"); - - let func_index = pos.ins().iconst( - ir::types::I32, - func_index.index() as i64 + self.module.info.imported_functions.len() as i64, - ); - - pos.ins().call(start_debug, &[vmctx, func_index]); - - for param in new_ebb_params.iter().cloned() { - match pos.func.dfg.value_type(param) { - ir::types::I32 => pos.ins().call(i32_print, &[vmctx, param]), - ir::types::I64 => pos.ins().call(i64_print, &[vmctx, param]), - ir::types::F32 => pos.ins().call(f32_print, &[vmctx, param]), - ir::types::F64 => pos.ins().call(f64_print, &[vmctx, param]), - _ => unimplemented!(), - }; - } - - pos.ins().call(end_debug, &[vmctx]); - - pos.ins().jump(entry_ebb, new_ebb_params.as_slice()); - } - self.functions.push(func_env); Ok(self.functions.last_mut().unwrap()) } @@ -851,7 +695,9 @@ impl FuncEnvironment for FunctionEnvironment { } /// Generates a call IR with `callee` and `call_args` and inserts it at `pos` - /// TODO: add support for imported functions + /// + /// It's about generating code that calls a local or imported function; in + /// WebAssembly: `(call $foo)`. fn translate_call( &mut self, mut pos: FuncCursor, @@ -923,20 +769,31 @@ impl FuncEnvironment for FunctionEnvironment { readonly: true, }); - let imported_vmctx_addr = pos.func.create_global_value(ir::GlobalValueData::Load { - base: imported_func_struct_addr, - offset: (vm::ImportedFunc::offset_vmctx() as i32).into(), - global_type: ptr_type, - readonly: true, - }); + let imported_func_ctx_addr = + pos.func.create_global_value(ir::GlobalValueData::Load { + base: imported_func_struct_addr, + offset: (vm::ImportedFunc::offset_func_ctx() as i32).into(), + global_type: ptr_type, + readonly: true, + }); + + let imported_func_ctx_vmctx_addr = + pos.func.create_global_value(ir::GlobalValueData::Load { + base: imported_func_ctx_addr, + offset: (vm::FuncCtx::offset_vmctx() as i32).into(), + global_type: ptr_type, + readonly: true, + }); let imported_func_addr = pos.ins().global_value(ptr_type, imported_func_addr); - let imported_vmctx_addr = pos.ins().global_value(ptr_type, imported_vmctx_addr); + let imported_func_ctx_vmctx_addr = pos + .ins() + .global_value(ptr_type, imported_func_ctx_vmctx_addr); let sig_ref = pos.func.dfg.ext_funcs[callee].signature; let mut args = Vec::with_capacity(call_args.len() + 1); - args.push(imported_vmctx_addr); + args.push(imported_func_ctx_vmctx_addr); args.extend(call_args.iter().cloned()); Ok(pos @@ -1117,7 +974,18 @@ impl FunctionCodeGenerator for CraneliftFunctionCodeGenerator { fn feed_local(&mut self, ty: WpType, n: usize) -> Result<(), CodegenError> { let mut next_local = self.next_local; - cranelift_wasm::declare_locals(&mut self.builder(), n as u32, ty, &mut next_local)?; + let mut builder = FunctionBuilder::new( + &mut self.func, + &mut self.func_translator.func_ctx, + &mut self.position, + ); + cranelift_wasm::declare_locals( + &mut builder, + n as u32, + ty, + &mut next_local, + &mut self.func_env, + )?; self.next_local = next_local; Ok(()) } diff --git a/lib/clif-backend/src/lib.rs b/lib/clif-backend/src/lib.rs index eed22d39e..06bffc791 100644 --- a/lib/clif-backend/src/lib.rs +++ b/lib/clif-backend/src/lib.rs @@ -1,5 +1,10 @@ +//! The Wasmer Cranelift Backend crate is used to compile wasm binary code via parse events from the +//! Wasmer runtime common parser code into machine code. +//! + #![deny( dead_code, + missing_docs, nonstandard_style, unused_imports, unused_mut, @@ -7,6 +12,9 @@ unused_unsafe, unreachable_patterns )] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + mod cache; mod code; mod libcalls; @@ -31,7 +39,7 @@ extern crate serde; fn get_isa() -> Box { let flags = { let mut builder = settings::builder(); - builder.set("opt_level", "best").unwrap(); + builder.set("opt_level", "speed_and_size").unwrap(); builder.set("jump_tables_enabled", "false").unwrap(); if cfg!(not(test)) { @@ -39,7 +47,7 @@ fn get_isa() -> Box { } let flags = settings::Flags::new(builder); - debug_assert_eq!(flags.opt_level(), settings::OptLevel::Best); + debug_assert_eq!(flags.opt_level(), settings::OptLevel::SpeedAndSize); flags }; isa::lookup(Triple::host()).unwrap().finish(flags) @@ -50,6 +58,8 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION"); use wasmer_runtime_core::codegen::SimpleStreamingCompilerGen; +/// Streaming compiler implementation for the Cranelift backed. Compiles web assembly binary into +/// machine code. pub type CraneliftCompiler = SimpleStreamingCompilerGen< code::CraneliftModuleCodeGenerator, code::CraneliftFunctionCodeGenerator, diff --git a/lib/clif-backend/src/relocation.rs b/lib/clif-backend/src/relocation.rs index 50a032095..a86bdeec9 100644 --- a/lib/clif-backend/src/relocation.rs +++ b/lib/clif-backend/src/relocation.rs @@ -105,7 +105,7 @@ impl binemit::RelocSink for RelocSink { _ebb_offset: binemit::CodeOffset, ) { // This should use the `offsets` field of `ir::Function`. - unimplemented!(); + unimplemented!("RelocSink::reloc_ebb"); } fn reloc_external( &mut self, @@ -146,7 +146,7 @@ impl binemit::RelocSink for RelocSink { DYNAMIC_MEM_GROW => VmCallKind::DynamicMemoryGrow, DYNAMIC_MEM_SIZE => VmCallKind::DynamicMemorySize, - _ => unimplemented!(), + _ => unimplemented!("reloc_external VmCall::Local {}", index), })), IMPORT_NAMESPACE => RelocationType::VmCall(VmCall::Import(match index { STATIC_MEM_GROW => VmCallKind::StaticMemoryGrow, @@ -157,10 +157,10 @@ impl binemit::RelocSink for RelocSink { DYNAMIC_MEM_GROW => VmCallKind::DynamicMemoryGrow, DYNAMIC_MEM_SIZE => VmCallKind::DynamicMemorySize, - _ => unimplemented!(), + _ => unimplemented!("reloc_external VmCall::Import {}", index), })), SIG_NAMESPACE => RelocationType::Signature(SigIndex::new(index as usize)), - _ => unimplemented!(), + _ => unimplemented!("reloc_external SigIndex {}", index), }; self.external_relocs.push(ExternalRelocation { reloc, @@ -202,13 +202,18 @@ impl binemit::RelocSink for RelocSink { } } } + + fn reloc_constant(&mut self, _: u32, _: cranelift_codegen::binemit::Reloc, _: u32) { + unimplemented!("RelocSink::reloc_constant") + } + fn reloc_jt( &mut self, _offset: binemit::CodeOffset, _reloc: binemit::Reloc, _jt: ir::JumpTable, ) { - unimplemented!(); + unimplemented!("RelocSink::reloc_jt"); } } diff --git a/lib/clif-backend/src/resolver.rs b/lib/clif-backend/src/resolver.rs index 4e4f807df..efc038d58 100644 --- a/lib/clif-backend/src/resolver.rs +++ b/lib/clif-backend/src/resolver.rs @@ -1,29 +1,31 @@ -use crate::{cache::BackendCache, trampoline::Trampolines}; use crate::{ + cache::BackendCache, libcalls, relocation::{ ExternalRelocation, LibCall, LocalRelocation, LocalTrapSink, Reloc, RelocSink, RelocationType, TrapSink, VmCall, VmCallKind, }, signal::HandlerData, + trampoline::Trampolines, +}; +use byteorder::{ByteOrder, LittleEndian}; +use cranelift_codegen::{ + binemit::{Stackmap, StackmapSink}, + ir, isa, Context, }; use rayon::prelude::*; - -use byteorder::{ByteOrder, LittleEndian}; -use cranelift_codegen::{ir, isa, Context}; use std::{ mem, ptr::{write_unaligned, NonNull}, sync::Arc, }; - -use wasmer_runtime_core::cache::Error as CacheError; use wasmer_runtime_core::{ self, backend::{ sys::{Memory, Protect}, SigRegistry, }, + cache::Error as CacheError, error::{CompileError, CompileResult}, module::ModuleInfo, structures::{Map, SliceMap, TypedIndex}, @@ -58,6 +60,11 @@ pub struct FuncResolverBuilder { import_len: usize, } +pub struct NoopStackmapSink {} +impl StackmapSink for NoopStackmapSink { + fn add_stackmap(&mut self, _: u32, _: Stackmap) {} +} + impl FuncResolverBuilder { pub fn new_from_backend_cache( backend_cache: BackendCache, @@ -109,12 +116,13 @@ impl FuncResolverBuilder { ctx.func = func.to_owned(); let mut reloc_sink = RelocSink::new(); let mut local_trap_sink = LocalTrapSink::new(); - + let mut stackmap_sink = NoopStackmapSink {}; ctx.compile_and_emit( isa, &mut code_buf, &mut reloc_sink, &mut local_trap_sink, + &mut stackmap_sink, ) .map_err(|e| CompileError::InternalError { msg: e.to_string() })?; ctx.clear(); @@ -241,25 +249,17 @@ impl FuncResolverBuilder { #[cfg(not(target_os = "windows"))] LibCall::Probestack => __rust_probestack as isize, }, - RelocationType::Intrinsic(ref name) => match name.as_str() { - "i32print" => i32_print as isize, - "i64print" => i64_print as isize, - "f32print" => f32_print as isize, - "f64print" => f64_print as isize, - "strtdbug" => start_debug as isize, - "enddbug" => end_debug as isize, - _ => Err(CompileError::InternalError { - msg: format!("unexpected intrinsic: {}", name), - })?, - }, + RelocationType::Intrinsic(ref name) => Err(CompileError::InternalError { + msg: format!("unexpected intrinsic: {}", name), + })?, RelocationType::VmCall(vmcall) => match vmcall { VmCall::Local(kind) => match kind { - VmCallKind::StaticMemoryGrow => vmcalls::local_static_memory_grow as _, - VmCallKind::StaticMemorySize => vmcalls::local_static_memory_size as _, - - VmCallKind::SharedStaticMemoryGrow => unimplemented!(), - VmCallKind::SharedStaticMemorySize => unimplemented!(), - + VmCallKind::StaticMemoryGrow | VmCallKind::SharedStaticMemoryGrow => { + vmcalls::local_static_memory_grow as _ + } + VmCallKind::StaticMemorySize | VmCallKind::SharedStaticMemorySize => { + vmcalls::local_static_memory_size as _ + } VmCallKind::DynamicMemoryGrow => { vmcalls::local_dynamic_memory_grow as _ } @@ -268,16 +268,12 @@ impl FuncResolverBuilder { } }, VmCall::Import(kind) => match kind { - VmCallKind::StaticMemoryGrow => { + VmCallKind::StaticMemoryGrow | VmCallKind::SharedStaticMemoryGrow => { vmcalls::imported_static_memory_grow as _ } - VmCallKind::StaticMemorySize => { + VmCallKind::StaticMemorySize | VmCallKind::SharedStaticMemorySize => { vmcalls::imported_static_memory_size as _ } - - VmCallKind::SharedStaticMemoryGrow => unimplemented!(), - VmCallKind::SharedStaticMemorySize => unimplemented!(), - VmCallKind::DynamicMemoryGrow => { vmcalls::imported_dynamic_memory_grow as _ } @@ -366,28 +362,3 @@ impl FuncResolver { fn round_up(n: usize, multiple: usize) -> usize { (n + multiple - 1) & !(multiple - 1) } - -extern "C" fn i32_print(_ctx: &mut vm::Ctx, n: i32) { - eprint!(" i32: {},", n); -} -extern "C" fn i64_print(_ctx: &mut vm::Ctx, n: i64) { - eprint!(" i64: {},", n); -} -extern "C" fn f32_print(_ctx: &mut vm::Ctx, n: f32) { - eprint!(" f32: {},", n); -} -extern "C" fn f64_print(_ctx: &mut vm::Ctx, n: f64) { - eprint!(" f64: {},", n); -} -extern "C" fn start_debug(ctx: &mut vm::Ctx, func_index: u32) { - if let Some(symbol_map) = unsafe { ctx.borrow_symbol_map() } { - if let Some(fn_name) = symbol_map.get(&func_index) { - eprint!("func ({} ({})), args: [", fn_name, func_index); - return; - } - } - eprint!("func ({}), args: [", func_index); -} -extern "C" fn end_debug(_ctx: &mut vm::Ctx) { - eprintln!(" ]"); -} diff --git a/lib/clif-backend/src/signal/mod.rs b/lib/clif-backend/src/signal/mod.rs index 3facce2ad..116da3f56 100644 --- a/lib/clif-backend/src/signal/mod.rs +++ b/lib/clif-backend/src/signal/mod.rs @@ -1,12 +1,14 @@ -use crate::relocation::{TrapData, TrapSink}; -use crate::resolver::FuncResolver; -use crate::trampoline::Trampolines; +use crate::{ + relocation::{TrapData, TrapSink}, + resolver::FuncResolver, + trampoline::Trampolines, +}; use libc::c_void; use std::{any::Any, cell::Cell, ptr::NonNull, sync::Arc}; use wasmer_runtime_core::{ backend::RunnableModule, module::ModuleInfo, - typed_func::{Wasm, WasmTrapInfo}, + typed_func::{Trampoline, Wasm, WasmTrapInfo}, types::{LocalFuncIndex, SigIndex}, vm, }; @@ -59,7 +61,7 @@ impl RunnableModule for Caller { fn get_trampoline(&self, _: &ModuleInfo, sig_index: SigIndex) -> Option { unsafe extern "C" fn invoke( - trampoline: unsafe extern "C" fn(*mut vm::Ctx, NonNull, *const u64, *mut u64), + trampoline: Trampoline, ctx: *mut vm::Ctx, func: NonNull, args: *const u64, diff --git a/lib/clif-backend/src/signal/unix.rs b/lib/clif-backend/src/signal/unix.rs index 39f3aa893..79b6619d3 100644 --- a/lib/clif-backend/src/signal/unix.rs +++ b/lib/clif-backend/src/signal/unix.rs @@ -98,7 +98,10 @@ pub fn call_protected( }, Ok(SIGSEGV) | Ok(SIGBUS) => WasmTrapInfo::MemoryOutOfBounds, Ok(SIGFPE) => WasmTrapInfo::IllegalArithmetic, - _ => unimplemented!(), + _ => unimplemented!( + "WasmTrapInfo::Unknown signal:{:?}", + Signal::from_c_int(signum) + ), })) } else { let signal = match Signal::from_c_int(signum) { diff --git a/lib/clif-backend/src/signal/windows.rs b/lib/clif-backend/src/signal/windows.rs index d755cd575..363119c6e 100644 --- a/lib/clif-backend/src/signal/windows.rs +++ b/lib/clif-backend/src/signal/windows.rs @@ -1,24 +1,30 @@ -use crate::relocation::{TrapCode, TrapData}; -use crate::signal::{CallProtError, HandlerData}; -use crate::trampoline::Trampoline; -use std::cell::Cell; -use std::ffi::c_void; -use std::ptr::{self, NonNull}; -use wasmer_runtime_core::typed_func::WasmTrapInfo; -use wasmer_runtime_core::vm::Ctx; -use wasmer_runtime_core::vm::Func; +use crate::{ + relocation::{TrapCode, TrapData}, + signal::{CallProtError, HandlerData}, +}; +use std::{ + cell::Cell, + ffi::c_void, + ptr::{self, NonNull}, +}; +use wasmer_runtime_core::{ + typed_func::{Trampoline, WasmTrapInfo}, + vm::{Ctx, Func}, +}; use wasmer_win_exception_handler::CallProtectedData; pub use wasmer_win_exception_handler::_call_protected; -use winapi::shared::minwindef::DWORD; -use winapi::um::minwinbase::{ - EXCEPTION_ACCESS_VIOLATION, EXCEPTION_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_BREAKPOINT, - EXCEPTION_DATATYPE_MISALIGNMENT, EXCEPTION_FLT_DENORMAL_OPERAND, EXCEPTION_FLT_DIVIDE_BY_ZERO, - EXCEPTION_FLT_INEXACT_RESULT, EXCEPTION_FLT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW, - EXCEPTION_FLT_STACK_CHECK, EXCEPTION_FLT_UNDERFLOW, EXCEPTION_GUARD_PAGE, - EXCEPTION_ILLEGAL_INSTRUCTION, EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW, - EXCEPTION_INVALID_HANDLE, EXCEPTION_IN_PAGE_ERROR, EXCEPTION_NONCONTINUABLE_EXCEPTION, - EXCEPTION_POSSIBLE_DEADLOCK, EXCEPTION_PRIV_INSTRUCTION, EXCEPTION_SINGLE_STEP, - EXCEPTION_STACK_OVERFLOW, +use winapi::{ + shared::minwindef::DWORD, + um::minwinbase::{ + EXCEPTION_ACCESS_VIOLATION, EXCEPTION_ARRAY_BOUNDS_EXCEEDED, EXCEPTION_BREAKPOINT, + EXCEPTION_DATATYPE_MISALIGNMENT, EXCEPTION_FLT_DENORMAL_OPERAND, + EXCEPTION_FLT_DIVIDE_BY_ZERO, EXCEPTION_FLT_INEXACT_RESULT, + EXCEPTION_FLT_INVALID_OPERATION, EXCEPTION_FLT_OVERFLOW, EXCEPTION_FLT_STACK_CHECK, + EXCEPTION_FLT_UNDERFLOW, EXCEPTION_GUARD_PAGE, EXCEPTION_ILLEGAL_INSTRUCTION, + EXCEPTION_INT_DIVIDE_BY_ZERO, EXCEPTION_INT_OVERFLOW, EXCEPTION_INVALID_HANDLE, + EXCEPTION_IN_PAGE_ERROR, EXCEPTION_NONCONTINUABLE_EXCEPTION, EXCEPTION_POSSIBLE_DEADLOCK, + EXCEPTION_PRIV_INSTRUCTION, EXCEPTION_SINGLE_STEP, EXCEPTION_STACK_OVERFLOW, + }, }; thread_local! { @@ -110,5 +116,5 @@ pub fn call_protected( pub unsafe fn trigger_trap() -> ! { // TODO - unimplemented!(); + unimplemented!("windows::trigger_trap"); } diff --git a/lib/clif-backend/src/trampoline.rs b/lib/clif-backend/src/trampoline.rs index 7f22c62a5..fcd1ff83d 100644 --- a/lib/clif-backend/src/trampoline.rs +++ b/lib/clif-backend/src/trampoline.rs @@ -1,17 +1,16 @@ -use crate::cache::TrampolineCache; +use crate::{cache::TrampolineCache, resolver::NoopStackmapSink}; use cranelift_codegen::{ binemit::{NullTrapSink, Reloc, RelocSink}, cursor::{Cursor, FuncCursor}, ir::{self, InstBuilder}, isa, Context, }; -use std::collections::HashMap; -use std::{iter, mem, ptr::NonNull}; +use std::{collections::HashMap, iter, mem}; use wasmer_runtime_core::{ backend::sys::{Memory, Protect}, module::{ExportIndex, ModuleInfo}, + typed_func::Trampoline, types::{FuncSig, SigIndex, Type}, - vm, }; struct NullRelocSink {} @@ -19,11 +18,14 @@ struct NullRelocSink {} impl RelocSink for NullRelocSink { fn reloc_ebb(&mut self, _: u32, _: Reloc, _: u32) {} fn reloc_external(&mut self, _: u32, _: Reloc, _: &ir::ExternalName, _: i64) {} + + fn reloc_constant(&mut self, _: u32, _: Reloc, _: u32) { + unimplemented!("RelocSink::reloc_constant") + } + fn reloc_jt(&mut self, _: u32, _: Reloc, _: ir::JumpTable) {} } -pub type Trampoline = unsafe extern "C" fn(*mut vm::Ctx, NonNull, *const u64, *mut u64); - pub struct Trampolines { memory: Memory, offsets: HashMap, @@ -89,12 +91,13 @@ impl Trampolines { ctx.func = trampoline_func; let mut code_buf = Vec::new(); - + let mut stackmap_sink = NoopStackmapSink {}; ctx.compile_and_emit( isa, &mut code_buf, &mut NullRelocSink {}, &mut NullTrapSink {}, + &mut stackmap_sink, ) .expect("unable to compile trampolines"); ctx.clear(); diff --git a/lib/dev-utils/Cargo.toml b/lib/dev-utils/Cargo.toml index 96fb6aeed..81fb49757 100644 --- a/lib/dev-utils/Cargo.toml +++ b/lib/dev-utils/Cargo.toml @@ -1,11 +1,12 @@ [package] name = "wasmer-dev-utils" -version = "0.6.0" +version = "0.11.0" description = "Wasmer runtime core library" license = "MIT" authors = ["The Wasmer Engineering Team "] edition = "2018" repository = "https://github.com/wasmerio/wasmer" +publish = false [dependencies] libc = "0.2.60" diff --git a/lib/dev-utils/src/lib.rs b/lib/dev-utils/src/lib.rs index 76ae6804f..2a470bba4 100644 --- a/lib/dev-utils/src/lib.rs +++ b/lib/dev-utils/src/lib.rs @@ -1,2 +1,5 @@ +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + pub mod file_descriptor; pub mod stdio; diff --git a/lib/emscripten-tests/Cargo.toml b/lib/emscripten-tests/Cargo.toml index 71546fbeb..2439b4295 100644 --- a/lib/emscripten-tests/Cargo.toml +++ b/lib/emscripten-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmer-emscripten-tests" -version = "0.6.0" +version = "0.11.0" description = "Tests for our Emscripten implementation" license = "MIT" authors = ["The Wasmer Engineering Team "] @@ -9,20 +9,20 @@ publish = false build = "build/mod.rs" [dependencies] -wasmer-emscripten = { path = "../emscripten", version = "0.6.0" } -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } -wasmer-clif-backend = { path = "../clif-backend", version = "0.6.0" } -wasmer-llvm-backend = { path = "../llvm-backend", version = "0.6.0", optional = true } -wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.6.0", optional = true } +wasmer-emscripten = { path = "../emscripten", version = "0.11.0" } +wasmer-runtime = { path = "../runtime", version = "0.11.0", default-features = false } +wasmer-clif-backend = { path = "../clif-backend", version = "0.11.0", optional = true} +wasmer-llvm-backend = { path = "../llvm-backend", version = "0.11.0", optional = true, features = ["test"] } +wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.11.0", optional = true } [dev-dependencies] wabt = "0.9.1" -wasmer-dev-utils = { path = "../dev-utils", version = "0.6.0"} +wasmer-dev-utils = { path = "../dev-utils", version = "0.11.0"} [build-dependencies] -glob = "0.3.0" +glob = "0.3" [features] -clif = [] -llvm = ["wasmer-llvm-backend"] -singlepass = ["wasmer-singlepass-backend"] +clif = ["wasmer-clif-backend", "wasmer-runtime/default-backend-cranelift"] +singlepass = ["wasmer-singlepass-backend", "wasmer-runtime/default-backend-singlepass"] +llvm = ["wasmer-llvm-backend", "wasmer-runtime/default-backend-llvm"] diff --git a/lib/emscripten-tests/emtests/ignores.txt b/lib/emscripten-tests/emtests/ignores.txt index 16ff1a2a7..af8f21b00 100644 --- a/lib/emscripten-tests/emtests/ignores.txt +++ b/lib/emscripten-tests/emtests/ignores.txt @@ -29,6 +29,7 @@ test_i16_emcc_intrinsic test_i64 test_i64_7z test_i64_varargs +test_indirectbr_many test_llvm_intrinsics test_longjmp_exc test_lower_intrinsics diff --git a/lib/emscripten-tests/src/lib.rs b/lib/emscripten-tests/src/lib.rs index 1da0abc73..59bcf90eb 100644 --- a/lib/emscripten-tests/src/lib.rs +++ b/lib/emscripten-tests/src/lib.rs @@ -3,40 +3,13 @@ mod tests { use std::sync::Arc; use wabt::wat2wasm; use wasmer_emscripten::is_emscripten_module; - use wasmer_runtime_core::backend::Compiler; - use wasmer_runtime_core::compile_with; - - #[cfg(feature = "clif")] - fn get_compiler() -> impl Compiler { - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() - } - - #[cfg(feature = "llvm")] - fn get_compiler() -> impl Compiler { - use wasmer_llvm_backend::LLVMCompiler; - LLVMCompiler::new() - } - - #[cfg(feature = "singlepass")] - fn get_compiler() -> impl Compiler { - use wasmer_singlepass_backend::SinglePassCompiler; - SinglePassCompiler::new() - } - - #[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] - fn get_compiler() -> impl Compiler { - panic!("compiler not specified, activate a compiler via features"); - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() - } + use wasmer_runtime::compile; #[test] fn should_detect_emscripten_files() { const WAST_BYTES: &[u8] = include_bytes!("tests/is_emscripten_true.wast"); let wasm_binary = wat2wasm(WAST_BYTES.to_vec()).expect("Can't convert to wasm"); - let module = - compile_with(&wasm_binary[..], &get_compiler()).expect("WASM can't be compiled"); + let module = compile(&wasm_binary[..]).expect("WASM can't be compiled"); let module = Arc::new(module); assert!(is_emscripten_module(&module)); } @@ -45,8 +18,7 @@ mod tests { fn should_detect_non_emscripten_files() { const WAST_BYTES: &[u8] = include_bytes!("tests/is_emscripten_false.wast"); let wasm_binary = wat2wasm(WAST_BYTES.to_vec()).expect("Can't convert to wasm"); - let module = - compile_with(&wasm_binary[..], &get_compiler()).expect("WASM can't be compiled"); + let module = compile(&wasm_binary[..]).expect("WASM can't be compiled"); let module = Arc::new(module); assert!(!is_emscripten_module(&module)); } diff --git a/lib/emscripten-tests/tests/emtests/_common.rs b/lib/emscripten-tests/tests/emtests/_common.rs index 1e5dfba6e..2e6c590d9 100644 --- a/lib/emscripten-tests/tests/emtests/_common.rs +++ b/lib/emscripten-tests/tests/emtests/_common.rs @@ -5,39 +5,12 @@ macro_rules! assert_emscripten_output { EmscriptenGlobals, generate_emscripten_env, }; - use wasmer_runtime_core::{ - backend::Compiler, - }; + use wasmer_runtime::compile; use wasmer_dev_utils::stdio::StdioCapturer; - #[cfg(feature = "clif")] - fn get_compiler() -> impl Compiler { - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() - } - - #[cfg(feature = "llvm")] - fn get_compiler() -> impl Compiler { - use wasmer_llvm_backend::LLVMCompiler; - LLVMCompiler::new() - } - - #[cfg(feature = "singlepass")] - fn get_compiler() -> impl Compiler { - use wasmer_singlepass_backend::SinglePassCompiler; - SinglePassCompiler::new() - } - - #[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] - fn get_compiler() -> impl Compiler { - panic!("compiler not specified, activate a compiler via features"); - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() - } - let wasm_bytes = include_bytes!($file); - let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &get_compiler()) + let module = compile(&wasm_bytes[..]) .expect("WASM can't be compiled"); // let module = compile(&wasm_bytes[..]) diff --git a/lib/emscripten-tests/tests/emtests/test_indirectbr_many.rs b/lib/emscripten-tests/tests/emtests/test_indirectbr_many.rs index fba25e6b2..a96fe2138 100644 --- a/lib/emscripten-tests/tests/emtests/test_indirectbr_many.rs +++ b/lib/emscripten-tests/tests/emtests/test_indirectbr_many.rs @@ -1,4 +1,5 @@ #[test] +#[ignore] fn test_test_indirectbr_many() { assert_emscripten_output!( "../../emtests/test_indirectbr_many.wasm", diff --git a/lib/emscripten/Cargo.toml b/lib/emscripten/Cargo.toml index 7ffaf2d7e..dffbe0942 100644 --- a/lib/emscripten/Cargo.toml +++ b/lib/emscripten/Cargo.toml @@ -1,21 +1,23 @@ [package] name = "wasmer-emscripten" -version = "0.6.0" +version = "0.11.0" description = "Wasmer runtime emscripten implementation library" license = "MIT" authors = ["The Wasmer Engineering Team "] repository = "https://github.com/wasmerio/wasmer" +keywords = ["wasm", "webassembly", "ABI", "emscripten", "posix"] +categories = ["wasm"] edition = "2018" [dependencies] -byteorder = "1.3.2" -lazy_static = "1.3.0" +byteorder = "1.3" +lazy_static = "1.4" libc = "0.2.60" -time = "0.1.42" -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } +time = "0.1" +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } [target.'cfg(windows)'.dependencies] -rand = "0.7.0" +getrandom = "0.1" [features] debug = ["wasmer-runtime-core/debug"] diff --git a/lib/emscripten/src/env/mod.rs b/lib/emscripten/src/env/mod.rs index 88fba54a6..a61a2bb9f 100644 --- a/lib/emscripten/src/env/mod.rs +++ b/lib/emscripten/src/env/mod.rs @@ -12,14 +12,14 @@ pub use self::windows::*; use libc::c_char; -use crate::{allocate_on_stack, EmscriptenData}; +use crate::{ + allocate_on_stack, + ptr::{Array, WasmPtr}, + EmscriptenData, +}; use std::os::raw::c_int; -use wasmer_runtime_core::{ - memory::ptr::{Array, WasmPtr}, - types::ValueType, - vm::Ctx, -}; +use wasmer_runtime_core::{types::ValueType, vm::Ctx}; pub fn call_malloc(ctx: &mut Ctx, size: u32) -> u32 { get_emscripten_data(ctx) diff --git a/lib/emscripten/src/env/unix/mod.rs b/lib/emscripten/src/env/unix/mod.rs index 0e62e8e2f..951650495 100644 --- a/lib/emscripten/src/env/unix/mod.rs +++ b/lib/emscripten/src/env/unix/mod.rs @@ -9,11 +9,9 @@ use std::mem; use std::os::raw::c_char; use crate::env::{call_malloc, call_malloc_with_cast, EmAddrInfo, EmSockAddr}; +use crate::ptr::{Array, WasmPtr}; use crate::utils::{copy_cstr_into_wasm, copy_terminated_array_of_cstrs}; -use wasmer_runtime_core::{ - memory::ptr::{Array, WasmPtr}, - vm::Ctx, -}; +use wasmer_runtime_core::vm::Ctx; // #[no_mangle] /// emscripten: _getenv // (name: *const char) -> *const c_char; diff --git a/lib/emscripten/src/env/windows/mod.rs b/lib/emscripten/src/env/windows/mod.rs index d6ba0b0da..025a01bd7 100644 --- a/lib/emscripten/src/env/windows/mod.rs +++ b/lib/emscripten/src/env/windows/mod.rs @@ -6,8 +6,9 @@ use std::mem; use std::os::raw::c_char; use crate::env::{call_malloc, EmAddrInfo}; +use crate::ptr::WasmPtr; use crate::utils::{copy_cstr_into_wasm, read_string_from_wasm}; -use wasmer_runtime_core::{memory::ptr::WasmPtr, vm::Ctx}; +use wasmer_runtime_core::vm::Ctx; extern "C" { #[link_name = "_putenv"] diff --git a/lib/emscripten/src/exception.rs b/lib/emscripten/src/exception.rs index 09f04a798..09ae286f8 100644 --- a/lib/emscripten/src/exception.rs +++ b/lib/emscripten/src/exception.rs @@ -10,22 +10,22 @@ pub fn ___cxa_allocate_exception(ctx: &mut Ctx, size: u32) -> u32 { pub fn ___cxa_current_primary_exception(_ctx: &mut Ctx) -> u32 { debug!("emscripten::___cxa_current_primary_exception"); - unimplemented!() + unimplemented!("emscripten::___cxa_current_primary_exception") } pub fn ___cxa_decrement_exception_refcount(_ctx: &mut Ctx, _a: u32) { debug!("emscripten::___cxa_decrement_exception_refcount({})", _a); - unimplemented!() + unimplemented!("emscripten::___cxa_decrement_exception_refcount({})", _a) } pub fn ___cxa_increment_exception_refcount(_ctx: &mut Ctx, _a: u32) { debug!("emscripten::___cxa_increment_exception_refcount({})", _a); - unimplemented!() + unimplemented!("emscripten::___cxa_increment_exception_refcount({})", _a) } pub fn ___cxa_rethrow_primary_exception(_ctx: &mut Ctx, _a: u32) { debug!("emscripten::___cxa_rethrow_primary_exception({})", _a); - unimplemented!() + unimplemented!("emscripten::___cxa_rethrow_primary_exception({})", _a) } /// emscripten: ___cxa_throw diff --git a/lib/emscripten/src/io/mod.rs b/lib/emscripten/src/io/mod.rs index 6666cd5af..bad5935fe 100644 --- a/lib/emscripten/src/io/mod.rs +++ b/lib/emscripten/src/io/mod.rs @@ -15,13 +15,13 @@ use wasmer_runtime_core::vm::Ctx; /// getprotobyname pub fn getprotobyname(_ctx: &mut Ctx, _name_ptr: i32) -> i32 { debug!("emscripten::getprotobyname"); - unimplemented!() + unimplemented!("emscripten::getprotobyname") } /// getprotobynumber pub fn getprotobynumber(_ctx: &mut Ctx, _one: i32) -> i32 { debug!("emscripten::getprotobynumber"); - unimplemented!() + unimplemented!("emscripten::getprotobynumber") } /// sigdelset @@ -53,11 +53,11 @@ pub fn sigfillset(ctx: &mut Ctx, set: i32) -> i32 { /// tzset pub fn tzset(_ctx: &mut Ctx) { debug!("emscripten::tzset - stub"); - //unimplemented!() + //unimplemented!("emscripten::tzset - stub") } /// strptime pub fn strptime(_ctx: &mut Ctx, _one: i32, _two: i32, _three: i32) -> i32 { debug!("emscripten::strptime"); - unimplemented!() + unimplemented!("emscripten::strptime") } diff --git a/lib/emscripten/src/io/windows.rs b/lib/emscripten/src/io/windows.rs index a3c6f70aa..a7d6dc60b 100644 --- a/lib/emscripten/src/io/windows.rs +++ b/lib/emscripten/src/io/windows.rs @@ -36,11 +36,11 @@ pub fn printf(_ctx: &mut Ctx, memory_offset: i32, extra: i32) -> i32 { /// chroot pub fn chroot(_ctx: &mut Ctx, _name_ptr: i32) -> i32 { debug!("emscripten::chroot"); - unimplemented!() + unimplemented!("emscripten::chroot") } /// getpwuid pub fn getpwuid(_ctx: &mut Ctx, _uid: i32) -> i32 { debug!("emscripten::getpwuid"); - unimplemented!() + unimplemented!("emscripten::getpwuid") } diff --git a/lib/emscripten/src/lib.rs b/lib/emscripten/src/lib.rs index c09f9b772..1c9b8a1f7 100644 --- a/lib/emscripten/src/lib.rs +++ b/lib/emscripten/src/lib.rs @@ -7,6 +7,9 @@ unused_unsafe, unreachable_patterns )] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + #[macro_use] extern crate wasmer_runtime_core; @@ -59,6 +62,7 @@ mod math; mod memory; mod process; mod pthread; +mod ptr; mod signal; mod storage; mod syscalls; @@ -107,7 +111,7 @@ pub struct EmscriptenData<'a> { pub dyn_call_iii: Option>, pub dyn_call_iiii: Option>, pub dyn_call_iifi: Option>, - pub dyn_call_v: Option>, + pub dyn_call_v: Option>, pub dyn_call_vi: Option>, pub dyn_call_vii: Option>, pub dyn_call_viii: Option>, @@ -164,7 +168,7 @@ pub struct EmscriptenData<'a> { pub temp_ret_0: i32, pub stack_save: Option>, - pub stack_restore: Option>, + pub stack_restore: Option>, pub set_threw: Option>, pub mapped_dirs: HashMap, } @@ -470,11 +474,7 @@ impl EmscriptenGlobals { let (memory_min, memory_max, shared) = get_emscripten_memory_size(&module)?; // Memory initialization - let memory_type = MemoryDescriptor { - minimum: memory_min, - maximum: memory_max, - shared: shared, - }; + let memory_type = MemoryDescriptor::new(memory_min, memory_max, shared)?; let memory = Memory::new(memory_type).unwrap(); let table_type = TableDescriptor { @@ -731,8 +731,9 @@ pub fn generate_emscripten_env(globals: &mut EmscriptenGlobals) -> ImportObject "___syscall345" => func!(crate::syscalls::___syscall345), // Process - "abort" => func!(crate::process::em_abort), + "abort" => func!(crate::process::_abort), "_abort" => func!(crate::process::_abort), + "_prctl" => func!(crate::process::_prctl), "abortStackOverflow" => func!(crate::process::abort_stack_overflow), "_llvm_trap" => func!(crate::process::_llvm_trap), "_fork" => func!(crate::process::_fork), @@ -825,6 +826,9 @@ pub fn generate_emscripten_env(globals: &mut EmscriptenGlobals) -> ImportObject "_gmtime" => func!(crate::time::_gmtime), // Math + "sqrt" => func!(crate::math::sqrt), + "floor" => func!(crate::math::floor), + "fabs" => func!(crate::math::fabs), "f64-rem" => func!(crate::math::f64_rem), "_llvm_copysign_f32" => func!(crate::math::_llvm_copysign_f32), "_llvm_copysign_f64" => func!(crate::math::_llvm_copysign_f64), diff --git a/lib/emscripten/src/math.rs b/lib/emscripten/src/math.rs index 3aea6f8b7..914411ee9 100644 --- a/lib/emscripten/src/math.rs +++ b/lib/emscripten/src/math.rs @@ -88,6 +88,21 @@ pub fn log(_ctx: &mut Ctx, value: f64) -> f64 { value.ln() } +// emscripten: global.Math sqrt +pub fn sqrt(_ctx: &mut Ctx, value: f64) -> f64 { + value.sqrt() +} + +// emscripten: global.Math floor +pub fn floor(_ctx: &mut Ctx, value: f64) -> f64 { + value.floor() +} + +// emscripten: global.Math fabs +pub fn fabs(_ctx: &mut Ctx, value: f64) -> f64 { + value.abs() +} + // emscripten: asm2wasm.f64-to-int pub fn f64_to_int(_ctx: &mut Ctx, value: f64) -> i32 { debug!("emscripten::f64_to_int {}", value); diff --git a/lib/emscripten/src/process.rs b/lib/emscripten/src/process.rs index cd083e943..a776734b4 100644 --- a/lib/emscripten/src/process.rs +++ b/lib/emscripten/src/process.rs @@ -1,11 +1,10 @@ -use libc::{abort, c_char, c_int, exit, EAGAIN}; +use libc::{abort, c_int, exit, EAGAIN}; #[cfg(not(target_os = "windows"))] type PidT = libc::pid_t; #[cfg(target_os = "windows")] type PidT = c_int; -use std::ffi::CStr; use wasmer_runtime_core::vm::Ctx; pub fn abort_with_message(ctx: &mut Ctx, message: &str) { @@ -21,6 +20,12 @@ pub fn _abort(_ctx: &mut Ctx) { } } +pub fn _prctl(ctx: &mut Ctx, _a: i32, _b: i32) -> i32 { + debug!("emscripten::_prctl"); + abort_with_message(ctx, "missing function: prctl"); + -1 +} + pub fn _fork(_ctx: &mut Ctx) -> PidT { debug!("emscripten::_fork"); // unsafe { @@ -45,18 +50,6 @@ pub fn _exit(_ctx: &mut Ctx, status: c_int) { unsafe { exit(status) } } -pub fn em_abort(ctx: &mut Ctx, message: u32) { - debug!("emscripten::em_abort {}", message); - let message_addr = emscripten_memory_pointer!(ctx.memory(0), message) as *mut c_char; - unsafe { - let message = CStr::from_ptr(message_addr) - .to_str() - .unwrap_or("Unexpected abort"); - - abort_with_message(ctx, message); - } -} - pub fn _kill(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 { debug!("emscripten::_kill"); -1 diff --git a/lib/emscripten/src/ptr.rs b/lib/emscripten/src/ptr.rs new file mode 100644 index 000000000..34cd68364 --- /dev/null +++ b/lib/emscripten/src/ptr.rs @@ -0,0 +1,116 @@ +//! This is a wrapper around the `WasmPtr` abstraction that does not allow deref of address 0 +//! This is a common assumption in Emscripten code + +// this is a wrapper with extra logic around the runtime-core `WasmPtr`, so we +// don't want to warn about unusued code here +#![allow(dead_code)] + +use std::{cell::Cell, fmt}; +pub use wasmer_runtime_core::memory::ptr::Array; +use wasmer_runtime_core::{ + memory::{ptr, Memory}, + types::{ValueType, WasmExternType}, +}; + +#[repr(transparent)] +pub struct WasmPtr(ptr::WasmPtr); + +unsafe impl ValueType for WasmPtr {} +impl Copy for WasmPtr {} + +impl Clone for WasmPtr { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl fmt::Debug for WasmPtr { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{:?}", self.0) + } +} + +unsafe impl WasmExternType for WasmPtr { + type Native = as WasmExternType>::Native; + + fn to_native(self) -> Self::Native { + self.0.to_native() + } + fn from_native(n: Self::Native) -> Self { + Self(ptr::WasmPtr::from_native(n)) + } +} + +impl PartialEq for WasmPtr { + fn eq(&self, other: &Self) -> bool { + self.0 == other.0 + } +} + +impl Eq for WasmPtr {} + +impl WasmPtr { + #[inline(always)] + pub fn new(offset: u32) -> Self { + Self(ptr::WasmPtr::new(offset)) + } + + #[inline(always)] + pub fn offset(self) -> u32 { + self.0.offset() + } +} + +impl WasmPtr { + #[inline(always)] + pub fn deref<'a>(self, memory: &'a Memory) -> Option<&'a Cell> { + if self.0.offset() == 0 { + None + } else { + self.0.deref(memory) + } + } + + #[inline(always)] + pub unsafe fn deref_mut<'a>(self, memory: &'a Memory) -> Option<&'a mut Cell> { + if self.0.offset() == 0 { + None + } else { + self.0.deref_mut(memory) + } + } +} + +impl WasmPtr { + #[inline(always)] + pub fn deref<'a>(self, memory: &'a Memory, index: u32, length: u32) -> Option<&'a [Cell]> { + if self.0.offset() == 0 { + None + } else { + self.0.deref(memory, index, length) + } + } + + #[inline] + pub unsafe fn deref_mut<'a>( + self, + memory: &'a Memory, + index: u32, + length: u32, + ) -> Option<&'a mut [Cell]> { + if self.0.offset() == 0 { + None + } else { + self.0.deref_mut(memory, index, length) + } + } + + #[inline(always)] + pub fn get_utf8_string<'a>(self, memory: &'a Memory, str_len: u32) -> Option<&'a str> { + if self.0.offset() == 0 { + None + } else { + self.0.get_utf8_string(memory, str_len) + } + } +} diff --git a/lib/emscripten/src/syscalls/mod.rs b/lib/emscripten/src/syscalls/mod.rs index b098bd152..ba5965fde 100644 --- a/lib/emscripten/src/syscalls/mod.rs +++ b/lib/emscripten/src/syscalls/mod.rs @@ -10,7 +10,10 @@ pub use self::unix::*; #[cfg(windows)] pub use self::windows::*; -use crate::utils::{copy_stat_into_wasm, get_cstr_path, get_current_directory}; +use crate::{ + ptr::{Array, WasmPtr}, + utils::{copy_stat_into_wasm, get_cstr_path, get_current_directory}, +}; use super::varargs::VarArgs; use byteorder::{ByteOrder, LittleEndian}; @@ -40,10 +43,7 @@ use libc::{ write, // ENOTTY, }; -use wasmer_runtime_core::{ - memory::ptr::{Array, WasmPtr}, - vm::Ctx, -}; +use wasmer_runtime_core::vm::Ctx; use super::env; use std::cell::Cell; diff --git a/lib/emscripten/src/syscalls/unix.rs b/lib/emscripten/src/syscalls/unix.rs index 000a3e319..62d807c02 100644 --- a/lib/emscripten/src/syscalls/unix.rs +++ b/lib/emscripten/src/syscalls/unix.rs @@ -1,4 +1,4 @@ -use crate::varargs::VarArgs; +use crate::{ptr::WasmPtr, varargs::VarArgs}; #[cfg(target_os = "macos")] use libc::size_t; /// NOTE: TODO: These syscalls only support wasm_32 for now because they assume offsets are u32 @@ -7,6 +7,7 @@ use libc::{ accept, access, bind, + c_char, c_int, c_ulong, c_void, @@ -111,7 +112,7 @@ fn translate_ioctl(wasm_ioctl: u32) -> c_ulong { #[allow(unused_imports)] use std::ffi::CStr; -use wasmer_runtime_core::{memory::ptr::WasmPtr, vm::Ctx}; +use wasmer_runtime_core::vm::Ctx; use crate::env::EmSockAddr; use crate::utils::{self, get_cstr_path}; @@ -259,7 +260,7 @@ pub fn ___syscall194(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in ftruncate64(_fd, _length) } #[cfg(target_os = "macos")] - unimplemented!() + unimplemented!("emscripten::___syscall194 (ftruncate64) {}", _which) } /// lchown @@ -1063,14 +1064,14 @@ pub fn ___syscall220(ctx: &mut Ctx, _which: i32, mut varargs: VarArgs) -> i32 { let upper_bound = std::cmp::min((*dirent).d_reclen, 255) as usize; let mut i = 0; while i < upper_bound { - *(dirp.add(pos + 11 + i) as *mut i8) = (*dirent).d_name[i] as _; + *(dirp.add(pos + 11 + i) as *mut c_char) = (*dirent).d_name[i] as c_char; i += 1; } // We set the termination string char - *(dirp.add(pos + 11 + i) as *mut i8) = 0 as i8; + *(dirp.add(pos + 11 + i) as *mut c_char) = 0 as c_char; debug!( " => file {}", - CStr::from_ptr(dirp.add(pos + 11) as *const i8) + CStr::from_ptr(dirp.add(pos + 11) as *const c_char) .to_str() .unwrap() ); @@ -1111,6 +1112,6 @@ pub fn ___syscall324(ctx: &mut Ctx, _which: c_int, mut varargs: VarArgs) -> c_in } #[cfg(target_os = "macos")] { - unimplemented!() + unimplemented!("emscripten::___syscall324 (fallocate) {}", _which) } } diff --git a/lib/emscripten/src/syscalls/windows.rs b/lib/emscripten/src/syscalls/windows.rs index f6a867356..b5ed6cc83 100644 --- a/lib/emscripten/src/syscalls/windows.rs +++ b/lib/emscripten/src/syscalls/windows.rs @@ -2,7 +2,6 @@ use crate::utils::{copy_cstr_into_wasm, get_cstr_path}; use crate::varargs::VarArgs; use libc::mkdir; use libc::open; -use rand::Rng; use std::env; use std::ffi::CString; use std::fs::File; @@ -39,7 +38,8 @@ pub fn ___syscall5(ctx: &mut Ctx, which: c_int, mut varargs: VarArgs) -> c_int { let ptr = tmp_dir_c_str.as_ptr() as *const i8; let mut urandom_file = File::create(tmp_dir).unwrap(); // create some random bytes and put them into the file - let random_bytes = rand::thread_rng().gen::<[u8; 32]>(); + let mut random_bytes = [0u8; 32]; + getrandom::getrandom(&mut random_bytes).unwrap(); let _ = urandom_file.write_all(&random_bytes).unwrap(); // put the file path string into wasm memory let urandom_file_offset = unsafe { copy_cstr_into_wasm(ctx, ptr) }; @@ -66,13 +66,13 @@ pub fn ___syscall5(ctx: &mut Ctx, which: c_int, mut varargs: VarArgs) -> c_int { /// link pub fn ___syscall9(_ctx: &mut Ctx, _which: c_int, mut _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall9 (link) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall9 (link) {}", _which); } /// ftruncate64 pub fn ___syscall194(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 { debug!("emscripten::___syscall194 - stub"); - unimplemented!() + unimplemented!("emscripten::___syscall194 - stub") } // chown @@ -86,13 +86,13 @@ pub fn ___syscall212(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_i /// access pub fn ___syscall33(_ctx: &mut Ctx, _which: c_int, mut _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall33 (access) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall33 (access) {}", _which); } /// nice pub fn ___syscall34(_ctx: &mut Ctx, _which: c_int, mut _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall34 (nice) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall34 (nice) {}", _which); } // mkdir @@ -113,19 +113,19 @@ pub fn ___syscall39(ctx: &mut Ctx, which: c_int, mut varargs: VarArgs) -> c_int /// dup pub fn ___syscall41(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall41 (dup) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall41 (dup) {}", _which); } /// getrusage pub fn ___syscall77(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall77 (getrusage) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall77 (getrusage) {}", _which); } /// symlink pub fn ___syscall83(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall83 (symlink) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall83 (symlink) {}", _which); } /// readlink @@ -143,38 +143,38 @@ pub fn ___syscall132(_ctx: &mut Ctx, _which: c_int, mut _varargs: VarArgs) -> c_ /// lchown pub fn ___syscall198(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall198 (lchown) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall198 (lchown) {}", _which); } /// getgid32 pub fn ___syscall200(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 { debug!("emscripten::___syscall200 (getgid32)"); - unimplemented!(); + unimplemented!("emscripten::___syscall200 (getgid32)"); } // geteuid32 pub fn ___syscall201(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 { debug!("emscripten::___syscall201 (geteuid32)"); - unimplemented!(); + unimplemented!("emscripten::___syscall201 (geteuid32)"); } // getegid32 pub fn ___syscall202(_ctx: &mut Ctx, _one: i32, _two: i32) -> i32 { // gid_t debug!("emscripten::___syscall202 (getegid32)"); - unimplemented!(); + unimplemented!("emscripten::___syscall202 (getegid32)"); } /// getgroups pub fn ___syscall205(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall205 (getgroups) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall205 (getgroups) {}", _which); } /// madvise pub fn ___syscall219(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall212 (chown) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall212 (chown) {}", _which); } /// dup3 @@ -194,7 +194,7 @@ pub fn ___syscall54(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_in /// fchmod pub fn ___syscall94(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall118 (fchmod) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall118 (fchmod) {}", _which); } // socketcall @@ -209,7 +209,7 @@ pub fn ___syscall102(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_i /// fsync pub fn ___syscall118(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall118 (fsync) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall118 (fsync) {}", _which); } // pread @@ -247,7 +247,7 @@ pub fn ___syscall142(_ctx: &mut Ctx, which: c_int, mut _varargs: VarArgs) -> c_i /// fdatasync pub fn ___syscall148(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall148 (fdatasync) {}", _which); - unimplemented!(); + unimplemented!("emscripten::___syscall148 (fdatasync) {}", _which); } // setpgid @@ -300,11 +300,11 @@ pub fn ___syscall221(_ctx: &mut Ctx, _which: c_int, mut _varargs: VarArgs) -> c_ /// fchown pub fn ___syscall207(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall207 (fchown) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall207 (fchown) {}", _which) } /// fallocate pub fn ___syscall324(_ctx: &mut Ctx, _which: c_int, _varargs: VarArgs) -> c_int { debug!("emscripten::___syscall324 (fallocate) {}", _which); - unimplemented!() + unimplemented!("emscripten::___syscall324 (fallocate) {}", _which) } diff --git a/lib/emscripten/src/utils.rs b/lib/emscripten/src/utils.rs index 2585af43f..e1c403565 100644 --- a/lib/emscripten/src/utils.rs +++ b/lib/emscripten/src/utils.rs @@ -24,7 +24,9 @@ pub fn is_emscripten_module(module: &Module) -> bool { .namespace_table .get(import_name.namespace_index); let field = module.info().name_table.get(import_name.name_index); - if (field == "_emscripten_memcpy_big" || field == "emscripten_memcpy_big") + if (field == "_emscripten_memcpy_big" + || field == "emscripten_memcpy_big" + || field == "__map_file") && namespace == "env" { return true; diff --git a/lib/kernel-loader/src/lib.rs b/lib/kernel-loader/src/lib.rs index 51ca6c7da..37f51a424 100644 --- a/lib/kernel-loader/src/lib.rs +++ b/lib/kernel-loader/src/lib.rs @@ -1,3 +1,6 @@ +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + pub mod service; use service::{ImportInfo, LoadProfile, RunProfile, ServiceContext, TableEntryRequest}; diff --git a/lib/kernel-net/src/lib.rs b/lib/kernel-net/src/lib.rs index 4686867a7..cc6f91820 100644 --- a/lib/kernel-net/src/lib.rs +++ b/lib/kernel-net/src/lib.rs @@ -1,5 +1,7 @@ #![cfg(all(target_arch = "wasm32", target_os = "wasi"))] #![feature(wasi_ext)] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] use std::cell::RefCell; use std::fs::File; diff --git a/lib/llvm-backend-tests/Cargo.toml b/lib/llvm-backend-tests/Cargo.toml new file mode 100644 index 000000000..bb6b24b51 --- /dev/null +++ b/lib/llvm-backend-tests/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "wasmer-llvm-backend-tests" +version = "0.10.2" +authors = ["Nick Lewycky "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +wabt = "0.9.1" +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } +wasmer-runtime = { path = "../runtime", version = "0.11.0" } +wasmer-llvm-backend = { path = "../llvm-backend", version = "0.11.0", features = ["test"] } + +[features] diff --git a/lib/llvm-backend-tests/src/lib.rs b/lib/llvm-backend-tests/src/lib.rs new file mode 100644 index 000000000..ea79b7872 --- /dev/null +++ b/lib/llvm-backend-tests/src/lib.rs @@ -0,0 +1,7 @@ +pub use wabt::wat2wasm; +use wasmer_llvm_backend::LLVMCompiler; +use wasmer_runtime_core::backend::Compiler; + +pub fn get_compiler() -> impl Compiler { + LLVMCompiler::new() +} diff --git a/lib/llvm-backend-tests/tests/compile.rs b/lib/llvm-backend-tests/tests/compile.rs new file mode 100644 index 000000000..17071a44a --- /dev/null +++ b/lib/llvm-backend-tests/tests/compile.rs @@ -0,0 +1,40 @@ +use wasmer_llvm_backend_tests::{get_compiler, wat2wasm}; +use wasmer_runtime::imports; +use wasmer_runtime_core::compile_with; + +#[test] +fn crash_return_with_float_on_stack() { + const MODULE: &str = r#" +(module + (type (;0;) (func)) + (type (;1;) (func (param f64) (result f64))) + (func $_start (type 0)) + (func $fmod (type 1) (param f64) (result f64) + local.get 0 + f64.const 0x0p+0 (;=0;) + f64.mul + return) +) +"#; + let wasm_binary = wat2wasm(MODULE.as_bytes()).expect("WAST not valid or malformed"); + let module = compile_with(&wasm_binary, &get_compiler()).unwrap(); + module.instantiate(&imports! {}).unwrap(); +} + +#[test] +fn crash_select_with_mismatched_pending() { + const MODULE: &str = r#" + (module + (func (param f64) + f64.const 0x0p+0 (;=0;) + local.get 0 + f64.add + f64.const 0x0p+0 (;=0;) + i32.const 0 + select + drop)) +"#; + let wasm_binary = wat2wasm(MODULE.as_bytes()).expect("WAST not valid or malformed"); + let module = compile_with(&wasm_binary, &get_compiler()).unwrap(); + module.instantiate(&imports! {}).unwrap(); +} diff --git a/lib/llvm-backend/Cargo.toml b/lib/llvm-backend/Cargo.toml index 886df698b..b9fa7e6a7 100644 --- a/lib/llvm-backend/Cargo.toml +++ b/lib/llvm-backend/Cargo.toml @@ -1,40 +1,44 @@ [package] name = "wasmer-llvm-backend" -version = "0.6.0" +version = "0.11.0" +license = "MIT" authors = ["The Wasmer Engineering Team "] +repository = "https://github.com/wasmerio/wasmer" +keywords = ["wasm", "webassembly", "compiler", "JIT", "llvm"] +categories = ["wasm"] edition = "2018" readme = "README.md" [dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } -wasmparser = "0.35.1" -smallvec = "0.6.10" +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } +wasmparser = "0.39.1" +smallvec = "0.6" goblin = "0.0.24" libc = "0.2.60" -capstone = { version = "0.6.0", optional = true } +byteorder = "1" [dependencies.inkwell] -git = "https://github.com/wasmerio/inkwell" -branch = "llvm8-0" +git = "https://github.com/TheDan64/inkwell" +rev = "781620e9fa30e51a6e03bd0d49b5f5bb7a782520" default-features = false features = ["llvm8-0", "target-x86"] [target.'cfg(unix)'.dependencies] -nix = "0.15.0" +nix = "0.15" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.7", features = ["memoryapi"] } +winapi = { version = "0.3", features = ["memoryapi"] } [build-dependencies] cc = "1.0" -lazy_static = "1.3.0" -regex = "1.2.1" +lazy_static = "1.4" +regex = "1.2" semver = "0.9" -rustc_version = "0.2.3" +rustc_version = "0.2" [dev-dependencies] wabt = "0.9.1" [features] debug = ["wasmer-runtime-core/debug"] -disasm = ["capstone"] +test = [] diff --git a/lib/llvm-backend/README.md b/lib/llvm-backend/README.md index 08dc5c5d9..584b37585 100644 --- a/lib/llvm-backend/README.md +++ b/lib/llvm-backend/README.md @@ -1,21 +1,21 @@

- Wasmer logo + Wasmer logo

- - Build Status + + Build Status - License + License Join the Wasmer Community - Number of downloads from crates.io + Number of downloads from crates.io Read our API documentation @@ -36,7 +36,7 @@ This crate represents the LLVM backend integration for Wasmer. If you are using the `wasmer` CLI, you can specify the backend with: -```bash +```sh wasmer run program.wasm --backend=llvm ``` diff --git a/lib/llvm-backend/cpp/object_loader.cpp b/lib/llvm-backend/cpp/object_loader.cpp index bf034a308..c4fc6d936 100644 --- a/lib/llvm-backend/cpp/object_loader.cpp +++ b/lib/llvm-backend/cpp/object_loader.cpp @@ -1,161 +1,196 @@ #include "object_loader.hh" #include #include +#include extern "C" void __register_frame(uint8_t *); extern "C" void __deregister_frame(uint8_t *); -struct MemoryManager : llvm::RuntimeDyld::MemoryManager { -public: - MemoryManager(callbacks_t callbacks) : callbacks(callbacks) {} +MemoryManager::~MemoryManager() { + deregisterEHFrames(); + // Deallocate all of the allocated memory. + callbacks.dealloc_memory(code_section.base, code_section.size); + callbacks.dealloc_memory(read_section.base, read_section.size); + callbacks.dealloc_memory(readwrite_section.base, readwrite_section.size); +} +void unwinding_setjmp(jmp_buf stack_out, void (*func)(void *), void *userdata) { + if (setjmp(stack_out)) { - virtual ~MemoryManager() override { - deregisterEHFrames(); - // Deallocate all of the allocated memory. - callbacks.dealloc_memory(code_section.base, code_section.size); - callbacks.dealloc_memory(read_section.base, read_section.size); - callbacks.dealloc_memory(readwrite_section.base, readwrite_section.size); + } else { + func(userdata); } +} - virtual uint8_t *allocateCodeSection(uintptr_t size, unsigned alignment, - unsigned section_id, - llvm::StringRef section_name) override { - return allocate_bump(code_section, code_bump_ptr, size, alignment); +[[noreturn]] void unwinding_longjmp(jmp_buf stack_in) { longjmp(stack_in, 42); } + +struct UnwindPoint { + UnwindPoint *prev; + jmp_buf stack; + std::function *f; + std::unique_ptr exception; +}; + +static thread_local UnwindPoint *unwind_state = nullptr; + +static void unwind_payload(void *_point) { + UnwindPoint *point = (UnwindPoint *)_point; + (*point->f)(); +} + +void catch_unwind(std::function &&f) { + UnwindPoint current; + current.prev = unwind_state; + current.f = &f; + unwind_state = ¤t; + + unwinding_setjmp(current.stack, unwind_payload, (void *)¤t); + + unwind_state = current.prev; + if (current.exception) { + throw std::move(current.exception); } +} - virtual uint8_t *allocateDataSection(uintptr_t size, unsigned alignment, - unsigned section_id, - llvm::StringRef section_name, - bool read_only) override { - // Allocate from the read-only section or the read-write section, depending - // on if this allocation should be read-only or not. - if (read_only) { - return allocate_bump(read_section, read_bump_ptr, size, alignment); - } else { - return allocate_bump(readwrite_section, readwrite_bump_ptr, size, - alignment); +void unsafe_unwind(WasmException *exception) { + UnwindPoint *state = unwind_state; + if (state) { + state->exception.reset(exception); + unwinding_longjmp(state->stack); + } else { + abort(); + } +} + +uint8_t *MemoryManager::allocateCodeSection(uintptr_t size, unsigned alignment, + unsigned section_id, + llvm::StringRef section_name) { + return allocate_bump(code_section, code_bump_ptr, size, alignment); +} + +uint8_t *MemoryManager::allocateDataSection(uintptr_t size, unsigned alignment, + unsigned section_id, + llvm::StringRef section_name, + bool read_only) { + // Allocate from the read-only section or the read-write section, depending + // on if this allocation should be read-only or not. + uint8_t *ret; + if (read_only) { + ret = allocate_bump(read_section, read_bump_ptr, size, alignment); + } else { + ret = allocate_bump(readwrite_section, readwrite_bump_ptr, size, alignment); + } + if (section_name.equals(llvm::StringRef("__llvm_stackmaps")) || + section_name.equals(llvm::StringRef(".llvm_stackmaps"))) { + stack_map_ptr = ret; + stack_map_size = size; + } + return ret; +} + +void MemoryManager::reserveAllocationSpace(uintptr_t code_size, + uint32_t code_align, + uintptr_t read_data_size, + uint32_t read_data_align, + uintptr_t read_write_data_size, + uint32_t read_write_data_align) { + auto aligner = [](uintptr_t ptr, size_t align) { + if (ptr == 0) { + return align; } - } + return (ptr + align - 1) & ~(align - 1); + }; + uint8_t *code_ptr_out = nullptr; + size_t code_size_out = 0; + auto code_result = + callbacks.alloc_memory(aligner(code_size, 4096), PROTECT_READ_WRITE, + &code_ptr_out, &code_size_out); + assert(code_result == RESULT_OK); + code_section = Section{code_ptr_out, code_size_out}; + code_bump_ptr = (uintptr_t)code_ptr_out; + code_start_ptr = (uintptr_t)code_ptr_out; + this->code_size = code_size; - virtual void reserveAllocationSpace(uintptr_t code_size, uint32_t code_align, - uintptr_t read_data_size, - uint32_t read_data_align, - uintptr_t read_write_data_size, - uint32_t read_write_data_align) override { - auto aligner = [](uintptr_t ptr, size_t align) { - if (ptr == 0) { - return align; - } - return (ptr + align - 1) & ~(align - 1); - }; + uint8_t *read_ptr_out = nullptr; + size_t read_size_out = 0; + auto read_result = + callbacks.alloc_memory(aligner(read_data_size, 4096), PROTECT_READ_WRITE, + &read_ptr_out, &read_size_out); + assert(read_result == RESULT_OK); + read_section = Section{read_ptr_out, read_size_out}; + read_bump_ptr = (uintptr_t)read_ptr_out; - uint8_t *code_ptr_out = nullptr; - size_t code_size_out = 0; - auto code_result = - callbacks.alloc_memory(aligner(code_size, 4096), PROTECT_READ_WRITE, - &code_ptr_out, &code_size_out); - assert(code_result == RESULT_OK); - code_section = Section{code_ptr_out, code_size_out}; - code_bump_ptr = (uintptr_t)code_ptr_out; + uint8_t *readwrite_ptr_out = nullptr; + size_t readwrite_size_out = 0; + auto readwrite_result = callbacks.alloc_memory( + aligner(read_write_data_size, 4096), PROTECT_READ_WRITE, + &readwrite_ptr_out, &readwrite_size_out); + assert(readwrite_result == RESULT_OK); + readwrite_section = Section{readwrite_ptr_out, readwrite_size_out}; + readwrite_bump_ptr = (uintptr_t)readwrite_ptr_out; +} - uint8_t *read_ptr_out = nullptr; - size_t read_size_out = 0; - auto read_result = callbacks.alloc_memory(aligner(read_data_size, 4096), - PROTECT_READ_WRITE, &read_ptr_out, - &read_size_out); - assert(read_result == RESULT_OK); - read_section = Section{read_ptr_out, read_size_out}; - read_bump_ptr = (uintptr_t)read_ptr_out; +bool MemoryManager::needsToReserveAllocationSpace() { return true; } - uint8_t *readwrite_ptr_out = nullptr; - size_t readwrite_size_out = 0; - auto readwrite_result = callbacks.alloc_memory( - aligner(read_write_data_size, 4096), PROTECT_READ_WRITE, - &readwrite_ptr_out, &readwrite_size_out); - assert(readwrite_result == RESULT_OK); - readwrite_section = Section{readwrite_ptr_out, readwrite_size_out}; - readwrite_bump_ptr = (uintptr_t)readwrite_ptr_out; - } - - /* Turn on the `reserveAllocationSpace` callback. */ - virtual bool needsToReserveAllocationSpace() override { return true; } - - virtual void registerEHFrames(uint8_t *addr, uint64_t LoadAddr, - size_t size) override { +void MemoryManager::registerEHFrames(uint8_t *addr, uint64_t LoadAddr, + size_t size) { // We don't know yet how to do this on Windows, so we hide this on compilation // so we can compile and pass spectests on unix systems #ifndef _WIN32 - eh_frame_ptr = addr; - eh_frame_size = size; - eh_frames_registered = true; - callbacks.visit_fde(addr, size, __register_frame); + eh_frame_ptr = addr; + eh_frame_size = size; + eh_frames_registered = true; + callbacks.visit_fde(addr, size, __register_frame); #endif - } +} - virtual void deregisterEHFrames() override { +void MemoryManager::deregisterEHFrames() { // We don't know yet how to do this on Windows, so we hide this on compilation // so we can compile and pass spectests on unix systems #ifndef _WIN32 - if (eh_frames_registered) { - callbacks.visit_fde(eh_frame_ptr, eh_frame_size, __deregister_frame); - } -#endif + if (eh_frames_registered) { + callbacks.visit_fde(eh_frame_ptr, eh_frame_size, __deregister_frame); } +#endif +} - virtual bool finalizeMemory(std::string *ErrMsg = nullptr) override { - auto code_result = - callbacks.protect_memory(code_section.base, code_section.size, - mem_protect_t::PROTECT_READ_EXECUTE); - if (code_result != RESULT_OK) { - return false; - } - - auto read_result = callbacks.protect_memory( - read_section.base, read_section.size, mem_protect_t::PROTECT_READ); - if (read_result != RESULT_OK) { - return false; - } - - // The readwrite section is already mapped as read-write. - +bool MemoryManager::finalizeMemory(std::string *ErrMsg) { + auto code_result = + callbacks.protect_memory(code_section.base, code_section.size, + mem_protect_t::PROTECT_READ_EXECUTE); + if (code_result != RESULT_OK) { return false; } - virtual void - notifyObjectLoaded(llvm::RuntimeDyld &RTDyld, - const llvm::object::ObjectFile &Obj) override {} - -private: - struct Section { - uint8_t *base; - size_t size; - }; - - uint8_t *allocate_bump(Section §ion, uintptr_t &bump_ptr, size_t size, - size_t align) { - auto aligner = [](uintptr_t &ptr, size_t align) { - ptr = (ptr + align - 1) & ~(align - 1); - }; - - // Align the bump pointer to the requires alignment. - aligner(bump_ptr, align); - - auto ret_ptr = bump_ptr; - bump_ptr += size; - - assert(bump_ptr <= (uintptr_t)section.base + section.size); - - return (uint8_t *)ret_ptr; + auto read_result = callbacks.protect_memory( + read_section.base, read_section.size, mem_protect_t::PROTECT_READ); + if (read_result != RESULT_OK) { + return false; } - Section code_section, read_section, readwrite_section; - uintptr_t code_bump_ptr, read_bump_ptr, readwrite_bump_ptr; - uint8_t *eh_frame_ptr; - size_t eh_frame_size; - bool eh_frames_registered = false; + // The readwrite section is already mapped as read-write. - callbacks_t callbacks; -}; + return false; +} + +void MemoryManager::notifyObjectLoaded(llvm::RuntimeDyld &RTDyld, + const llvm::object::ObjectFile &Obj) {} + +uint8_t *MemoryManager::allocate_bump(Section §ion, uintptr_t &bump_ptr, + size_t size, size_t align) { + auto aligner = [](uintptr_t &ptr, size_t align) { + ptr = (ptr + align - 1) & ~(align - 1); + }; + + // Align the bump pointer to the requires alignment. + aligner(bump_ptr, align); + + auto ret_ptr = bump_ptr; + bump_ptr += size; + + assert(bump_ptr <= (uintptr_t)section.base + section.size); + + return (uint8_t *)ret_ptr; +} struct SymbolLookup : llvm::JITSymbolResolver { public: @@ -218,3 +253,19 @@ void *WasmModule::get_func(llvm::StringRef name) const { auto symbol = runtime_dyld->getSymbol(name); return (void *)symbol.getAddress(); } + +uint8_t *WasmModule::get_stack_map_ptr() const { + return memory_manager->get_stack_map_ptr(); +} + +size_t WasmModule::get_stack_map_size() const { + return memory_manager->get_stack_map_size(); +} + +uint8_t *WasmModule::get_code_ptr() const { + return memory_manager->get_code_ptr(); +} + +size_t WasmModule::get_code_size() const { + return memory_manager->get_code_size(); +} diff --git a/lib/llvm-backend/cpp/object_loader.hh b/lib/llvm-backend/cpp/object_loader.hh index 7a410b2dc..bc9b9ab67 100644 --- a/lib/llvm-backend/cpp/object_loader.hh +++ b/lib/llvm-backend/cpp/object_loader.hh @@ -1,7 +1,12 @@ +#pragma once + #include #include #include +#include #include +#include +#include #include #include @@ -48,11 +53,92 @@ typedef struct { size_t data, vtable; } box_any_t; -struct WasmException { -public: - virtual std::string description() const noexcept = 0; +enum WasmTrapType { + Unreachable = 0, + IncorrectCallIndirectSignature = 1, + MemoryOutOfBounds = 2, + CallIndirectOOB = 3, + IllegalArithmetic = 4, + Unknown, }; +extern "C" void callback_trampoline(void *, void *); + +struct MemoryManager : llvm::RuntimeDyld::MemoryManager { +public: + MemoryManager(callbacks_t callbacks) : callbacks(callbacks) {} + virtual ~MemoryManager() override; + + inline uint8_t *get_stack_map_ptr() const { return stack_map_ptr; } + inline size_t get_stack_map_size() const { return stack_map_size; } + inline uint8_t *get_code_ptr() const { return (uint8_t *)code_start_ptr; } + inline size_t get_code_size() const { return code_size; } + + virtual uint8_t *allocateCodeSection(uintptr_t size, unsigned alignment, + unsigned section_id, + llvm::StringRef section_name) override; + virtual uint8_t *allocateDataSection(uintptr_t size, unsigned alignment, + unsigned section_id, + llvm::StringRef section_name, + bool read_only) override; + virtual void reserveAllocationSpace(uintptr_t code_size, uint32_t code_align, + uintptr_t read_data_size, + uint32_t read_data_align, + uintptr_t read_write_data_size, + uint32_t read_write_data_align) override; + /* Turn on the `reserveAllocationSpace` callback. */ + virtual bool needsToReserveAllocationSpace() override; + virtual void registerEHFrames(uint8_t *addr, uint64_t LoadAddr, + size_t size) override; + virtual void deregisterEHFrames() override; + virtual bool finalizeMemory(std::string *ErrMsg = nullptr) override; + virtual void notifyObjectLoaded(llvm::RuntimeDyld &RTDyld, + const llvm::object::ObjectFile &Obj) override; + +private: + struct Section { + uint8_t *base; + size_t size; + }; + + uint8_t *allocate_bump(Section §ion, uintptr_t &bump_ptr, size_t size, + size_t align); + + Section code_section, read_section, readwrite_section; + uintptr_t code_start_ptr; + size_t code_size; + uintptr_t code_bump_ptr, read_bump_ptr, readwrite_bump_ptr; + uint8_t *eh_frame_ptr; + size_t eh_frame_size; + bool eh_frames_registered = false; + + callbacks_t callbacks; + + uint8_t *stack_map_ptr = nullptr; + size_t stack_map_size = 0; +}; + +struct WasmErrorSink { + WasmTrapType *trap_out; + box_any_t *user_error; +}; + +struct WasmException : std::exception { +public: + virtual std::string description() const noexcept { return "unknown"; } + + virtual const char *what() const noexcept override { + return "wasm exception"; + } + + virtual void write_error(WasmErrorSink &out) const noexcept { + *out.trap_out = WasmTrapType::Unknown; + } +}; + +void catch_unwind(std::function &&f); +[[noreturn]] void unsafe_unwind(WasmException *exception); + struct UncatchableException : WasmException { public: virtual std::string description() const noexcept override { @@ -70,6 +156,10 @@ public: // The parts of a `Box`. box_any_t error_data; + + virtual void write_error(WasmErrorSink &out) const noexcept override { + *out.user_error = error_data; + } }; struct BreakpointException : UncatchableException { @@ -81,20 +171,35 @@ public: } uintptr_t callback; + + virtual void write_error(WasmErrorSink &out) const noexcept override { + puts("CB TRAMPOLINE"); + callback_trampoline(out.user_error, (void *)callback); + } +}; + +struct WasmModule { +public: + WasmModule(const uint8_t *object_start, size_t object_size, + callbacks_t callbacks); + + void *get_func(llvm::StringRef name) const; + uint8_t *get_stack_map_ptr() const; + size_t get_stack_map_size() const; + uint8_t *get_code_ptr() const; + size_t get_code_size() const; + + bool _init_failed = false; + +private: + std::unique_ptr memory_manager; + std::unique_ptr object_file; + std::unique_ptr runtime_dyld; }; struct WasmTrap : UncatchableException { public: - enum Type { - Unreachable = 0, - IncorrectCallIndirectSignature = 1, - MemoryOutOfBounds = 2, - CallIndirectOOB = 3, - IllegalArithmetic = 4, - Unknown, - }; - - WasmTrap(Type type) : type(type) {} + WasmTrap(WasmTrapType type) : type(type) {} virtual std::string description() const noexcept override { std::ostringstream ss; @@ -103,27 +208,31 @@ public: return ss.str(); } - Type type; + WasmTrapType type; + + virtual void write_error(WasmErrorSink &out) const noexcept override { + *out.trap_out = type; + } private: - friend std::ostream &operator<<(std::ostream &out, const Type &ty) { + friend std::ostream &operator<<(std::ostream &out, const WasmTrapType &ty) { switch (ty) { - case Type::Unreachable: + case WasmTrapType::Unreachable: out << "unreachable"; break; - case Type::IncorrectCallIndirectSignature: + case WasmTrapType::IncorrectCallIndirectSignature: out << "incorrect call_indirect signature"; break; - case Type::MemoryOutOfBounds: + case WasmTrapType::MemoryOutOfBounds: out << "memory access out-of-bounds"; break; - case Type::CallIndirectOOB: + case WasmTrapType::CallIndirectOOB: out << "call_indirect out-of-bounds"; break; - case Type::IllegalArithmetic: + case WasmTrapType::IllegalArithmetic: out << "illegal arithmetic operation"; break; - case Type::Unknown: + case WasmTrapType::Unknown: default: out << "unknown"; break; @@ -145,23 +254,7 @@ public: uint64_t values[1]; }; -struct WasmModule { -public: - WasmModule(const uint8_t *object_start, size_t object_size, - callbacks_t callbacks); - - void *get_func(llvm::StringRef name) const; - - bool _init_failed = false; - -private: - std::unique_ptr memory_manager; - std::unique_ptr object_file; - std::unique_ptr runtime_dyld; -}; - extern "C" { -void callback_trampoline(void *, void *); result_t module_load(const uint8_t *mem_ptr, size_t mem_size, callbacks_t callbacks, WasmModule **module_out) { @@ -174,42 +267,40 @@ result_t module_load(const uint8_t *mem_ptr, size_t mem_size, return RESULT_OK; } -[[noreturn]] void throw_trap(WasmTrap::Type ty) { throw WasmTrap(ty); } +[[noreturn]] void throw_trap(WasmTrapType ty) { + unsafe_unwind(new WasmTrap(ty)); +} void module_delete(WasmModule *module) { delete module; } // Throw a fat pointer that's assumed to be `*mut dyn Any` on the rust // side. [[noreturn]] void throw_any(size_t data, size_t vtable) { - throw UserException(data, vtable); + unsafe_unwind(new UserException(data, vtable)); } // Throw a pointer that's assumed to be codegen::BreakpointHandler on the // rust side. [[noreturn]] void throw_breakpoint(uintptr_t callback) { - throw BreakpointException(callback); + unsafe_unwind(new BreakpointException(callback)); } bool invoke_trampoline(trampoline_t trampoline, void *ctx, void *func, - void *params, void *results, WasmTrap::Type *trap_out, + void *params, void *results, WasmTrapType *trap_out, box_any_t *user_error, void *invoke_env) noexcept { try { - trampoline(ctx, func, params, results); + catch_unwind([trampoline, ctx, func, params, results]() { + trampoline(ctx, func, params, results); + }); return true; - } catch (const WasmTrap &e) { - *trap_out = e.type; - return false; - } catch (const UserException &e) { - *user_error = e.error_data; - return false; - } catch (const BreakpointException &e) { - callback_trampoline(user_error, (void *)e.callback); - return false; - } catch (const WasmException &e) { - *trap_out = WasmTrap::Type::Unknown; + } catch (std::unique_ptr &e) { + WasmErrorSink sink; + sink.trap_out = trap_out; + sink.user_error = user_error; + e->write_error(sink); return false; } catch (...) { - *trap_out = WasmTrap::Type::Unknown; + *trap_out = WasmTrapType::Unknown; return false; } } @@ -217,4 +308,20 @@ bool invoke_trampoline(trampoline_t trampoline, void *ctx, void *func, void *get_func_symbol(WasmModule *module, const char *name) { return module->get_func(llvm::StringRef(name)); } + +const uint8_t *llvm_backend_get_stack_map_ptr(const WasmModule *module) { + return module->get_stack_map_ptr(); +} + +size_t llvm_backend_get_stack_map_size(const WasmModule *module) { + return module->get_stack_map_size(); +} + +const uint8_t *llvm_backend_get_code_ptr(const WasmModule *module) { + return module->get_code_ptr(); +} + +size_t llvm_backend_get_code_size(const WasmModule *module) { + return module->get_code_size(); +} } diff --git a/lib/llvm-backend/src/backend.rs b/lib/llvm-backend/src/backend.rs index 92f0f06a1..a5d262035 100644 --- a/lib/llvm-backend/src/backend.rs +++ b/lib/llvm-backend/src/backend.rs @@ -1,20 +1,24 @@ -use crate::intrinsics::Intrinsics; -use crate::structs::{Callbacks, LLVMModule, LLVMResult, MemProtect}; +use super::stackmap::StackmapRegistry; +use crate::{ + intrinsics::Intrinsics, + structs::{Callbacks, LLVMModule, LLVMResult, MemProtect}, +}; use inkwell::{ memory_buffer::MemoryBuffer, module::Module, - targets::{CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine}, - OptimizationLevel, + targets::{FileType, TargetMachine}, }; use libc::c_char; use std::{ any::Any, + cell::RefCell, ffi::{c_void, CString}, fs::File, io::Write, mem, ops::Deref, ptr::{self, NonNull}, + rc::Rc, slice, str, sync::{Arc, Once}, }; @@ -25,8 +29,9 @@ use wasmer_runtime_core::{ }, cache::Error as CacheError, module::ModuleInfo, + state::ModuleStateMap, structures::TypedIndex, - typed_func::{Wasm, WasmTrapInfo}, + typed_func::{Trampoline, Wasm, WasmTrapInfo}, types::{LocalFuncIndex, SigIndex}, vm, vmcalls, }; @@ -40,6 +45,10 @@ extern "C" { ) -> LLVMResult; fn module_delete(module: *mut LLVMModule); fn get_func_symbol(module: *mut LLVMModule, name: *const c_char) -> *const vm::Func; + fn llvm_backend_get_stack_map_ptr(module: *const LLVMModule) -> *const u8; + fn llvm_backend_get_stack_map_size(module: *const LLVMModule) -> usize; + fn llvm_backend_get_code_ptr(module: *const LLVMModule) -> *const u8; + fn llvm_backend_get_code_size(module: *const LLVMModule) -> usize; fn throw_trap(ty: i32) -> !; fn throw_breakpoint(ty: i64) -> !; @@ -52,7 +61,7 @@ extern "C" { #[allow(improper_ctypes)] fn invoke_trampoline( - trampoline: unsafe extern "C" fn(*mut vm::Ctx, NonNull, *const u64, *mut u64), + trampoline: Trampoline, vmctx_ptr: *mut vm::Ctx, func_ptr: NonNull, params: *const u64, @@ -63,6 +72,8 @@ extern "C" { ) -> bool; } +static SIGNAL_HANDLER_INSTALLED: Once = Once::new(); + fn get_callbacks() -> Callbacks { extern "C" fn alloc_memory( size: usize, @@ -154,33 +165,20 @@ pub struct LLVMBackend { module: *mut LLVMModule, #[allow(dead_code)] buffer: Arc, + msm: Option, + local_func_id_to_offset: Vec, } impl LLVMBackend { - pub fn new(module: Module, _intrinsics: Intrinsics) -> (Self, LLVMCache) { - Target::initialize_x86(&InitializationConfig { - asm_parser: true, - asm_printer: true, - base: true, - disassembler: true, - info: true, - machine_code: true, - }); - let triple = TargetMachine::get_default_triple().to_string(); - let target = Target::from_triple(&triple).unwrap(); - let target_machine = target - .create_target_machine( - &triple, - &TargetMachine::get_host_cpu_name().to_string(), - &TargetMachine::get_host_cpu_features().to_string(), - OptimizationLevel::Aggressive, - RelocMode::PIC, - CodeModel::Default, - ) - .unwrap(); - + pub fn new( + module: Rc>, + _intrinsics: Intrinsics, + _stackmaps: &StackmapRegistry, + _module_info: &ModuleInfo, + target_machine: &TargetMachine, + ) -> (Self, LLVMCache) { let memory_buffer = target_machine - .write_to_memory_buffer(&module, FileType::Object) + .write_to_memory_buffer(&module.borrow_mut(), FileType::Object) .unwrap(); let mem_buf_slice = memory_buffer.as_slice(); @@ -204,22 +202,134 @@ impl LLVMBackend { ) }; - static SIGNAL_HANDLER_INSTALLED: Once = Once::new(); - - SIGNAL_HANDLER_INSTALLED.call_once(|| unsafe { - crate::platform::install_signal_handler(); - }); - if res != LLVMResult::OK { panic!("failed to load object") } let buffer = Arc::new(Buffer::LlvmMemory(memory_buffer)); + #[cfg(all(any(target_os = "linux", target_os = "macos"), target_arch = "x86_64"))] + { + use super::stackmap::{self, StkMapRecord, StkSizeRecord}; + use std::collections::BTreeMap; + + let stackmaps = _stackmaps; + let module_info = _module_info; + + let raw_stackmap = unsafe { + std::slice::from_raw_parts( + llvm_backend_get_stack_map_ptr(module), + llvm_backend_get_stack_map_size(module), + ) + }; + if raw_stackmap.len() > 0 { + let map = stackmap::StackMap::parse(raw_stackmap).unwrap(); + + let (code_ptr, code_size) = unsafe { + ( + llvm_backend_get_code_ptr(module), + llvm_backend_get_code_size(module), + ) + }; + let mut msm = ModuleStateMap { + local_functions: Default::default(), + total_size: code_size, + }; + + let num_local_functions = + module_info.func_assoc.len() - module_info.imported_functions.len(); + let mut local_func_id_to_addr: Vec = Vec::with_capacity(num_local_functions); + + // All local functions. + for index in module_info.imported_functions.len()..module_info.func_assoc.len() { + let name = if cfg!(target_os = "macos") { + format!("_fn{}", index) + } else { + format!("fn{}", index) + }; + + let c_str = CString::new(name).unwrap(); + let ptr = unsafe { get_func_symbol(module, c_str.as_ptr()) }; + + assert!(!ptr.is_null()); + local_func_id_to_addr.push(ptr as usize); + } + + let mut addr_to_size_record: BTreeMap = BTreeMap::new(); + + for record in &map.stk_size_records { + addr_to_size_record.insert(record.function_address as usize, record); + } + + let mut map_records: BTreeMap = BTreeMap::new(); + + for record in &map.stk_map_records { + map_records.insert(record.patchpoint_id as usize, record); + } + + for ((start_id, start_entry), (end_id, end_entry)) in stackmaps + .entries + .iter() + .enumerate() + .step_by(2) + .zip(stackmaps.entries.iter().enumerate().skip(1).step_by(2)) + { + if let Some(map_record) = map_records.get(&start_id) { + assert_eq!(start_id, map_record.patchpoint_id as usize); + assert!(start_entry.is_start); + assert!(!end_entry.is_start); + + let end_record = map_records.get(&end_id); + + let addr = local_func_id_to_addr[start_entry.local_function_id]; + let size_record = *addr_to_size_record + .get(&addr) + .expect("size_record not found"); + + start_entry.populate_msm( + module_info, + code_ptr as usize, + &map, + size_record, + map_record, + end_record.map(|x| (end_entry, *x)), + &mut msm, + ); + } else { + // The record is optimized out. + } + } + + let code_ptr = unsafe { llvm_backend_get_code_ptr(module) } as usize; + let code_len = unsafe { llvm_backend_get_code_size(module) } as usize; + + let local_func_id_to_offset: Vec = local_func_id_to_addr + .iter() + .map(|&x| { + assert!(x >= code_ptr && x < code_ptr + code_len); + x - code_ptr + }) + .collect(); + + return ( + Self { + module, + buffer: Arc::clone(&buffer), + msm: Some(msm), + local_func_id_to_offset, + }, + LLVMCache { buffer }, + ); + } + } + + // Stackmap is not supported on this platform, or this module contains no functions so no stackmaps. ( Self { module, buffer: Arc::clone(&buffer), + msm: None, + local_func_id_to_offset: vec![], }, LLVMCache { buffer }, ) @@ -237,8 +347,6 @@ impl LLVMBackend { return Err("failed to load object".to_string()); } - static SIGNAL_HANDLER_INSTALLED: Once = Once::new(); - SIGNAL_HANDLER_INSTALLED.call_once(|| { crate::platform::install_signal_handler(); }); @@ -249,6 +357,8 @@ impl LLVMBackend { Self { module, buffer: Arc::clone(&buffer), + msm: None, + local_func_id_to_offset: vec![], }, LLVMCache { buffer }, )) @@ -281,12 +391,7 @@ impl RunnableModule for LLVMBackend { } fn get_trampoline(&self, _: &ModuleInfo, sig_index: SigIndex) -> Option { - let trampoline: unsafe extern "C" fn( - *mut vm::Ctx, - NonNull, - *const u64, - *mut u64, - ) = unsafe { + let trampoline: Trampoline = unsafe { let name = if cfg!(target_os = "macos") { format!("_trmp{}", sig_index.index()) } else { @@ -300,9 +405,30 @@ impl RunnableModule for LLVMBackend { mem::transmute(symbol) }; + SIGNAL_HANDLER_INSTALLED.call_once(|| unsafe { + crate::platform::install_signal_handler(); + }); + Some(unsafe { Wasm::from_raw_parts(trampoline, invoke_trampoline, None) }) } + fn get_code(&self) -> Option<&[u8]> { + Some(unsafe { + std::slice::from_raw_parts( + llvm_backend_get_code_ptr(self.module), + llvm_backend_get_code_size(self.module), + ) + }) + } + + fn get_local_function_offsets(&self) -> Option> { + Some(self.local_func_id_to_offset.clone()) + } + + fn get_module_state_map(&self) -> Option { + self.msm.clone() + } + unsafe fn do_early_trap(&self, data: Box) -> ! { throw_any(Box::leak(data)) } @@ -329,33 +455,3 @@ impl CacheGen for LLVMCache { Ok(([].as_ref().into(), memory)) } } - -#[cfg(feature = "disasm")] -unsafe fn disass_ptr(ptr: *const u8, size: usize, inst_count: usize) { - use capstone::arch::BuildsCapstone; - let mut cs = capstone::Capstone::new() // Call builder-pattern - .x86() // X86 architecture - .mode(capstone::arch::x86::ArchMode::Mode64) // 64-bit mode - .detail(true) // Generate extra instruction details - .build() - .expect("Failed to create Capstone object"); - - // Get disassembled instructions - let insns = cs - .disasm_count( - std::slice::from_raw_parts(ptr, size), - ptr as u64, - inst_count, - ) - .expect("Failed to disassemble"); - - println!("count = {}", insns.len()); - for insn in insns.iter() { - println!( - "0x{:x}: {:6} {}", - insn.address(), - insn.mnemonic().unwrap_or(""), - insn.op_str().unwrap_or("") - ); - } -} diff --git a/lib/llvm-backend/src/code.rs b/lib/llvm-backend/src/code.rs index 1f78ccc5b..c0f6e07dd 100644 --- a/lib/llvm-backend/src/code.rs +++ b/lib/llvm-backend/src/code.rs @@ -1,19 +1,34 @@ +use crate::{ + backend::LLVMBackend, + intrinsics::{tbaa_label, CtxType, GlobalCache, Intrinsics, MemoryCache}, + read_info::{blocktype_to_type, type_to_type}, + stackmap::{StackmapEntry, StackmapEntryKind, StackmapRegistry, ValueSemantic}, + state::{ControlFrame, ExtraInfo, IfElseState, State}, + trampolines::generate_trampolines, +}; use inkwell::{ builder::Builder, context::Context, module::{Linkage, Module}, passes::PassManager, + targets::{CodeModel, InitializationConfig, RelocMode, Target, TargetMachine}, types::{BasicType, BasicTypeEnum, FunctionType, PointerType, VectorType}, values::{ BasicValue, BasicValueEnum, FloatValue, FunctionValue, IntValue, PhiValue, PointerValue, VectorValue, }, - AddressSpace, AtomicOrdering, AtomicRMWBinOp, FloatPredicate, IntPredicate, + AddressSpace, AtomicOrdering, AtomicRMWBinOp, FloatPredicate, IntPredicate, OptimizationLevel, }; use smallvec::SmallVec; -use std::sync::{Arc, RwLock}; +use std::{ + cell::RefCell, + collections::HashMap, + mem::ManuallyDrop, + rc::Rc, + sync::{Arc, RwLock}, +}; use wasmer_runtime_core::{ - backend::{Backend, CacheGen, Token}, + backend::{Backend, CacheGen, CompilerConfig, Token}, cache::{Artifact, Error as CacheError}, codegen::*, memory::MemoryType, @@ -25,13 +40,12 @@ use wasmer_runtime_core::{ }; use wasmparser::{BinaryReaderError, MemoryImmediate, Operator, Type as WpType}; -use crate::backend::LLVMBackend; -use crate::intrinsics::{CtxType, GlobalCache, Intrinsics, MemoryCache}; -use crate::read_info::{blocktype_to_type, type_to_type}; -use crate::state::{ControlFrame, IfElseState, State}; -use crate::trampolines::generate_trampolines; - -fn func_sig_to_llvm(context: &Context, intrinsics: &Intrinsics, sig: &FuncSig) -> FunctionType { +fn func_sig_to_llvm<'ctx>( + context: &'ctx Context, + intrinsics: &Intrinsics<'ctx>, + sig: &FuncSig, + type_to_llvm: fn(intrinsics: &Intrinsics<'ctx>, ty: Type) -> BasicTypeEnum<'ctx>, +) -> FunctionType<'ctx> { let user_param_types = sig.params().iter().map(|&ty| type_to_llvm(intrinsics, ty)); let param_types: Vec<_> = std::iter::once(intrinsics.ctx_ptr_ty.as_basic_type_enum()) @@ -54,7 +68,7 @@ fn func_sig_to_llvm(context: &Context, intrinsics: &Intrinsics, sig: &FuncSig) - } } -fn type_to_llvm(intrinsics: &Intrinsics, ty: Type) -> BasicTypeEnum { +fn type_to_llvm<'ctx>(intrinsics: &Intrinsics<'ctx>, ty: Type) -> BasicTypeEnum<'ctx> { match ty { Type::I32 => intrinsics.i32_ty.as_basic_type_enum(), Type::I64 => intrinsics.i64_ty.as_basic_type_enum(), @@ -64,14 +78,22 @@ fn type_to_llvm(intrinsics: &Intrinsics, ty: Type) -> BasicTypeEnum { } } +fn type_to_llvm_int_only<'ctx>(intrinsics: &Intrinsics<'ctx>, ty: Type) -> BasicTypeEnum<'ctx> { + match ty { + Type::I32 | Type::F32 => intrinsics.i32_ty.as_basic_type_enum(), + Type::I64 | Type::F64 => intrinsics.i64_ty.as_basic_type_enum(), + Type::V128 => intrinsics.i128_ty.as_basic_type_enum(), + } +} + // Create a vector where each lane contains the same value. -fn splat_vector( - builder: &Builder, - intrinsics: &Intrinsics, - value: BasicValueEnum, - vec_ty: VectorType, +fn splat_vector<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + vec_ty: VectorType<'ctx>, name: &str, -) -> VectorValue { +) -> VectorValue<'ctx> { // Use insert_element to insert the element into an undef vector, then use // shuffle vector to copy that lane to all lanes. builder.build_shuffle_vector( @@ -85,18 +107,18 @@ fn splat_vector( // Convert floating point vector to integer and saturate when out of range. // TODO: generalize to non-vectors using FloatMathType, IntMathType, etc. for // https://github.com/WebAssembly/nontrapping-float-to-int-conversions/blob/master/proposals/nontrapping-float-to-int-conversion/Overview.md -fn trunc_sat( - builder: &Builder, - intrinsics: &Intrinsics, - fvec_ty: VectorType, - ivec_ty: VectorType, +fn trunc_sat<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + fvec_ty: VectorType<'ctx>, + ivec_ty: VectorType<'ctx>, lower_bound: u64, // Exclusive (lowest representable value) upper_bound: u64, // Exclusive (greatest representable value) int_min_value: u64, int_max_value: u64, - value: IntValue, + value: IntValue<'ctx>, name: &str, -) -> IntValue { +) -> IntValue<'ctx> { // a) Compare vector with itself to identify NaN lanes. // b) Compare vector with splat of inttofp(upper_bound) to identify // lanes that need to saturate to max. @@ -204,11 +226,11 @@ fn trunc_sat( .into_int_value() } -fn trap_if_not_representable_as_int( - builder: &Builder, - intrinsics: &Intrinsics, - context: &Context, - function: &FunctionValue, +fn trap_if_not_representable_as_int<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + context: &'ctx Context, + function: &FunctionValue<'ctx>, lower_bound: u64, // Inclusive (not a trapping value) upper_bound: u64, // Inclusive (not a trapping value) value: FloatValue, @@ -240,8 +262,8 @@ fn trap_if_not_representable_as_int( "out_of_bounds", ); - let failure_block = context.append_basic_block(function, "conversion_failure_block"); - let continue_block = context.append_basic_block(function, "conversion_success_block"); + let failure_block = context.append_basic_block(*function, "conversion_failure_block"); + let continue_block = context.append_basic_block(*function, "conversion_success_block"); builder.build_conditional_branch(out_of_bounds, &failure_block, &continue_block); builder.position_at_end(&failure_block); @@ -254,11 +276,11 @@ fn trap_if_not_representable_as_int( builder.position_at_end(&continue_block); } -fn trap_if_zero_or_overflow( - builder: &Builder, - intrinsics: &Intrinsics, - context: &Context, - function: &FunctionValue, +fn trap_if_zero_or_overflow<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + context: &'ctx Context, + function: &FunctionValue<'ctx>, left: IntValue, right: IntValue, ) { @@ -305,8 +327,8 @@ fn trap_if_zero_or_overflow( .unwrap() .into_int_value(); - let shouldnt_trap_block = context.append_basic_block(function, "shouldnt_trap_block"); - let should_trap_block = context.append_basic_block(function, "should_trap_block"); + let shouldnt_trap_block = context.append_basic_block(*function, "shouldnt_trap_block"); + let should_trap_block = context.append_basic_block(*function, "should_trap_block"); builder.build_conditional_branch(should_trap, &should_trap_block, &shouldnt_trap_block); builder.position_at_end(&should_trap_block); builder.build_call( @@ -318,11 +340,11 @@ fn trap_if_zero_or_overflow( builder.position_at_end(&shouldnt_trap_block); } -fn trap_if_zero( - builder: &Builder, - intrinsics: &Intrinsics, - context: &Context, - function: &FunctionValue, +fn trap_if_zero<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + context: &'ctx Context, + function: &FunctionValue<'ctx>, value: IntValue, ) { let int_type = value.get_type(); @@ -347,8 +369,8 @@ fn trap_if_zero( .unwrap() .into_int_value(); - let shouldnt_trap_block = context.append_basic_block(function, "shouldnt_trap_block"); - let should_trap_block = context.append_basic_block(function, "should_trap_block"); + let shouldnt_trap_block = context.append_basic_block(*function, "shouldnt_trap_block"); + let should_trap_block = context.append_basic_block(*function, "should_trap_block"); builder.build_conditional_branch(should_trap, &should_trap_block, &shouldnt_trap_block); builder.position_at_end(&should_trap_block); builder.build_call( @@ -360,12 +382,161 @@ fn trap_if_zero( builder.position_at_end(&shouldnt_trap_block); } +fn v128_into_int_vec<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + info: ExtraInfo, + int_vec_ty: VectorType<'ctx>, +) -> (VectorValue<'ctx>, ExtraInfo) { + let (value, info) = if info.has_pending_f32_nan() { + let value = builder.build_bitcast(value, intrinsics.f32x4_ty, ""); + ( + canonicalize_nans(builder, intrinsics, value), + info.strip_pending(), + ) + } else if info.has_pending_f64_nan() { + let value = builder.build_bitcast(value, intrinsics.f64x2_ty, ""); + ( + canonicalize_nans(builder, intrinsics, value), + info.strip_pending(), + ) + } else { + (value, info) + }; + ( + builder + .build_bitcast(value, int_vec_ty, "") + .into_vector_value(), + info, + ) +} + +fn v128_into_i8x16<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + info: ExtraInfo, +) -> (VectorValue<'ctx>, ExtraInfo) { + v128_into_int_vec(builder, intrinsics, value, info, intrinsics.i8x16_ty) +} + +fn v128_into_i16x8<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + info: ExtraInfo, +) -> (VectorValue<'ctx>, ExtraInfo) { + v128_into_int_vec(builder, intrinsics, value, info, intrinsics.i16x8_ty) +} + +fn v128_into_i32x4<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + info: ExtraInfo, +) -> (VectorValue<'ctx>, ExtraInfo) { + v128_into_int_vec(builder, intrinsics, value, info, intrinsics.i32x4_ty) +} + +fn v128_into_i64x2<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + info: ExtraInfo, +) -> (VectorValue<'ctx>, ExtraInfo) { + v128_into_int_vec(builder, intrinsics, value, info, intrinsics.i64x2_ty) +} + +// If the value is pending a 64-bit canonicalization, do it now. +// Return a f32x4 vector. +fn v128_into_f32x4<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + info: ExtraInfo, +) -> (VectorValue<'ctx>, ExtraInfo) { + let (value, info) = if info.has_pending_f64_nan() { + let value = builder.build_bitcast(value, intrinsics.f64x2_ty, ""); + ( + canonicalize_nans(builder, intrinsics, value), + info.strip_pending(), + ) + } else { + (value, info) + }; + ( + builder + .build_bitcast(value, intrinsics.f32x4_ty, "") + .into_vector_value(), + info, + ) +} + +// If the value is pending a 32-bit canonicalization, do it now. +// Return a f64x2 vector. +fn v128_into_f64x2<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + info: ExtraInfo, +) -> (VectorValue<'ctx>, ExtraInfo) { + let (value, info) = if info.has_pending_f32_nan() { + let value = builder.build_bitcast(value, intrinsics.f32x4_ty, ""); + ( + canonicalize_nans(builder, intrinsics, value), + info.strip_pending(), + ) + } else { + (value, info) + }; + ( + builder + .build_bitcast(value, intrinsics.f64x2_ty, "") + .into_vector_value(), + info, + ) +} + +fn apply_pending_canonicalization<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, + info: ExtraInfo, +) -> BasicValueEnum<'ctx> { + if info.has_pending_f32_nan() { + if value.get_type().is_vector_type() + || value.get_type() == intrinsics.i128_ty.as_basic_type_enum() + { + let ty = value.get_type(); + let value = builder.build_bitcast(value, intrinsics.f32x4_ty, ""); + let value = canonicalize_nans(builder, intrinsics, value); + builder.build_bitcast(value, ty, "") + } else { + canonicalize_nans(builder, intrinsics, value) + } + } else if info.has_pending_f64_nan() { + if value.get_type().is_vector_type() + || value.get_type() == intrinsics.i128_ty.as_basic_type_enum() + { + let ty = value.get_type(); + let value = builder.build_bitcast(value, intrinsics.f64x2_ty, ""); + let value = canonicalize_nans(builder, intrinsics, value); + builder.build_bitcast(value, ty, "") + } else { + canonicalize_nans(builder, intrinsics, value) + } + } else { + value + } +} + // Replaces any NaN with the canonical QNaN, otherwise leaves the value alone. -fn canonicalize_nans( - builder: &Builder, - intrinsics: &Intrinsics, - value: BasicValueEnum, -) -> BasicValueEnum { +fn canonicalize_nans<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + value: BasicValueEnum<'ctx>, +) -> BasicValueEnum<'ctx> { let f_ty = value.get_type(); let canonicalized = if f_ty.is_vector_type() { let value = value.into_vector_value(); @@ -399,62 +570,110 @@ fn canonicalize_nans( canonicalized } -fn resolve_memory_ptr( - builder: &Builder, - intrinsics: &Intrinsics, - context: &Context, - function: &FunctionValue, - state: &mut State, - ctx: &mut CtxType, +fn resolve_memory_ptr<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + context: &'ctx Context, + module: Rc>>, + function: &FunctionValue<'ctx>, + state: &mut State<'ctx>, + ctx: &mut CtxType<'static, 'ctx>, memarg: &MemoryImmediate, - ptr_ty: PointerType, + ptr_ty: PointerType<'ctx>, value_size: usize, -) -> Result { - // Ignore alignment hint for the time being. - let imm_offset = intrinsics.i64_ty.const_int(memarg.offset as u64, false); - let value_size_v = intrinsics.i64_ty.const_int(value_size as u64, false); - let var_offset_i32 = state.pop1()?.into_int_value(); - let var_offset = - builder.build_int_z_extend(var_offset_i32, intrinsics.i64_ty, &state.var_name()); - let effective_offset = builder.build_int_add(var_offset, imm_offset, &state.var_name()); - let end_offset = builder.build_int_add(effective_offset, value_size_v, &state.var_name()); - let memory_cache = ctx.memory(MemoryIndex::new(0), intrinsics); - - let mem_base_int = match memory_cache { +) -> Result, BinaryReaderError> { + // Look up the memory base (as pointer) and bounds (as unsigned integer). + let memory_cache = ctx.memory(MemoryIndex::new(0), intrinsics, module.clone()); + let (mem_base, mem_bound, minimum, _maximum) = match memory_cache { MemoryCache::Dynamic { ptr_to_base_ptr, ptr_to_bounds, + minimum, + maximum, } => { let base = builder .build_load(ptr_to_base_ptr, "base") .into_pointer_value(); let bounds = builder.build_load(ptr_to_bounds, "bounds").into_int_value(); + tbaa_label( + &module, + intrinsics, + "dynamic_memory_base", + base.as_instruction_value().unwrap(), + Some(0), + ); + tbaa_label( + &module, + intrinsics, + "dynamic_memory_bounds", + bounds.as_instruction_value().unwrap(), + Some(0), + ); + (base, bounds, minimum, maximum) + } + MemoryCache::Static { + base_ptr, + bounds, + minimum, + maximum, + } => (base_ptr, bounds, minimum, maximum), + }; + let mem_base = builder + .build_bitcast(mem_base, intrinsics.i8_ptr_ty, &state.var_name()) + .into_pointer_value(); - let base_as_int = builder.build_ptr_to_int(base, intrinsics.i64_ty, "base_as_int"); + // Compute the offset over the memory_base. + let imm_offset = intrinsics.i64_ty.const_int(memarg.offset as u64, false); + let var_offset_i32 = state.pop1()?.into_int_value(); + let var_offset = + builder.build_int_z_extend(var_offset_i32, intrinsics.i64_ty, &state.var_name()); + let effective_offset = builder.build_int_add(var_offset, imm_offset, &state.var_name()); - let base_in_bounds_1 = builder.build_int_compare( + if let MemoryCache::Dynamic { .. } = memory_cache { + // If the memory is dynamic, do a bounds check. For static we rely on + // the size being a multiple of the page size and hitting a guard page. + let value_size_v = intrinsics.i64_ty.const_int(value_size as u64, false); + let ptr_in_bounds = if effective_offset.is_const() { + let load_offset_end = effective_offset.const_add(value_size_v); + let ptr_in_bounds = load_offset_end.const_int_compare( IntPredicate::ULE, - end_offset, - bounds, - "base_in_bounds_1", + intrinsics.i64_ty.const_int(minimum.bytes().0 as u64, false), ); - let base_in_bounds_2 = builder.build_int_compare( - IntPredicate::ULT, - effective_offset, - end_offset, - "base_in_bounds_2", - ); - let base_in_bounds = - builder.build_and(base_in_bounds_1, base_in_bounds_2, "base_in_bounds"); + if ptr_in_bounds.get_zero_extended_constant() == Some(1) { + Some(ptr_in_bounds) + } else { + None + } + } else { + None + } + .unwrap_or_else(|| { + let load_offset_end = + builder.build_int_add(effective_offset, value_size_v, &state.var_name()); - let base_in_bounds = builder + builder.build_int_compare( + IntPredicate::ULE, + load_offset_end, + mem_bound, + &state.var_name(), + ) + }); + if !ptr_in_bounds.is_constant_int() + || ptr_in_bounds.get_zero_extended_constant().unwrap() != 1 + { + // LLVM may have folded this into 'i1 true' in which case we know + // the pointer is in bounds. LLVM may also have folded it into a + // constant expression, not known to be either true or false yet. + // If it's false, unknown-but-constant, or not-a-constant, emit a + // runtime bounds check. LLVM may yet succeed at optimizing it away. + let ptr_in_bounds = builder .build_call( intrinsics.expect_i1, &[ - base_in_bounds.as_basic_value_enum(), + ptr_in_bounds.as_basic_value_enum(), intrinsics.i1_ty.const_int(1, false).as_basic_value_enum(), ], - "base_in_bounds_expect", + "ptr_in_bounds_expect", ) .try_as_basic_value() .left() @@ -462,10 +681,10 @@ fn resolve_memory_ptr( .into_int_value(); let in_bounds_continue_block = - context.append_basic_block(function, "in_bounds_continue_block"); - let not_in_bounds_block = context.append_basic_block(function, "not_in_bounds_block"); + context.append_basic_block(*function, "in_bounds_continue_block"); + let not_in_bounds_block = context.append_basic_block(*function, "not_in_bounds_block"); builder.build_conditional_branch( - base_in_bounds, + ptr_in_bounds, &in_bounds_continue_block, ¬_in_bounds_block, ); @@ -477,27 +696,106 @@ fn resolve_memory_ptr( ); builder.build_unreachable(); builder.position_at_end(&in_bounds_continue_block); - - base_as_int } - MemoryCache::Static { - base_ptr, - bounds: _, - } => builder.build_ptr_to_int(base_ptr, intrinsics.i64_ty, "base_as_int"), - }; + } - let effective_address_int = - builder.build_int_add(mem_base_int, effective_offset, &state.var_name()); - Ok(builder.build_int_to_ptr(effective_address_int, ptr_ty, &state.var_name())) + let ptr = unsafe { builder.build_gep(mem_base, &[effective_offset], &state.var_name()) }; + Ok(builder + .build_bitcast(ptr, ptr_ty, &state.var_name()) + .into_pointer_value()) } -fn trap_if_misaligned( - builder: &Builder, - intrinsics: &Intrinsics, - context: &Context, - function: &FunctionValue, +fn emit_stack_map<'ctx>( + _module_info: &ModuleInfo, + intrinsics: &Intrinsics<'ctx>, + builder: &Builder<'ctx>, + local_function_id: usize, + target: &mut StackmapRegistry, + kind: StackmapEntryKind, + locals: &[PointerValue], + state: &State<'ctx>, + _ctx: &mut CtxType<'_, 'ctx>, + opcode_offset: usize, +) { + let stackmap_id = target.entries.len(); + + let mut params = Vec::with_capacity(2 + locals.len() + state.stack.len()); + + params.push( + intrinsics + .i64_ty + .const_int(stackmap_id as u64, false) + .as_basic_value_enum(), + ); + params.push(intrinsics.i32_ty.const_int(0, false).as_basic_value_enum()); + + let locals: Vec<_> = locals.iter().map(|x| x.as_basic_value_enum()).collect(); + let mut value_semantics: Vec = + Vec::with_capacity(locals.len() + state.stack.len()); + + params.extend_from_slice(&locals); + value_semantics.extend((0..locals.len()).map(ValueSemantic::WasmLocal)); + + params.extend(state.stack.iter().map(|x| x.0)); + value_semantics.extend((0..state.stack.len()).map(ValueSemantic::WasmStack)); + + // FIXME: Information needed for Abstract -> Runtime state transform is not fully preserved + // to accelerate compilation and reduce memory usage. Check this again when we try to support + // "full" LLVM OSR. + + assert_eq!(params.len(), value_semantics.len() + 2); + + builder.build_call(intrinsics.experimental_stackmap, ¶ms, &state.var_name()); + + target.entries.push(StackmapEntry { + kind, + local_function_id, + local_count: locals.len(), + stack_count: state.stack.len(), + opcode_offset, + value_semantics, + is_start: true, + }); +} + +fn finalize_opcode_stack_map<'ctx>( + intrinsics: &Intrinsics<'ctx>, + builder: &Builder<'ctx>, + local_function_id: usize, + target: &mut StackmapRegistry, + kind: StackmapEntryKind, + opcode_offset: usize, +) { + let stackmap_id = target.entries.len(); + builder.build_call( + intrinsics.experimental_stackmap, + &[ + intrinsics + .i64_ty + .const_int(stackmap_id as u64, false) + .as_basic_value_enum(), + intrinsics.i32_ty.const_int(0, false).as_basic_value_enum(), + ], + "opcode_stack_map_end", + ); + target.entries.push(StackmapEntry { + kind, + local_function_id, + local_count: 0, + stack_count: 0, + opcode_offset, + value_semantics: vec![], + is_start: false, + }); +} + +fn trap_if_misaligned<'ctx>( + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, + context: &'ctx Context, + function: &FunctionValue<'ctx>, memarg: &MemoryImmediate, - ptr: PointerValue, + ptr: PointerValue<'ctx>, ) { let align = match memarg.flags & 3 { 0 => { @@ -529,8 +827,8 @@ fn trap_if_misaligned( .unwrap() .into_int_value(); - let continue_block = context.append_basic_block(function, "aligned_access_continue_block"); - let not_aligned_block = context.append_basic_block(function, "misaligned_trap_block"); + let continue_block = context.append_basic_block(*function, "aligned_access_continue_block"); + let not_aligned_block = context.append_basic_block(*function, "misaligned_trap_block"); builder.build_conditional_branch(aligned, &continue_block, ¬_aligned_block); builder.position_at_end(¬_aligned_block); @@ -564,34 +862,45 @@ pub unsafe extern "C" fn callback_trampoline( } } -pub struct LLVMModuleCodeGenerator { - context: Option, - builder: Option, - intrinsics: Option, - functions: Vec, - signatures: Map, +pub struct LLVMModuleCodeGenerator<'ctx> { + context: Option<&'ctx Context>, + builder: Option>, + intrinsics: Option>, + functions: Vec>, + signatures: Map>, signatures_raw: Map, function_signatures: Option>>, + llvm_functions: Rc>>>, func_import_count: usize, - personality_func: FunctionValue, - module: Module, + personality_func: ManuallyDrop>, + module: ManuallyDrop>>>, + stackmaps: Rc>, + track_state: bool, + target_machine: TargetMachine, } -pub struct LLVMFunctionCodeGenerator { - context: Option, - builder: Option, - intrinsics: Option, - state: State, - function: FunctionValue, +pub struct LLVMFunctionCodeGenerator<'ctx> { + context: Option<&'ctx Context>, + builder: Option>, + alloca_builder: Option>, + intrinsics: Option>, + state: State<'ctx>, + llvm_functions: Rc>>>, + function: FunctionValue<'ctx>, func_sig: FuncSig, - signatures: Map, - locals: Vec, // Contains params and locals + signatures: Map>, + locals: Vec>, // Contains params and locals num_params: usize, - ctx: Option>, + ctx: Option>, unreachable_depth: usize, + stackmaps: Rc>, + index: usize, + opcode_offset: usize, + track_state: bool, + module: Rc>>, } -impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { +impl<'ctx> FunctionCodeGenerator for LLVMFunctionCodeGenerator<'ctx> { fn feed_return(&mut self, _ty: WpType) -> Result<(), CodegenError> { Ok(()) } @@ -600,12 +909,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { Ok(()) } - fn feed_local(&mut self, ty: WpType, n: usize) -> Result<(), CodegenError> { + fn feed_local(&mut self, ty: WpType, count: usize) -> Result<(), CodegenError> { let param_len = self.num_params; - let mut local_idx = 0; - // let (count, ty) = local?; - let count = n; let wasmer_ty = type_to_type(ty)?; let intrinsics = self.intrinsics.as_ref().unwrap(); @@ -620,14 +926,22 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { }; let builder = self.builder.as_ref().unwrap(); + let alloca_builder = self.alloca_builder.as_ref().unwrap(); - for _ in 0..count { - let alloca = builder.build_alloca(ty, &format!("local{}", param_len + local_idx)); - + for local_idx in 0..count { + let alloca = + alloca_builder.build_alloca(ty, &format!("local{}", param_len + local_idx)); builder.build_store(alloca, default_value); - + if local_idx == 0 { + alloca_builder.position_before( + &alloca + .as_instruction() + .unwrap() + .get_next_instruction() + .unwrap(), + ); + } self.locals.push(alloca); - local_idx += 1; } Ok(()) } @@ -637,7 +951,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { .context .as_ref() .unwrap() - .append_basic_block(&self.function, "start_of_code"); + .append_basic_block(self.function, "start_of_code"); let entry_end_inst = self .builder .as_ref() @@ -652,12 +966,40 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { cache_builder.position_before(&entry_end_inst); let module_info = unsafe { ::std::mem::transmute::<&ModuleInfo, &'static ModuleInfo>(module_info) }; - let function = unsafe { - ::std::mem::transmute::<&FunctionValue, &'static FunctionValue>(&self.function) - }; - let ctx = CtxType::new(module_info, function, cache_builder); + let ctx = CtxType::new(module_info, &self.function, cache_builder); self.ctx = Some(ctx); + + { + let state = &mut self.state; + let builder = self.builder.as_ref().unwrap(); + let intrinsics = self.intrinsics.as_ref().unwrap(); + + if self.track_state { + let mut stackmaps = self.stackmaps.borrow_mut(); + emit_stack_map( + &module_info, + &intrinsics, + &builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::FunctionHeader, + &self.locals, + &state, + self.ctx.as_mut().unwrap(), + ::std::usize::MAX, + ); + finalize_opcode_stack_map( + &intrinsics, + &builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::FunctionHeader, + ::std::usize::MAX, + ); + } + } + Ok(()) } @@ -672,9 +1014,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let signatures = &self.signatures; let mut ctx = self.ctx.as_mut().unwrap(); + let mut opcode_offset: Option = None; let op = match event { - Event::Wasm(x) => x, - Event::WasmOwned(ref x) => x, + Event::Wasm(x) => { + opcode_offset = Some(self.opcode_offset); + self.opcode_offset += 1; + x + } Event::Internal(x) => { match x { InternalEvent::FunctionBegin(_) | InternalEvent::FunctionEnd => { @@ -693,22 +1039,39 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { InternalEvent::GetInternal(idx) => { if state.reachable { let idx = idx as usize; - let field_ptr = ctx.internal_field(idx, intrinsics, builder); + let field_ptr = + ctx.internal_field(idx, intrinsics, self.module.clone(), builder); let result = builder.build_load(field_ptr, "get_internal"); + tbaa_label( + &self.module, + intrinsics, + "internal", + result.as_instruction_value().unwrap(), + Some(idx as u32), + ); state.push1(result); } } InternalEvent::SetInternal(idx) => { if state.reachable { let idx = idx as usize; - let field_ptr = ctx.internal_field(idx, intrinsics, builder); + let field_ptr = + ctx.internal_field(idx, intrinsics, self.module.clone(), builder); let v = state.pop1()?; - builder.build_store(field_ptr, v); + let store = builder.build_store(field_ptr, v); + tbaa_label( + &self.module, + intrinsics, + "internal", + store, + Some(idx as u32), + ); } } } return Ok(()); } + Event::WasmOwned(ref x) => x, }; if !state.reachable { @@ -745,7 +1108,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { offset: -1isize as usize, })?; - let end_block = context.append_basic_block(&function, "end"); + let end_block = context.append_basic_block(function, "end"); builder.position_at_end(&end_block); let phis = if let Ok(wasmer_ty) = blocktype_to_type(ty) { @@ -762,8 +1125,8 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder.position_at_end(¤t_block); } Operator::Loop { ty } => { - let loop_body = context.append_basic_block(&function, "loop_body"); - let loop_next = context.append_basic_block(&function, "loop_outer"); + let loop_body = context.append_basic_block(function, "loop_body"); + let loop_next = context.append_basic_block(function, "loop_outer"); builder.build_unconditional_branch(&loop_body); @@ -779,6 +1142,38 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { }; builder.position_at_end(&loop_body); + + if self.track_state { + if let Some(offset) = opcode_offset { + let mut stackmaps = self.stackmaps.borrow_mut(); + emit_stack_map( + &info, + intrinsics, + builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::Loop, + &self.locals, + state, + ctx, + offset, + ); + let signal_mem = ctx.signal_mem(); + let iv = builder + .build_store(signal_mem, context.i8_type().const_int(0 as u64, false)); + // Any 'store' can be made volatile. + iv.set_volatile(true).unwrap(); + finalize_opcode_stack_map( + intrinsics, + builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::Loop, + offset, + ); + } + } + state.push_loop(loop_body, loop_next, phis); } Operator::Br { relative_depth } => { @@ -795,13 +1190,16 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { frame.phis().len() }; - let values = state.peekn(value_len)?; + let values = state.peekn_extra(value_len)?; + let values = values.iter().map(|(v, info)| { + apply_pending_canonicalization(builder, intrinsics, *v, *info) + }); // For each result of the block we're branching to, // pop a value off the value stack and load it into // the corresponding phi. - for (phi, value) in frame.phis().iter().zip(values.iter()) { - phi.add_incoming(&[(value, ¤t_block)]); + for (phi, value) in frame.phis().iter().zip(values) { + phi.add_incoming(&[(&value, ¤t_block)]); } builder.build_unconditional_branch(frame.br_dest()); @@ -824,13 +1222,16 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { frame.phis().len() }; - let param_stack = state.peekn(value_len)?; + let param_stack = state.peekn_extra(value_len)?; + let param_stack = param_stack.iter().map(|(v, info)| { + apply_pending_canonicalization(builder, intrinsics, *v, *info) + }); - for (phi, value) in frame.phis().iter().zip(param_stack.iter()) { - phi.add_incoming(&[(value, ¤t_block)]); + for (phi, value) in frame.phis().iter().zip(param_stack) { + phi.add_incoming(&[(&value, ¤t_block)]); } - let else_block = context.append_basic_block(&function, "else"); + let else_block = context.append_basic_block(function, "else"); let cond_value = builder.build_int_compare( IntPredicate::NE, @@ -854,7 +1255,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let default_frame = state.frame_at_depth(default_depth)?; let args = if default_frame.is_loop() { - &[] + Vec::new() } else { let res_len = default_frame.phis().len(); state.peekn(res_len)? @@ -896,9 +1297,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { message: "not currently in a block", offset: -1isize as usize, })?; - let if_then_block = context.append_basic_block(&function, "if_then"); - let if_else_block = context.append_basic_block(&function, "if_else"); - let end_block = context.append_basic_block(&function, "if_end"); + let if_then_block = context.append_basic_block(function, "if_then"); + let if_else_block = context.append_basic_block(function, "if_else"); + let end_block = context.append_basic_block(function, "if_end"); let end_phis = { builder.position_at_end(&end_block); @@ -933,16 +1334,19 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { Operator::Else => { if state.reachable { let frame = state.frame_at_depth(0)?; - builder.build_unconditional_branch(frame.code_after()); let current_block = builder.get_insert_block().ok_or(BinaryReaderError { message: "not currently in a block", offset: -1isize as usize, })?; for phi in frame.phis().to_vec().iter().rev() { - let value = state.pop1()?; + let (value, info) = state.pop1_extra()?; + let value = + apply_pending_canonicalization(builder, intrinsics, value, info); phi.add_incoming(&[(&value, ¤t_block)]) } + let frame = state.frame_at_depth(0)?; + builder.build_unconditional_branch(frame.code_after()); } let (if_else_block, if_else_state) = if let ControlFrame::IfElse { @@ -970,12 +1374,14 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { })?; if state.reachable { - builder.build_unconditional_branch(frame.code_after()); - for phi in frame.phis().iter().rev() { - let value = state.pop1()?; + let (value, info) = state.pop1_extra()?; + let value = + apply_pending_canonicalization(builder, intrinsics, value, info); phi.add_incoming(&[(&value, ¤t_block)]); } + + builder.build_unconditional_branch(frame.code_after()); } if let ControlFrame::IfElse { @@ -1009,7 +1415,11 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { BasicTypeEnum::FloatType(float_ty) => { float_ty.const_float(0.0).as_basic_value_enum() } - _ => unimplemented!(), + _ => { + return Err(CodegenError { + message: "Operator::End phi type unimplemented".to_string(), + }); + } }; state.push1(placeholder_value); phi.as_instruction().erase_from_basic_block(); @@ -1017,29 +1427,58 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { } } Operator::Return => { - let frame = state.outermost_frame()?; let current_block = builder.get_insert_block().ok_or(BinaryReaderError { message: "not currently in a block", offset: -1isize as usize, })?; - builder.build_unconditional_branch(frame.br_dest()); - - let phis = frame.phis().to_vec(); - - for phi in phis.iter() { - let arg = state.pop1()?; + let frame = state.outermost_frame()?; + for phi in frame.phis().to_vec().iter() { + let (arg, info) = state.pop1_extra()?; + let arg = apply_pending_canonicalization(builder, intrinsics, arg, info); phi.add_incoming(&[(&arg, ¤t_block)]); } + let frame = state.outermost_frame()?; + builder.build_unconditional_branch(frame.br_dest()); + state.reachable = false; } Operator::Unreachable => { // Emit an unreachable instruction. - // If llvm cannot prove that this is never touched, + // If llvm cannot prove that this is never reached, // it will emit a `ud2` instruction on x86_64 arches. + // Comment out this `if` block to allow spectests to pass. + // TODO: fix this + if let Some(offset) = opcode_offset { + if self.track_state { + let mut stackmaps = self.stackmaps.borrow_mut(); + emit_stack_map( + &info, + intrinsics, + builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::Trappable, + &self.locals, + state, + ctx, + offset, + ); + builder.build_call(intrinsics.trap, &[], "trap"); + finalize_opcode_stack_map( + intrinsics, + builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::Trappable, + offset, + ); + } + } + builder.build_call( intrinsics.throw_trap, &[intrinsics.trap_unreachable], @@ -1064,21 +1503,41 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { // Generate const values. Operator::I32Const { value } => { let i = intrinsics.i32_ty.const_int(value as u64, false); - state.push1(i); + let info = if is_f32_arithmetic(value as u32) { + ExtraInfo::arithmetic_f32() + } else { + Default::default() + }; + state.push1_extra(i, info); } Operator::I64Const { value } => { let i = intrinsics.i64_ty.const_int(value as u64, false); - state.push1(i); + let info = if is_f64_arithmetic(value as u64) { + ExtraInfo::arithmetic_f64() + } else { + Default::default() + }; + state.push1_extra(i, info); } Operator::F32Const { value } => { let bits = intrinsics.i32_ty.const_int(value.bits() as u64, false); + let info = if is_f32_arithmetic(value.bits()) { + ExtraInfo::arithmetic_f32() + } else { + Default::default() + }; let f = builder.build_bitcast(bits, intrinsics.f32_ty, "f"); - state.push1(f); + state.push1_extra(f, info); } Operator::F64Const { value } => { let bits = intrinsics.i64_ty.const_int(value.bits(), false); + let info = if is_f64_arithmetic(value.bits()) { + ExtraInfo::arithmetic_f64() + } else { + Default::default() + }; let f = builder.build_bitcast(bits, intrinsics.f64_ty, "f"); - state.push1(f); + state.push1_extra(f, info); } Operator::V128Const { value } => { let mut hi: [u8; 8] = Default::default(); @@ -1087,11 +1546,31 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { lo.copy_from_slice(&value.bytes()[8..16]); let packed = [u64::from_le_bytes(hi), u64::from_le_bytes(lo)]; let i = intrinsics.i128_ty.const_int_arbitrary_precision(&packed); - state.push1(i); + let mut quad1: [u8; 4] = Default::default(); + let mut quad2: [u8; 4] = Default::default(); + let mut quad3: [u8; 4] = Default::default(); + let mut quad4: [u8; 4] = Default::default(); + quad1.copy_from_slice(&value.bytes()[0..4]); + quad2.copy_from_slice(&value.bytes()[4..8]); + quad3.copy_from_slice(&value.bytes()[8..12]); + quad4.copy_from_slice(&value.bytes()[12..16]); + let mut info: ExtraInfo = Default::default(); + if is_f32_arithmetic(u32::from_le_bytes(quad1)) + && is_f32_arithmetic(u32::from_le_bytes(quad2)) + && is_f32_arithmetic(u32::from_le_bytes(quad3)) + && is_f32_arithmetic(u32::from_le_bytes(quad4)) + { + info |= ExtraInfo::arithmetic_f32(); + } + if is_f64_arithmetic(packed[0]) && is_f64_arithmetic(packed[1]) { + info |= ExtraInfo::arithmetic_f64(); + } + state.push1_extra(i, info); } Operator::I8x16Splat => { - let v = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = v.into_int_value(); let v = builder.build_int_truncate(v, intrinsics.i8_ty, ""); let res = splat_vector( builder, @@ -1101,10 +1580,11 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra(res, i); } Operator::I16x8Splat => { - let v = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = v.into_int_value(); let v = builder.build_int_truncate(v, intrinsics.i16_ty, ""); let res = splat_vector( builder, @@ -1114,10 +1594,10 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra(res, i); } Operator::I32x4Splat => { - let v = state.pop1()?; + let (v, i) = state.pop1_extra()?; let res = splat_vector( builder, intrinsics, @@ -1126,10 +1606,10 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra(res, i); } Operator::I64x2Splat => { - let v = state.pop1()?; + let (v, i) = state.pop1_extra()?; let res = splat_vector( builder, intrinsics, @@ -1138,10 +1618,10 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra(res, i); } Operator::F32x4Splat => { - let v = state.pop1()?; + let (v, i) = state.pop1_extra()?; let res = splat_vector( builder, intrinsics, @@ -1150,10 +1630,12 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // The spec is unclear, we interpret splat as preserving NaN + // payload bits. + state.push1_extra(res, i); } Operator::F64x2Splat => { - let v = state.pop1()?; + let (v, i) = state.pop1_extra()?; let res = splat_vector( builder, intrinsics, @@ -1162,46 +1644,74 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // The spec is unclear, we interpret splat as preserving NaN + // payload bits. + state.push1_extra(res, i); } // Operate on locals. Operator::GetLocal { local_index } => { let pointer_value = locals[local_index as usize]; let v = builder.build_load(pointer_value, &state.var_name()); + tbaa_label( + &self.module, + intrinsics, + "local", + v.as_instruction_value().unwrap(), + Some(local_index), + ); state.push1(v); } Operator::SetLocal { local_index } => { let pointer_value = locals[local_index as usize]; - let v = state.pop1()?; - builder.build_store(pointer_value, v); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let store = builder.build_store(pointer_value, v); + tbaa_label(&self.module, intrinsics, "local", store, Some(local_index)); } Operator::TeeLocal { local_index } => { let pointer_value = locals[local_index as usize]; - let v = state.peek1()?; - builder.build_store(pointer_value, v); + let (v, i) = state.peek1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let store = builder.build_store(pointer_value, v); + tbaa_label(&self.module, intrinsics, "local", store, Some(local_index)); } Operator::GetGlobal { global_index } => { let index = GlobalIndex::new(global_index as usize); - let global_cache = ctx.global_cache(index, intrinsics); + let global_cache = ctx.global_cache(index, intrinsics, self.module.clone()); match global_cache { GlobalCache::Const { value } => { state.push1(value); } GlobalCache::Mut { ptr_to_value } => { let value = builder.build_load(ptr_to_value, "global_value"); + tbaa_label( + &self.module, + intrinsics, + "global", + value.as_instruction_value().unwrap(), + Some(global_index), + ); state.push1(value); } } } Operator::SetGlobal { global_index } => { - let value = state.pop1()?; + let (value, info) = state.pop1_extra()?; + let value = apply_pending_canonicalization(builder, intrinsics, value, info); let index = GlobalIndex::new(global_index as usize); - let global_cache = ctx.global_cache(index, intrinsics); + let global_cache = ctx.global_cache(index, intrinsics, self.module.clone()); match global_cache { GlobalCache::Mut { ptr_to_value } => { - builder.build_store(ptr_to_value, value); + let store = builder.build_store(ptr_to_value, value); + tbaa_label( + &self.module, + intrinsics, + "global", + store, + Some(global_index), + ); } GlobalCache::Const { value: _ } => { return Err(CodegenError { @@ -1212,7 +1722,26 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { } Operator::Select => { - let (v1, v2, cond) = state.pop3()?; + let ((v1, i1), (v2, i2), (cond, _)) = state.pop3_extra()?; + // We don't bother canonicalizing 'cond' here because we only + // compare it to zero, and that's invariant under + // canonicalization. + + // If the pending bits of v1 and v2 are the same, we can pass + // them along to the result. Otherwise, apply pending + // canonicalizations now. + let (v1, i1, v2, i2) = if i1.has_pending_f32_nan() != i2.has_pending_f32_nan() + || i1.has_pending_f64_nan() != i2.has_pending_f64_nan() + { + ( + apply_pending_canonicalization(builder, intrinsics, v1, i1), + i1.strip_pending(), + apply_pending_canonicalization(builder, intrinsics, v2, i2), + i2.strip_pending(), + ) + } else { + (v1, i1, v2, i2) + }; let cond_value = builder.build_int_compare( IntPredicate::NE, cond.into_int_value(), @@ -1220,7 +1749,19 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_select(cond_value, v1, v2, &state.var_name()); - state.push1(res); + let info = { + let mut info = i1.strip_pending() & i2.strip_pending(); + if i1.has_pending_f32_nan() { + debug_assert!(i2.has_pending_f32_nan()); + info |= ExtraInfo::pending_f32_nan(); + } + if i1.has_pending_f64_nan() { + debug_assert!(i2.has_pending_f64_nan()); + info |= ExtraInfo::pending_f64_nan(); + } + info + }; + state.push1_extra(res, info); } Operator::Call { function_index } => { let func_index = FuncIndex::new(function_index as usize); @@ -1228,45 +1769,129 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let llvm_sig = signatures[sigindex]; let func_sig = &info.signatures[sigindex]; - let call_site = match func_index.local_or_import(info) { - LocalOrImport::Local(local_func_index) => { - let params: Vec<_> = [ctx.basic()] - .iter() - .chain(state.peekn(func_sig.params().len())?.iter()) - .map(|v| *v) + let (params, func_ptr) = match func_index.local_or_import(info) { + LocalOrImport::Local(_) => { + let params: Vec<_> = std::iter::once(ctx.basic()) + .chain( + state + .peekn_extra(func_sig.params().len())? + .iter() + .enumerate() + .map(|(i, (v, info))| match func_sig.params()[i] { + Type::F32 => builder.build_bitcast( + apply_pending_canonicalization( + builder, intrinsics, *v, *info, + ), + intrinsics.i32_ty, + &state.var_name(), + ), + Type::F64 => builder.build_bitcast( + apply_pending_canonicalization( + builder, intrinsics, *v, *info, + ), + intrinsics.i64_ty, + &state.var_name(), + ), + Type::V128 => apply_pending_canonicalization( + builder, intrinsics, *v, *info, + ), + _ => *v, + }), + ) .collect(); - let func_ptr = - ctx.local_func(local_func_index, llvm_sig, intrinsics, builder); + let func_ptr = self.llvm_functions.borrow_mut()[&func_index]; - builder.build_call(func_ptr, ¶ms, &state.var_name()) + (params, func_ptr.as_global_value().as_pointer_value()) } LocalOrImport::Import(import_func_index) => { let (func_ptr_untyped, ctx_ptr) = - ctx.imported_func(import_func_index, intrinsics); - let params: Vec<_> = [ctx_ptr.as_basic_value_enum()] - .iter() - .chain(state.peekn(func_sig.params().len())?.iter()) - .map(|v| *v) + ctx.imported_func(import_func_index, intrinsics, self.module.clone()); + + let params: Vec<_> = std::iter::once(ctx_ptr.as_basic_value_enum()) + .chain( + state + .peekn_extra(func_sig.params().len())? + .iter() + .enumerate() + .map(|(i, (v, info))| match func_sig.params()[i] { + Type::F32 => builder.build_bitcast( + apply_pending_canonicalization( + builder, intrinsics, *v, *info, + ), + intrinsics.i32_ty, + &state.var_name(), + ), + Type::F64 => builder.build_bitcast( + apply_pending_canonicalization( + builder, intrinsics, *v, *info, + ), + intrinsics.i64_ty, + &state.var_name(), + ), + Type::V128 => apply_pending_canonicalization( + builder, intrinsics, *v, *info, + ), + _ => *v, + }), + ) .collect(); let func_ptr_ty = llvm_sig.ptr_type(AddressSpace::Generic); - let func_ptr = builder.build_pointer_cast( func_ptr_untyped, func_ptr_ty, "typed_func_ptr", ); - builder.build_call(func_ptr, ¶ms, &state.var_name()) + (params, func_ptr) } }; state.popn(func_sig.params().len())?; + if self.track_state { + if let Some(offset) = opcode_offset { + let mut stackmaps = self.stackmaps.borrow_mut(); + emit_stack_map( + &info, + intrinsics, + builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::Call, + &self.locals, + state, + ctx, + offset, + ) + } + } + let call_site = builder.build_call(func_ptr, ¶ms, &state.var_name()); + if self.track_state { + if let Some(offset) = opcode_offset { + let mut stackmaps = self.stackmaps.borrow_mut(); + finalize_opcode_stack_map( + intrinsics, + builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::Call, + offset, + ) + } + } if let Some(basic_value) = call_site.try_as_basic_value().left() { match func_sig.returns().len() { - 1 => state.push1(basic_value), + 1 => state.push1(match func_sig.returns()[0] { + Type::F32 => { + builder.build_bitcast(basic_value, intrinsics.f32_ty, "ret_cast") + } + Type::F64 => { + builder.build_bitcast(basic_value, intrinsics.f64_ty, "ret_cast") + } + _ => basic_value, + }), count @ _ => { // This is a multi-value return. let struct_value = basic_value.into_struct_value(); @@ -1283,8 +1908,12 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { Operator::CallIndirect { index, table_index } => { let sig_index = SigIndex::new(index as usize); let expected_dynamic_sigindex = ctx.dynamic_sigindex(sig_index, intrinsics); - let (table_base, table_bound) = - ctx.table(TableIndex::new(table_index as usize), intrinsics, builder); + let (table_base, table_bound) = ctx.table( + TableIndex::new(table_index as usize), + intrinsics, + self.module.clone(), + builder, + ); let func_index = state.pop1()?.into_int_value(); // We assume the table has the `anyfunc` element type. @@ -1353,9 +1982,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { .into_int_value(); let in_bounds_continue_block = - context.append_basic_block(&function, "in_bounds_continue_block"); + context.append_basic_block(function, "in_bounds_continue_block"); let not_in_bounds_block = - context.append_basic_block(&function, "not_in_bounds_block"); + context.append_basic_block(function, "not_in_bounds_block"); builder.build_conditional_branch( index_in_bounds, &in_bounds_continue_block, @@ -1394,9 +2023,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { .unwrap() .into_int_value(); - let continue_block = context.append_basic_block(&function, "continue_block"); + let continue_block = context.append_basic_block(function, "continue_block"); let sigindices_notequal_block = - context.append_basic_block(&function, "sigindices_notequal_block"); + context.append_basic_block(function, "sigindices_notequal_block"); builder.build_conditional_branch( sigindices_equal, &continue_block, @@ -1415,10 +2044,27 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let wasmer_fn_sig = &info.signatures[sig_index]; let fn_ty = signatures[sig_index]; - let pushed_args = state.popn_save(wasmer_fn_sig.params().len())?; + let pushed_args = state.popn_save_extra(wasmer_fn_sig.params().len())?; let args: Vec<_> = std::iter::once(ctx_ptr) - .chain(pushed_args.into_iter()) + .chain(pushed_args.into_iter().enumerate().map(|(i, (v, info))| { + match wasmer_fn_sig.params()[i] { + Type::F32 => builder.build_bitcast( + apply_pending_canonicalization(builder, intrinsics, v, info), + intrinsics.i32_ty, + &state.var_name(), + ), + Type::F64 => builder.build_bitcast( + apply_pending_canonicalization(builder, intrinsics, v, info), + intrinsics.i64_ty, + &state.var_name(), + ), + Type::V128 => { + apply_pending_canonicalization(builder, intrinsics, v, info) + } + _ => v, + } + })) .collect(); let typed_func_ptr = builder.build_pointer_cast( @@ -1427,15 +2073,58 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { "typed_func_ptr", ); + if self.track_state { + if let Some(offset) = opcode_offset { + let mut stackmaps = self.stackmaps.borrow_mut(); + emit_stack_map( + &info, + intrinsics, + builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::Call, + &self.locals, + state, + ctx, + offset, + ) + } + } let call_site = builder.build_call(typed_func_ptr, &args, "indirect_call"); + if self.track_state { + if let Some(offset) = opcode_offset { + let mut stackmaps = self.stackmaps.borrow_mut(); + finalize_opcode_stack_map( + intrinsics, + builder, + self.index, + &mut *stackmaps, + StackmapEntryKind::Call, + offset, + ) + } + } match wasmer_fn_sig.returns() { [] => {} [_] => { let value = call_site.try_as_basic_value().left().unwrap(); - state.push1(value); + state.push1(match wasmer_fn_sig.returns()[0] { + Type::F32 => { + builder.build_bitcast(value, intrinsics.f32_ty, "ret_cast") + } + Type::F64 => { + builder.build_bitcast(value, intrinsics.f64_ty, "ret_cast") + } + _ => value, + }); + } + _ => { + return Err(CodegenError { + message: "Operator::CallIndirect multi-value returns unimplemented" + .to_string(), + }); } - _ => unimplemented!("multi-value returns"), } } @@ -1444,67 +2133,55 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { * https://github.com/sunfishcode/wasm-reference-manual/blob/master/WebAssembly.md#integer-arithmetic-instructions ***************************/ Operator::I32Add | Operator::I64Add => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let res = builder.build_int_add(v1, v2, &state.var_name()); state.push1(res); } Operator::I8x16Add => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_add(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8Add => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_add(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4Add => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_add(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I64x2Add => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder - .build_bitcast(v1, intrinsics.i64x2_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i64x2(builder, intrinsics, v2, i2); let res = builder.build_int_add(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I8x16AddSaturateS => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder.build_bitcast(v1, intrinsics.i8x16_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.i8x16_ty, ""); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder - .build_call(intrinsics.sadd_sat_i8x16, &[v1, v2], &state.var_name()) + .build_call( + intrinsics.sadd_sat_i8x16, + &[v1.as_basic_value_enum(), v2.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -1512,12 +2189,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8AddSaturateS => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder.build_bitcast(v1, intrinsics.i16x8_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.i16x8_ty, ""); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder - .build_call(intrinsics.sadd_sat_i16x8, &[v1, v2], &state.var_name()) + .build_call( + intrinsics.sadd_sat_i16x8, + &[v1.as_basic_value_enum(), v2.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -1525,12 +2205,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I8x16AddSaturateU => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder.build_bitcast(v1, intrinsics.i8x16_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.i8x16_ty, ""); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder - .build_call(intrinsics.uadd_sat_i8x16, &[v1, v2], &state.var_name()) + .build_call( + intrinsics.uadd_sat_i8x16, + &[v1.as_basic_value_enum(), v2.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -1538,12 +2221,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8AddSaturateU => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder.build_bitcast(v1, intrinsics.i16x8_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.i16x8_ty, ""); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder - .build_call(intrinsics.uadd_sat_i16x8, &[v1, v2], &state.var_name()) + .build_call( + intrinsics.uadd_sat_i16x8, + &[v1.as_basic_value_enum(), v2.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -1551,70 +2237,55 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32Sub | Operator::I64Sub => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let res = builder.build_int_sub(v1, v2, &state.var_name()); state.push1(res); } Operator::I8x16Sub => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_sub(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8Sub => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_sub(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4Sub => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_sub(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I64x2Sub => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder - .build_bitcast(v1, intrinsics.i64x2_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i64x2(builder, intrinsics, v2, i2); let res = builder.build_int_sub(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I8x16SubSaturateS => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder.build_bitcast(v1, intrinsics.i8x16_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.i8x16_ty, ""); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder - .build_call(intrinsics.ssub_sat_i8x16, &[v1, v2], &state.var_name()) + .build_call( + intrinsics.ssub_sat_i8x16, + &[v1.as_basic_value_enum(), v2.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -1622,12 +2293,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8SubSaturateS => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder.build_bitcast(v1, intrinsics.i16x8_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.i16x8_ty, ""); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder - .build_call(intrinsics.ssub_sat_i16x8, &[v1, v2], &state.var_name()) + .build_call( + intrinsics.ssub_sat_i16x8, + &[v1.as_basic_value_enum(), v2.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -1635,12 +2309,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I8x16SubSaturateU => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder.build_bitcast(v1, intrinsics.i8x16_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.i8x16_ty, ""); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder - .build_call(intrinsics.usub_sat_i8x16, &[v1, v2], &state.var_name()) + .build_call( + intrinsics.usub_sat_i8x16, + &[v1.as_basic_value_enum(), v2.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -1648,12 +2325,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8SubSaturateU => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder.build_bitcast(v1, intrinsics.i16x8_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.i16x8_ty, ""); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder - .build_call(intrinsics.usub_sat_i16x8, &[v1, v2], &state.var_name()) + .build_call( + intrinsics.usub_sat_i16x8, + &[v1.as_basic_value_enum(), v2.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -1661,52 +2341,41 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32Mul | Operator::I64Mul => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let res = builder.build_int_mul(v1, v2, &state.var_name()); state.push1(res); } Operator::I8x16Mul => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_mul(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8Mul => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_mul(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4Mul => { - let (v1, v2) = state.pop2()?; - let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_mul(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32DivS | Operator::I64DivS => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); trap_if_zero_or_overflow(builder, intrinsics, context, &function, v1, v2); @@ -1715,7 +2384,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32DivU | Operator::I64DivU => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); trap_if_zero(builder, intrinsics, context, &function, v2); @@ -1724,7 +2395,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32RemS | Operator::I64RemS => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let int_type = v1.get_type(); let (min_value, neg_one_value) = if int_type == intrinsics.i32_ty { @@ -1769,7 +2442,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32RemU | Operator::I64RemU => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); trap_if_zero(builder, intrinsics, context, &function, v2); @@ -1778,25 +2453,34 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32And | Operator::I64And | Operator::V128And => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let res = builder.build_and(v1, v2, &state.var_name()); state.push1(res); } Operator::I32Or | Operator::I64Or | Operator::V128Or => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let res = builder.build_or(v1, v2, &state.var_name()); state.push1(res); } Operator::I32Xor | Operator::I64Xor | Operator::V128Xor => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let res = builder.build_xor(v1, v2, &state.var_name()); state.push1(res); } Operator::V128Bitselect => { - let (v1, v2, cond) = state.pop3()?; + let ((v1, i1), (v2, i2), (cond, cond_info)) = state.pop3_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); + let cond = apply_pending_canonicalization(builder, intrinsics, cond, cond_info); let v1 = builder .build_bitcast(v1, intrinsics.i1x128_ty, "") .into_vector_value(); @@ -1811,17 +2495,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32Shl | Operator::I64Shl => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); // TODO: missing 'and' of v2? let res = builder.build_left_shift(v1, v2, &state.var_name()); state.push1(res); } Operator::I8x16Shl => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(7, false), ""); let v2 = builder.build_int_truncate(v2, intrinsics.i8_ty, ""); @@ -1837,10 +2522,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8Shl => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(15, false), ""); let v2 = builder.build_int_truncate(v2, intrinsics.i16_ty, ""); @@ -1856,10 +2540,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32x4Shl => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(31, false), ""); let v2 = splat_vector( @@ -1874,10 +2557,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I64x2Shl => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i64x2(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(63, false), ""); let v2 = builder.build_int_z_extend(v2, intrinsics.i64_ty, ""); @@ -1893,17 +2575,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32ShrS | Operator::I64ShrS => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); // TODO: check wasm spec, is this missing v2 mod LaneBits? let res = builder.build_right_shift(v1, v2, true, &state.var_name()); state.push1(res); } Operator::I8x16ShrS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(7, false), ""); let v2 = builder.build_int_truncate(v2, intrinsics.i8_ty, ""); @@ -1919,10 +2602,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8ShrS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(15, false), ""); let v2 = builder.build_int_truncate(v2, intrinsics.i16_ty, ""); @@ -1938,10 +2620,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32x4ShrS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(31, false), ""); let v2 = splat_vector( @@ -1956,10 +2637,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I64x2ShrS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i64x2(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(63, false), ""); let v2 = builder.build_int_z_extend(v2, intrinsics.i64_ty, ""); @@ -1975,16 +2655,17 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32ShrU | Operator::I64ShrU => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let res = builder.build_right_shift(v1, v2, false, &state.var_name()); state.push1(res); } Operator::I8x16ShrU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(7, false), ""); let v2 = builder.build_int_truncate(v2, intrinsics.i8_ty, ""); @@ -2000,10 +2681,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8ShrU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(15, false), ""); let v2 = builder.build_int_truncate(v2, intrinsics.i16_ty, ""); @@ -2019,10 +2699,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32x4ShrU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(31, false), ""); let v2 = splat_vector( @@ -2037,10 +2716,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I64x2ShrU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i64x2(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); let v2 = builder.build_and(v2, intrinsics.i32_ty.const_int(63, false), ""); let v2 = builder.build_int_z_extend(v2, intrinsics.i64_ty, ""); @@ -2056,7 +2734,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32Rotl => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let lhs = builder.build_left_shift(v1, v2, &state.var_name()); let rhs = { @@ -2068,7 +2748,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I64Rotl => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let lhs = builder.build_left_shift(v1, v2, &state.var_name()); let rhs = { @@ -2080,7 +2762,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32Rotr => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let lhs = builder.build_right_shift(v1, v2, false, &state.var_name()); let rhs = { @@ -2092,7 +2776,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I64Rotr => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let lhs = builder.build_right_shift(v1, v2, false, &state.var_name()); let rhs = { @@ -2104,90 +2790,84 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32Clz => { - let input = state.pop1()?; - let ensure_defined_zero = intrinsics - .i1_ty - .const_int(1 as u64, false) - .as_basic_value_enum(); + let (input, info) = state.pop1_extra()?; + let input = apply_pending_canonicalization(builder, intrinsics, input, info); + let is_zero_undef = intrinsics.i1_zero.as_basic_value_enum(); let res = builder .build_call( intrinsics.ctlz_i32, - &[input, ensure_defined_zero], + &[input, is_zero_undef], &state.var_name(), ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::I64Clz => { - let input = state.pop1()?; - let ensure_defined_zero = intrinsics - .i1_ty - .const_int(1 as u64, false) - .as_basic_value_enum(); + let (input, info) = state.pop1_extra()?; + let input = apply_pending_canonicalization(builder, intrinsics, input, info); + let is_zero_undef = intrinsics.i1_zero.as_basic_value_enum(); let res = builder .build_call( intrinsics.ctlz_i64, - &[input, ensure_defined_zero], + &[input, is_zero_undef], &state.var_name(), ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } Operator::I32Ctz => { - let input = state.pop1()?; - let ensure_defined_zero = intrinsics - .i1_ty - .const_int(1 as u64, false) - .as_basic_value_enum(); + let (input, info) = state.pop1_extra()?; + let input = apply_pending_canonicalization(builder, intrinsics, input, info); + let is_zero_undef = intrinsics.i1_zero.as_basic_value_enum(); let res = builder .build_call( intrinsics.cttz_i32, - &[input, ensure_defined_zero], + &[input, is_zero_undef], &state.var_name(), ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::I64Ctz => { - let input = state.pop1()?; - let ensure_defined_zero = intrinsics - .i1_ty - .const_int(1 as u64, false) - .as_basic_value_enum(); + let (input, info) = state.pop1_extra()?; + let input = apply_pending_canonicalization(builder, intrinsics, input, info); + let is_zero_undef = intrinsics.i1_zero.as_basic_value_enum(); let res = builder .build_call( intrinsics.cttz_i64, - &[input, ensure_defined_zero], + &[input, is_zero_undef], &state.var_name(), ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } Operator::I32Popcnt => { - let input = state.pop1()?; + let (input, info) = state.pop1_extra()?; + let input = apply_pending_canonicalization(builder, intrinsics, input, info); let res = builder .build_call(intrinsics.ctpop_i32, &[input], &state.var_name()) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::I64Popcnt => { - let input = state.pop1()?; + let (input, info) = state.pop1_extra()?; + let input = apply_pending_canonicalization(builder, intrinsics, input, info); let res = builder .build_call(intrinsics.ctpop_i64, &[input], &state.var_name()) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } Operator::I32Eqz => { let input = state.pop1()?.into_int_value(); @@ -2198,7 +2878,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::I64Eqz => { let input = state.pop1()?.into_int_value(); @@ -2209,132 +2889,160 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } /*************************** * Floating-Point Arithmetic instructions. * https://github.com/sunfishcode/wasm-reference-manual/blob/master/WebAssembly.md#floating-point-arithmetic-instructions ***************************/ - Operator::F32Add | Operator::F64Add => { - let (v1, v2) = state.pop2()?; - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); + Operator::F32Add => { + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); let res = builder.build_float_add(v1, v2, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f32_nan(), + ); + } + Operator::F64Add => { + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); + let res = builder.build_float_add(v1, v2, &state.var_name()); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f64_nan(), + ); } Operator::F32x4Add => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f32x4_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f32x4_ty, ""); - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); - let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, i2) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_add(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f32_nan(), + ); } Operator::F64x2Add => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f64x2_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f64x2_ty, ""); - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); - let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, i2) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_add(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f64_nan(), + ); } - Operator::F32Sub | Operator::F64Sub => { - let (v1, v2) = state.pop2()?; - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); + Operator::F32Sub => { + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); let res = builder.build_float_sub(v1, v2, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f32_nan(), + ); + } + Operator::F64Sub => { + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); + let res = builder.build_float_sub(v1, v2, &state.var_name()); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f64_nan(), + ); } Operator::F32x4Sub => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f32x4_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f32x4_ty, ""); - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); - let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, i2) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_sub(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f32_nan(), + ); } Operator::F64x2Sub => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f64x2_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f64x2_ty, ""); - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); - let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, i2) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_sub(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f64_nan(), + ); } - Operator::F32Mul | Operator::F64Mul => { - let (v1, v2) = state.pop2()?; - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); + Operator::F32Mul => { + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); let res = builder.build_float_mul(v1, v2, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f32_nan(), + ); + } + Operator::F64Mul => { + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); + let res = builder.build_float_mul(v1, v2, &state.var_name()); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f64_nan(), + ); } Operator::F32x4Mul => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f32x4_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f32x4_ty, ""); - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); - let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, i2) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_mul(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f32_nan(), + ); } Operator::F64x2Mul => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f64x2_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f64x2_ty, ""); - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); - let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, i2) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_mul(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra( + res, + (i1.strip_pending() & i2.strip_pending()) | ExtraInfo::pending_f64_nan(), + ); } - Operator::F32Div | Operator::F64Div => { + Operator::F32Div => { let (v1, v2) = state.pop2()?; - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); let res = builder.build_float_div(v1, v2, &state.var_name()); - state.push1(res); + state.push1_extra(res, ExtraInfo::pending_f32_nan()); + } + Operator::F64Div => { + let (v1, v2) = state.pop2()?; + let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); + let res = builder.build_float_div(v1, v2, &state.var_name()); + state.push1_extra(res, ExtraInfo::pending_f64_nan()); } Operator::F32x4Div => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f32x4_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f32x4_ty, ""); - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); - let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_div(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra(res, ExtraInfo::pending_f32_nan()); } Operator::F64x2Div => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f64x2_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f64x2_ty, ""); - let v1 = canonicalize_nans(builder, intrinsics, v1); - let v2 = canonicalize_nans(builder, intrinsics, v2); - let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_div(v1, v2, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra(res, ExtraInfo::pending_f64_nan()); } Operator::F32Sqrt => { let input = state.pop1()?; @@ -2343,7 +3051,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, ExtraInfo::pending_f32_nan()); } Operator::F64Sqrt => { let input = state.pop1()?; @@ -2352,24 +3060,32 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, ExtraInfo::pending_f64_nan()); } Operator::F32x4Sqrt => { - let input = state.pop1()?.into_int_value(); - let float = builder.build_bitcast(input, intrinsics.f32x4_ty, "float"); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_f32x4(builder, intrinsics, v, i); let res = builder - .build_call(intrinsics.sqrt_f32x4, &[float], &state.var_name()) + .build_call( + intrinsics.sqrt_f32x4, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); let bits = builder.build_bitcast(res, intrinsics.i128_ty, "bits"); - state.push1(bits); + state.push1_extra(bits, ExtraInfo::pending_f32_nan()); } Operator::F64x2Sqrt => { - let input = state.pop1()?.into_int_value(); - let float = builder.build_bitcast(input, intrinsics.f64x2_ty, "float"); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_f64x2(builder, intrinsics, v, i); let res = builder - .build_call(intrinsics.sqrt_f64x2, &[float], &state.var_name()) + .build_call( + intrinsics.sqrt_f64x2, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); @@ -2377,241 +3093,689 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(bits); } Operator::F32Min => { + // This implements the same logic as LLVM's @llvm.minimum + // intrinsic would, but x86 lowering of that intrinsic + // encounters a fatal error in LLVM 8 and LLVM 9. let (v1, v2) = state.pop2()?; - let res = builder - .build_call(intrinsics.minimum_f32, &[v1, v2], &state.var_name()) - .try_as_basic_value() - .left() - .unwrap(); - state.push1(res); + + // To detect min(-0.0, 0.0), we check whether the integer + // representations are equal. There's one other case where that + // can happen: non-canonical NaNs. Here we unconditionally + // canonicalize the NaNs. + let v1 = canonicalize_nans(builder, intrinsics, v1); + let v2 = canonicalize_nans(builder, intrinsics, v2); + + let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); + let v1_is_nan = builder.build_float_compare( + FloatPredicate::UNO, + v1, + intrinsics.f32_zero, + "nan", + ); + let v2_is_not_nan = builder.build_float_compare( + FloatPredicate::ORD, + v2, + intrinsics.f32_zero, + "notnan", + ); + let v1_repr = builder + .build_bitcast(v1, intrinsics.i32_ty, "") + .into_int_value(); + let v2_repr = builder + .build_bitcast(v2, intrinsics.i32_ty, "") + .into_int_value(); + let repr_ne = builder.build_int_compare(IntPredicate::NE, v1_repr, v2_repr, ""); + let float_eq = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); + let min_cmp = builder.build_float_compare(FloatPredicate::OLT, v1, v2, ""); + let negative_zero = intrinsics.f32_ty.const_float(-0.0); + let v2 = builder + .build_select( + builder.build_and( + builder.build_and(float_eq, repr_ne, ""), + v2_is_not_nan, + "", + ), + negative_zero, + v2, + "", + ) + .into_float_value(); + let res = + builder.build_select(builder.build_or(v1_is_nan, min_cmp, ""), v1, v2, ""); + // Because inputs were canonicalized, we always produce + // canonical NaN outputs. No pending NaN cleanup. + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::F64Min => { + // This implements the same logic as LLVM's @llvm.minimum + // intrinsic would, but x86 lowering of that intrinsic + // encounters a fatal error in LLVM 8 and LLVM 9. let (v1, v2) = state.pop2()?; - let res = builder - .build_call(intrinsics.minimum_f64, &[v1, v2], &state.var_name()) - .try_as_basic_value() - .left() - .unwrap(); - state.push1(res); + + // To detect min(-0.0, 0.0), we check whether the integer + // representations are equal. There's one other case where that + // can happen: non-canonical NaNs. Here we unconditionally + // canonicalize the NaNs. + let v1 = canonicalize_nans(builder, intrinsics, v1); + let v2 = canonicalize_nans(builder, intrinsics, v2); + + let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); + let v1_is_nan = builder.build_float_compare( + FloatPredicate::UNO, + v1, + intrinsics.f64_zero, + "nan", + ); + let v2_is_not_nan = builder.build_float_compare( + FloatPredicate::ORD, + v2, + intrinsics.f64_zero, + "notnan", + ); + let v1_repr = builder + .build_bitcast(v1, intrinsics.i64_ty, "") + .into_int_value(); + let v2_repr = builder + .build_bitcast(v2, intrinsics.i64_ty, "") + .into_int_value(); + let repr_ne = builder.build_int_compare(IntPredicate::NE, v1_repr, v2_repr, ""); + let float_eq = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); + let min_cmp = builder.build_float_compare(FloatPredicate::OLT, v1, v2, ""); + let negative_zero = intrinsics.f64_ty.const_float(-0.0); + let v2 = builder + .build_select( + builder.build_and( + builder.build_and(float_eq, repr_ne, ""), + v2_is_not_nan, + "", + ), + negative_zero, + v2, + "", + ) + .into_float_value(); + let res = + builder.build_select(builder.build_or(v1_is_nan, min_cmp, ""), v1, v2, ""); + // Because inputs were canonicalized, we always produce + // canonical NaN outputs. No pending NaN cleanup. + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } Operator::F32x4Min => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f32x4_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f32x4_ty, ""); - let res = builder - .build_call(intrinsics.minimum_f32x4, &[v1, v2], &state.var_name()) - .try_as_basic_value() - .left() - .unwrap(); + // This implements the same logic as LLVM's @llvm.minimum + // intrinsic would, but x86 lowering of that intrinsic + // encounters a fatal error in LLVM 8 and LLVM 9. + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); + + // To detect min(-0.0, 0.0), we check whether the integer + // representations are equal. There's one other case where that + // can happen: non-canonical NaNs. Here we unconditionally + // canonicalize the NaNs. Note that this is a different + // canonicalization from that which may be performed in the + // v128_into_f32x4 function. That may canonicalize as F64x2 if + // previous computations may have emitted F64x2 NaNs. + let v1 = canonicalize_nans(builder, intrinsics, v1.as_basic_value_enum()); + let v2 = canonicalize_nans(builder, intrinsics, v2.as_basic_value_enum()); + let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + + let v1_is_nan = builder.build_float_compare( + FloatPredicate::UNO, + v1, + intrinsics.f32x4_zero, + "nan", + ); + let v2_is_not_nan = builder.build_float_compare( + FloatPredicate::ORD, + v2, + intrinsics.f32x4_zero, + "notnan", + ); + let v1_repr = builder + .build_bitcast(v1, intrinsics.i32x4_ty, "") + .into_vector_value(); + let v2_repr = builder + .build_bitcast(v2, intrinsics.i32x4_ty, "") + .into_vector_value(); + let repr_ne = builder.build_int_compare(IntPredicate::NE, v1_repr, v2_repr, ""); + let float_eq = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); + let min_cmp = builder.build_float_compare(FloatPredicate::OLT, v1, v2, ""); + let negative_zero = splat_vector( + builder, + intrinsics, + intrinsics.f32_ty.const_float(-0.0).as_basic_value_enum(), + intrinsics.f32x4_ty, + "", + ); + let v2 = builder + .build_select( + builder.build_and( + builder.build_and(float_eq, repr_ne, ""), + v2_is_not_nan, + "", + ), + negative_zero, + v2, + "", + ) + .into_vector_value(); + let res = + builder.build_select(builder.build_or(v1_is_nan, min_cmp, ""), v1, v2, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // Because inputs were canonicalized, we always produce + // canonical NaN outputs. No pending NaN cleanup. + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::F64x2Min => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f64x2_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f64x2_ty, ""); - let res = builder - .build_call(intrinsics.minimum_f64x2, &[v1, v2], &state.var_name()) - .try_as_basic_value() - .left() - .unwrap(); + // This implements the same logic as LLVM's @llvm.minimum + // intrinsic would, but x86 lowering of that intrinsic + // encounters a fatal error in LLVM 8 and LLVM 9. + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); + + // To detect min(-0.0, 0.0), we check whether the integer + // representations are equal. There's one other case where that + // can happen: non-canonical NaNs. Here we unconditionally + // canonicalize the NaNs. Note that this is a different + // canonicalization from that which may be performed in the + // v128_into_f32x4 function. That may canonicalize as F64x2 if + // previous computations may have emitted F64x2 NaNs. + let v1 = canonicalize_nans(builder, intrinsics, v1.as_basic_value_enum()); + let v2 = canonicalize_nans(builder, intrinsics, v2.as_basic_value_enum()); + let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + + let v1_is_nan = builder.build_float_compare( + FloatPredicate::UNO, + v1, + intrinsics.f64x2_zero, + "nan", + ); + let v2_is_not_nan = builder.build_float_compare( + FloatPredicate::ORD, + v2, + intrinsics.f64x2_zero, + "notnan", + ); + let v1_repr = builder + .build_bitcast(v1, intrinsics.i64x2_ty, "") + .into_vector_value(); + let v2_repr = builder + .build_bitcast(v2, intrinsics.i64x2_ty, "") + .into_vector_value(); + let repr_ne = builder.build_int_compare(IntPredicate::NE, v1_repr, v2_repr, ""); + let float_eq = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); + let min_cmp = builder.build_float_compare(FloatPredicate::OLT, v1, v2, ""); + let negative_zero = splat_vector( + builder, + intrinsics, + intrinsics.f64_ty.const_float(-0.0).as_basic_value_enum(), + intrinsics.f64x2_ty, + "", + ); + let v2 = builder + .build_select( + builder.build_and( + builder.build_and(float_eq, repr_ne, ""), + v2_is_not_nan, + "", + ), + negative_zero, + v2, + "", + ) + .into_vector_value(); + let res = + builder.build_select(builder.build_or(v1_is_nan, min_cmp, ""), v1, v2, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // Because inputs were canonicalized, we always produce + // canonical NaN outputs. No pending NaN cleanup. + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } Operator::F32Max => { + // This implements the same logic as LLVM's @llvm.maximum + // intrinsic would, but x86 lowering of that intrinsic + // encounters a fatal error in LLVM 8 and LLVM 9. let (v1, v2) = state.pop2()?; - let res = builder - .build_call(intrinsics.maximum_f32, &[v1, v2], &state.var_name()) - .try_as_basic_value() - .left() - .unwrap(); - state.push1(res); + + // To detect min(-0.0, 0.0), we check whether the integer + // representations are equal. There's one other case where that + // can happen: non-canonical NaNs. Here we unconditionally + // canonicalize the NaNs. + let v1 = canonicalize_nans(builder, intrinsics, v1); + let v2 = canonicalize_nans(builder, intrinsics, v2); + + let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); + let v1_is_nan = builder.build_float_compare( + FloatPredicate::UNO, + v1, + intrinsics.f32_zero, + "nan", + ); + let v2_is_not_nan = builder.build_float_compare( + FloatPredicate::ORD, + v2, + intrinsics.f32_zero, + "notnan", + ); + let v1_repr = builder + .build_bitcast(v1, intrinsics.i32_ty, "") + .into_int_value(); + let v2_repr = builder + .build_bitcast(v2, intrinsics.i32_ty, "") + .into_int_value(); + let repr_ne = builder.build_int_compare(IntPredicate::NE, v1_repr, v2_repr, ""); + let float_eq = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); + let min_cmp = builder.build_float_compare(FloatPredicate::OGT, v1, v2, ""); + let v2 = builder + .build_select( + builder.build_and( + builder.build_and(float_eq, repr_ne, ""), + v2_is_not_nan, + "", + ), + intrinsics.f32_zero, + v2, + "", + ) + .into_float_value(); + let res = + builder.build_select(builder.build_or(v1_is_nan, min_cmp, ""), v1, v2, ""); + // Because inputs were canonicalized, we always produce + // canonical NaN outputs. No pending NaN cleanup. + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::F64Max => { + // This implements the same logic as LLVM's @llvm.maximum + // intrinsic would, but x86 lowering of that intrinsic + // encounters a fatal error in LLVM 8 and LLVM 9. let (v1, v2) = state.pop2()?; - let res = builder - .build_call(intrinsics.maximum_f64, &[v1, v2], &state.var_name()) - .try_as_basic_value() - .left() - .unwrap(); - state.push1(res); + + // To detect min(-0.0, 0.0), we check whether the integer + // representations are equal. There's one other case where that + // can happen: non-canonical NaNs. Here we unconditionally + // canonicalize the NaNs. + let v1 = canonicalize_nans(builder, intrinsics, v1); + let v2 = canonicalize_nans(builder, intrinsics, v2); + + let (v1, v2) = (v1.into_float_value(), v2.into_float_value()); + let v1_is_nan = builder.build_float_compare( + FloatPredicate::UNO, + v1, + intrinsics.f64_zero, + "nan", + ); + let v2_is_not_nan = builder.build_float_compare( + FloatPredicate::ORD, + v2, + intrinsics.f64_zero, + "notnan", + ); + let v1_repr = builder + .build_bitcast(v1, intrinsics.i64_ty, "") + .into_int_value(); + let v2_repr = builder + .build_bitcast(v2, intrinsics.i64_ty, "") + .into_int_value(); + let repr_ne = builder.build_int_compare(IntPredicate::NE, v1_repr, v2_repr, ""); + let float_eq = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); + let min_cmp = builder.build_float_compare(FloatPredicate::OGT, v1, v2, ""); + let v2 = builder + .build_select( + builder.build_and( + builder.build_and(float_eq, repr_ne, ""), + v2_is_not_nan, + "", + ), + intrinsics.f64_zero, + v2, + "", + ) + .into_float_value(); + let res = + builder.build_select(builder.build_or(v1_is_nan, min_cmp, ""), v1, v2, ""); + // Because inputs were canonicalized, we always produce + // canonical NaN outputs. No pending NaN cleanup. + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } Operator::F32x4Max => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f32x4_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f32x4_ty, ""); - let res = builder - .build_call(intrinsics.maximum_f32x4, &[v1, v2], &state.var_name()) - .try_as_basic_value() - .left() - .unwrap(); + // This implements the same logic as LLVM's @llvm.maximum + // intrinsic would, but x86 lowering of that intrinsic + // encounters a fatal error in LLVM 8 and LLVM 9. + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); + + // To detect min(-0.0, 0.0), we check whether the integer + // representations are equal. There's one other case where that + // can happen: non-canonical NaNs. Here we unconditionally + // canonicalize the NaNs. Note that this is a different + // canonicalization from that which may be performed in the + // v128_into_f32x4 function. That may canonicalize as F64x2 if + // previous computations may have emitted F64x2 NaNs. + let v1 = canonicalize_nans(builder, intrinsics, v1.as_basic_value_enum()); + let v2 = canonicalize_nans(builder, intrinsics, v2.as_basic_value_enum()); + let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let v1_is_nan = builder.build_float_compare( + FloatPredicate::UNO, + v1, + intrinsics.f32x4_zero, + "nan", + ); + let v2_is_not_nan = builder.build_float_compare( + FloatPredicate::ORD, + v2, + intrinsics.f32x4_zero, + "notnan", + ); + let v1_repr = builder + .build_bitcast(v1, intrinsics.i32x4_ty, "") + .into_vector_value(); + let v2_repr = builder + .build_bitcast(v2, intrinsics.i32x4_ty, "") + .into_vector_value(); + let repr_ne = builder.build_int_compare(IntPredicate::NE, v1_repr, v2_repr, ""); + let float_eq = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); + let min_cmp = builder.build_float_compare(FloatPredicate::OGT, v1, v2, ""); + let zero = splat_vector( + builder, + intrinsics, + intrinsics.f32_zero.as_basic_value_enum(), + intrinsics.f32x4_ty, + "", + ); + let v2 = builder + .build_select( + builder.build_and( + builder.build_and(float_eq, repr_ne, ""), + v2_is_not_nan, + "", + ), + zero, + v2, + "", + ) + .into_vector_value(); + let res = + builder.build_select(builder.build_or(v1_is_nan, min_cmp, ""), v1, v2, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // Because inputs were canonicalized, we always produce + // canonical NaN outputs. No pending NaN cleanup. + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::F64x2Max => { - let (v1, v2) = state.pop2()?; - let v1 = builder.build_bitcast(v1, intrinsics.f64x2_ty, ""); - let v2 = builder.build_bitcast(v2, intrinsics.f64x2_ty, ""); - let res = builder - .build_call(intrinsics.maximum_f64x2, &[v1, v2], &state.var_name()) - .try_as_basic_value() - .left() - .unwrap(); + // This implements the same logic as LLVM's @llvm.maximum + // intrinsic would, but x86 lowering of that intrinsic + // encounters a fatal error in LLVM 8 and LLVM 9. + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); + + // To detect min(-0.0, 0.0), we check whether the integer + // representations are equal. There's one other case where that + // can happen: non-canonical NaNs. Here we unconditionally + // canonicalize the NaNs. Note that this is a different + // canonicalization from that which may be performed in the + // v128_into_f32x4 function. That may canonicalize as F64x2 if + // previous computations may have emitted F64x2 NaNs. + let v1 = canonicalize_nans(builder, intrinsics, v1.as_basic_value_enum()); + let v2 = canonicalize_nans(builder, intrinsics, v2.as_basic_value_enum()); + let (v1, v2) = (v1.into_vector_value(), v2.into_vector_value()); + let v1_is_nan = builder.build_float_compare( + FloatPredicate::UNO, + v1, + intrinsics.f64x2_zero, + "nan", + ); + let v2_is_not_nan = builder.build_float_compare( + FloatPredicate::ORD, + v2, + intrinsics.f64x2_zero, + "notnan", + ); + let v1_repr = builder + .build_bitcast(v1, intrinsics.i64x2_ty, "") + .into_vector_value(); + let v2_repr = builder + .build_bitcast(v2, intrinsics.i64x2_ty, "") + .into_vector_value(); + let repr_ne = builder.build_int_compare(IntPredicate::NE, v1_repr, v2_repr, ""); + let float_eq = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); + let min_cmp = builder.build_float_compare(FloatPredicate::OGT, v1, v2, ""); + let zero = splat_vector( + builder, + intrinsics, + intrinsics.f64_zero.as_basic_value_enum(), + intrinsics.f64x2_ty, + "", + ); + let v2 = builder + .build_select( + builder.build_and( + builder.build_and(float_eq, repr_ne, ""), + v2_is_not_nan, + "", + ), + zero, + v2, + "", + ) + .into_vector_value(); + let res = + builder.build_select(builder.build_or(v1_is_nan, min_cmp, ""), v1, v2, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // Because inputs were canonicalized, we always produce + // canonical NaN outputs. No pending NaN cleanup. + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } Operator::F32Ceil => { - let input = state.pop1()?; + let (input, info) = state.pop1_extra()?; let res = builder .build_call(intrinsics.ceil_f32, &[input], &state.var_name()) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, info | ExtraInfo::pending_f32_nan()); } Operator::F64Ceil => { - let input = state.pop1()?; + let (input, info) = state.pop1_extra()?; let res = builder .build_call(intrinsics.ceil_f64, &[input], &state.var_name()) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, info | ExtraInfo::pending_f64_nan()); } Operator::F32Floor => { - let input = state.pop1()?; + let (input, info) = state.pop1_extra()?; let res = builder .build_call(intrinsics.floor_f32, &[input], &state.var_name()) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, info | ExtraInfo::pending_f32_nan()); } Operator::F64Floor => { - let input = state.pop1()?; + let (input, info) = state.pop1_extra()?; let res = builder .build_call(intrinsics.floor_f64, &[input], &state.var_name()) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, info | ExtraInfo::pending_f64_nan()); } Operator::F32Trunc => { - let input = state.pop1()?; + let (v, i) = state.pop1_extra()?; let res = builder - .build_call(intrinsics.trunc_f32, &[input], &state.var_name()) + .build_call( + intrinsics.trunc_f32, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, i); } Operator::F64Trunc => { - let input = state.pop1()?; + let (v, i) = state.pop1_extra()?; let res = builder - .build_call(intrinsics.trunc_f64, &[input], &state.var_name()) + .build_call( + intrinsics.trunc_f64, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, i); } Operator::F32Nearest => { - let input = state.pop1()?; + let (v, i) = state.pop1_extra()?; let res = builder - .build_call(intrinsics.nearbyint_f32, &[input], &state.var_name()) + .build_call( + intrinsics.nearbyint_f32, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, i); } Operator::F64Nearest => { - let input = state.pop1()?; + let (v, i) = state.pop1_extra()?; let res = builder - .build_call(intrinsics.nearbyint_f64, &[input], &state.var_name()) + .build_call( + intrinsics.nearbyint_f64, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + state.push1_extra(res, i); } Operator::F32Abs => { - let input = state.pop1()?; + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let res = builder - .build_call(intrinsics.fabs_f32, &[input], &state.var_name()) + .build_call( + intrinsics.fabs_f32, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + // The exact NaN returned by F32Abs is fully defined. Do not + // adjust. + state.push1_extra(res, i.strip_pending()); } Operator::F64Abs => { - let input = state.pop1()?; + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let res = builder - .build_call(intrinsics.fabs_f64, &[input], &state.var_name()) + .build_call( + intrinsics.fabs_f64, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + // The exact NaN returned by F64Abs is fully defined. Do not + // adjust. + state.push1_extra(res, i.strip_pending()); } Operator::F32x4Abs => { - let v = state.pop1()?.into_int_value(); - let v = builder.build_bitcast(v, intrinsics.f32x4_ty, ""); + let (v, i) = state.pop1_extra()?; + let v = builder.build_bitcast(v.into_int_value(), intrinsics.f32x4_ty, ""); + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let res = builder - .build_call(intrinsics.fabs_f32x4, &[v], &state.var_name()) + .build_call( + intrinsics.fabs_f32x4, + &[v.as_basic_value_enum()], + &state.var_name(), + ) .try_as_basic_value() .left() .unwrap(); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // The exact NaN returned by F32x4Abs is fully defined. Do not + // adjust. + state.push1_extra(res, i.strip_pending()); } Operator::F64x2Abs => { - let v = state.pop1()?.into_int_value(); - let v = builder.build_bitcast(v, intrinsics.f64x2_ty, ""); + let (v, i) = state.pop1_extra()?; + let v = builder.build_bitcast(v.into_int_value(), intrinsics.f64x2_ty, ""); + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let res = builder .build_call(intrinsics.fabs_f64x2, &[v], &state.var_name()) .try_as_basic_value() .left() .unwrap(); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // The exact NaN returned by F32x4Abs is fully defined. Do not + // adjust. + state.push1_extra(res, i.strip_pending()); } Operator::F32x4Neg => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.f32x4_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let v = builder.build_bitcast(v.into_int_value(), intrinsics.f32x4_ty, ""); + let v = + apply_pending_canonicalization(builder, intrinsics, v, i).into_vector_value(); let res = builder.build_float_neg(v, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // The exact NaN returned by F32x4Neg is fully defined. Do not + // adjust. + state.push1_extra(res, i.strip_pending()); } Operator::F64x2Neg => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.f64x2_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let v = builder.build_bitcast(v.into_int_value(), intrinsics.f64x2_ty, ""); + let v = + apply_pending_canonicalization(builder, intrinsics, v, i).into_vector_value(); let res = builder.build_float_neg(v, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + // The exact NaN returned by F64x2Neg is fully defined. Do not + // adjust. + state.push1_extra(res, i.strip_pending()); } Operator::F32Neg | Operator::F64Neg => { - let input = state.pop1()?.into_float_value(); - let res = builder.build_float_neg(input, &state.var_name()); - state.push1(res); + let (v, i) = state.pop1_extra()?; + let v = + apply_pending_canonicalization(builder, intrinsics, v, i).into_float_value(); + let res = builder.build_float_neg(v, &state.var_name()); + // The exact NaN returned by F32Neg and F64Neg are fully defined. + // Do not adjust. + state.push1_extra(res, i.strip_pending()); } Operator::F32Copysign => { - let (mag, sgn) = state.pop2()?; + let ((mag, mag_info), (sgn, sgn_info)) = state.pop2_extra()?; + let mag = apply_pending_canonicalization(builder, intrinsics, mag, mag_info); + let sgn = apply_pending_canonicalization(builder, intrinsics, sgn, sgn_info); let res = builder .build_call(intrinsics.copysign_f32, &[mag, sgn], &state.var_name()) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + // The exact NaN returned by F32Copysign is fully defined. + // Do not adjust. + state.push1_extra(res, mag_info.strip_pending()); } Operator::F64Copysign => { - let (msg, sgn) = state.pop2()?; + let ((mag, mag_info), (sgn, sgn_info)) = state.pop2_extra()?; + let mag = apply_pending_canonicalization(builder, intrinsics, mag, mag_info); + let sgn = apply_pending_canonicalization(builder, intrinsics, sgn, sgn_info); let res = builder - .build_call(intrinsics.copysign_f64, &[msg, sgn], &state.var_name()) + .build_call(intrinsics.copysign_f64, &[mag, sgn], &state.var_name()) .try_as_basic_value() .left() .unwrap(); - state.push1(res); + // The exact NaN returned by F32Copysign is fully defined. + // Do not adjust. + state.push1_extra(res, mag_info.strip_pending()); } /*************************** @@ -2619,460 +3783,384 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { * https://github.com/sunfishcode/wasm-reference-manual/blob/master/WebAssembly.md#integer-comparison-instructions ***************************/ Operator::I32Eq | Operator::I64Eq => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::EQ, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16Eq => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::EQ, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8Eq => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::EQ, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4Eq => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::EQ, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32Ne | Operator::I64Ne => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::NE, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16Ne => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::NE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8Ne => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::NE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4Ne => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::NE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32LtS | Operator::I64LtS => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::SLT, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16LtS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SLT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8LtS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SLT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4LtS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SLT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32LtU | Operator::I64LtU => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::ULT, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); state.push1(res); } Operator::I8x16LtU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::ULT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8LtU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::ULT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4LtU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::ULT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32LeS | Operator::I64LeS => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::SLE, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16LeS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SLE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8LeS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SLE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4LeS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SLE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32LeU | Operator::I64LeU => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::ULE, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16LeU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::ULE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8LeU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::ULE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4LeU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::ULE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32GtS | Operator::I64GtS => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::SGT, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16GtS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SGT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8GtS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SGT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4GtS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SGT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32GtU | Operator::I64GtU => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::UGT, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16GtU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::UGT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8GtU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::UGT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4GtU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::UGT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32GeS | Operator::I64GeS => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::SGE, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); state.push1(res); } Operator::I8x16GeS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SGE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8GeS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SGE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4GeS => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::SGE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32GeU | Operator::I64GeU => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let (v1, v2) = (v1.into_int_value(), v2.into_int_value()); let cond = builder.build_int_compare(IntPredicate::UGE, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16GeU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i8x16(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::UGE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i8x16_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8GeU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i16x8(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::UGE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i16x8_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4GeU => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_i32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_i32x4(builder, intrinsics, v2, i2); let res = builder.build_int_compare(IntPredicate::UGE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); @@ -3089,29 +4177,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let cond = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::F32x4Eq => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::F64x2Eq => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f64x2_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OEQ, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i64x2_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); @@ -3123,29 +4206,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let cond = builder.build_float_compare(FloatPredicate::UNE, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::F32x4Ne => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::UNE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::F64x2Ne => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f64x2_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::UNE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i64x2_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); @@ -3157,29 +4235,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let cond = builder.build_float_compare(FloatPredicate::OLT, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::F32x4Lt => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OLT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::F64x2Lt => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f64x2_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OLT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i64x2_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); @@ -3191,29 +4264,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let cond = builder.build_float_compare(FloatPredicate::OLE, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::F32x4Le => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OLE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::F64x2Le => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f64x2_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OLE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i64x2_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); @@ -3225,29 +4293,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let cond = builder.build_float_compare(FloatPredicate::OGT, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::F32x4Gt => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OGT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::F64x2Gt => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f64x2_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OGT, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i64x2_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); @@ -3259,29 +4322,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let cond = builder.build_float_compare(FloatPredicate::OGE, v1, v2, &state.var_name()); let res = builder.build_int_z_extend(cond, intrinsics.i32_ty, &state.var_name()); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::F32x4Ge => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f32x4_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f32x4(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f32x4(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OGE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i32x4_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::F64x2Ge => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f64x2_ty, "") - .into_vector_value(); - let v2 = builder - .build_bitcast(v2, intrinsics.f64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, _) = v128_into_f64x2(builder, intrinsics, v1, i1); + let (v2, _) = v128_into_f64x2(builder, intrinsics, v2, i2); let res = builder.build_float_compare(FloatPredicate::OGE, v1, v2, ""); let res = builder.build_int_s_extend(res, intrinsics.i64x2_ty, ""); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); @@ -3293,22 +4351,30 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { * https://github.com/sunfishcode/wasm-reference-manual/blob/master/WebAssembly.md#conversion-instructions ***************************/ Operator::I32WrapI64 => { - let v1 = state.pop1()?.into_int_value(); - let res = builder.build_int_truncate(v1, intrinsics.i32_ty, &state.var_name()); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); + let res = builder.build_int_truncate(v, intrinsics.i32_ty, &state.var_name()); state.push1(res); } Operator::I64ExtendSI32 => { - let v1 = state.pop1()?.into_int_value(); - let res = builder.build_int_s_extend(v1, intrinsics.i64_ty, &state.var_name()); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); + let res = builder.build_int_s_extend(v, intrinsics.i64_ty, &state.var_name()); state.push1(res); } Operator::I64ExtendUI32 => { - let v1 = state.pop1()?.into_int_value(); - let res = builder.build_int_z_extend(v1, intrinsics.i64_ty, &state.var_name()); - state.push1(res); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); + let res = builder.build_int_z_extend(v, intrinsics.i64_ty, &state.var_name()); + state.push1_extra(res, ExtraInfo::arithmetic_f64()); } Operator::I32x4TruncSF32x4Sat => { - let v = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); let res = trunc_sat( builder, intrinsics, @@ -3324,7 +4390,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32x4TruncUF32x4Sat => { - let v = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); let res = trunc_sat( builder, intrinsics, @@ -3340,7 +4408,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I64x2TruncSF64x2Sat => { - let v = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); let res = trunc_sat( builder, intrinsics, @@ -3356,7 +4426,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I64x2TruncUF64x2Sat => { - let v = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); let res = trunc_sat( builder, intrinsics, @@ -3501,39 +4573,47 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::F32DemoteF64 => { - let v1 = state.pop1()?; - let v1 = canonicalize_nans(builder, intrinsics, v1).into_float_value(); - let res = builder.build_float_trunc(v1, intrinsics.f32_ty, &state.var_name()); - state.push1(res); + let v = state.pop1()?; + let v = v.into_float_value(); + let res = builder.build_float_trunc(v, intrinsics.f32_ty, &state.var_name()); + state.push1_extra(res, ExtraInfo::pending_f32_nan()); } Operator::F64PromoteF32 => { - let v1 = state.pop1()?; - let v1 = canonicalize_nans(builder, intrinsics, v1).into_float_value(); - let res = builder.build_float_ext(v1, intrinsics.f64_ty, &state.var_name()); - state.push1(res); + let v = state.pop1()?; + let v = v.into_float_value(); + let res = builder.build_float_ext(v, intrinsics.f64_ty, &state.var_name()); + state.push1_extra(res, ExtraInfo::pending_f64_nan()); } Operator::F32ConvertSI32 | Operator::F32ConvertSI64 => { - let v1 = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); let res = - builder.build_signed_int_to_float(v1, intrinsics.f32_ty, &state.var_name()); + builder.build_signed_int_to_float(v, intrinsics.f32_ty, &state.var_name()); state.push1(res); } Operator::F64ConvertSI32 | Operator::F64ConvertSI64 => { - let v1 = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); let res = - builder.build_signed_int_to_float(v1, intrinsics.f64_ty, &state.var_name()); + builder.build_signed_int_to_float(v, intrinsics.f64_ty, &state.var_name()); state.push1(res); } Operator::F32ConvertUI32 | Operator::F32ConvertUI64 => { - let v1 = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); let res = - builder.build_unsigned_int_to_float(v1, intrinsics.f32_ty, &state.var_name()); + builder.build_unsigned_int_to_float(v, intrinsics.f32_ty, &state.var_name()); state.push1(res); } Operator::F64ConvertUI32 | Operator::F64ConvertUI64 => { - let v1 = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); + let v = v.into_int_value(); let res = - builder.build_unsigned_int_to_float(v1, intrinsics.f64_ty, &state.var_name()); + builder.build_unsigned_int_to_float(v, intrinsics.f64_ty, &state.var_name()); state.push1(res); } Operator::F32x4ConvertSI32x4 => { @@ -3577,24 +4657,26 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32ReinterpretF32 => { - let v = state.pop1()?; + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let ret = builder.build_bitcast(v, intrinsics.i32_ty, &state.var_name()); - state.push1(ret); + state.push1_extra(ret, ExtraInfo::arithmetic_f32()); } Operator::I64ReinterpretF64 => { - let v = state.pop1()?; + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let ret = builder.build_bitcast(v, intrinsics.i64_ty, &state.var_name()); - state.push1(ret); + state.push1_extra(ret, ExtraInfo::arithmetic_f64()); } Operator::F32ReinterpretI32 => { - let v = state.pop1()?; + let (v, i) = state.pop1_extra()?; let ret = builder.build_bitcast(v, intrinsics.f32_ty, &state.var_name()); - state.push1(ret); + state.push1_extra(ret, i); } Operator::F64ReinterpretI64 => { - let v = state.pop1()?; + let (v, i) = state.pop1_extra()?; let ret = builder.build_bitcast(v, intrinsics.f64_ty, &state.var_name()); - state.push1(ret); + state.push1_extra(ret, i); } /*************************** @@ -3651,6 +4733,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3659,6 +4742,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { 4, )?; let result = builder.build_load(effective_address, &state.var_name()); + result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + result.as_instruction_value().unwrap(), + Some(0), + ); state.push1(result); } Operator::I64Load { ref memarg } => { @@ -3666,6 +4761,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3674,6 +4770,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { 8, )?; let result = builder.build_load(effective_address, &state.var_name()); + result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + result.as_instruction_value().unwrap(), + Some(0), + ); state.push1(result); } Operator::F32Load { ref memarg } => { @@ -3681,6 +4789,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3689,6 +4798,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { 4, )?; let result = builder.build_load(effective_address, &state.var_name()); + result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + result.as_instruction_value().unwrap(), + Some(0), + ); state.push1(result); } Operator::F64Load { ref memarg } => { @@ -3696,6 +4817,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3704,6 +4826,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { 8, )?; let result = builder.build_load(effective_address, &state.var_name()); + result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + result.as_instruction_value().unwrap(), + Some(0), + ); state.push1(result); } Operator::V128Load { ref memarg } => { @@ -3711,6 +4845,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3719,6 +4854,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { 16, )?; let result = builder.build_load(effective_address, &state.var_name()); + result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + result.as_instruction_value().unwrap(), + Some(0), + ); state.push1(result); } @@ -3728,6 +4875,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3735,7 +4883,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i32_ptr_ty, 4, )?; - builder.build_store(effective_address, value); + let store = builder.build_store(effective_address, value); + store.set_alignment(1).unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I64Store { ref memarg } => { let value = state.pop1()?; @@ -3743,6 +4893,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3750,14 +4901,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i64_ptr_ty, 8, )?; - builder.build_store(effective_address, value); + let store = builder.build_store(effective_address, value); + store.set_alignment(1).unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::F32Store { ref memarg } => { - let value = state.pop1()?; + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3765,14 +4920,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.f32_ptr_ty, 4, )?; - builder.build_store(effective_address, value); + let store = builder.build_store(effective_address, v); + store.set_alignment(1).unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::F64Store { ref memarg } => { - let value = state.pop1()?; + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3780,14 +4939,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.f64_ptr_ty, 8, )?; - builder.build_store(effective_address, value); + let store = builder.build_store(effective_address, v); + store.set_alignment(1).unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::V128Store { ref memarg } => { - let value = state.pop1()?; + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3795,14 +4958,16 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i128_ptr_ty, 16, )?; - builder.build_store(effective_address, value); + let store = builder.build_store(effective_address, v); + store.set_alignment(1).unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } - Operator::I32Load8S { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3810,11 +4975,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i8_ptr_ty, 1, )?; - let narrow_result = builder - .build_load(effective_address, &state.var_name()) - .into_int_value(); - let result = - builder.build_int_s_extend(narrow_result, intrinsics.i32_ty, &state.var_name()); + let narrow_result = builder.build_load(effective_address, &state.var_name()); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); + let result = builder.build_int_s_extend( + narrow_result.into_int_value(), + intrinsics.i32_ty, + &state.var_name(), + ); state.push1(result); } Operator::I32Load16S { ref memarg } => { @@ -3822,6 +5000,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3829,11 +5008,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i16_ptr_ty, 2, )?; - let narrow_result = builder - .build_load(effective_address, &state.var_name()) - .into_int_value(); - let result = - builder.build_int_s_extend(narrow_result, intrinsics.i32_ty, &state.var_name()); + let narrow_result = builder.build_load(effective_address, &state.var_name()); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); + let result = builder.build_int_s_extend( + narrow_result.into_int_value(), + intrinsics.i32_ty, + &state.var_name(), + ); state.push1(result); } Operator::I64Load8S { ref memarg } => { @@ -3841,6 +5033,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3851,6 +5044,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let narrow_result = builder .build_load(effective_address, &state.var_name()) .into_int_value(); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); let result = builder.build_int_s_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); state.push1(result); @@ -3860,6 +5065,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3870,6 +5076,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let narrow_result = builder .build_load(effective_address, &state.var_name()) .into_int_value(); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); let result = builder.build_int_s_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); state.push1(result); @@ -3879,6 +5097,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3886,11 +5105,24 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i32_ptr_ty, 4, )?; - let narrow_result = builder - .build_load(effective_address, &state.var_name()) - .into_int_value(); - let result = - builder.build_int_s_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); + let narrow_result = builder.build_load(effective_address, &state.var_name()); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); + let result = builder.build_int_s_extend( + narrow_result.into_int_value(), + intrinsics.i64_ty, + &state.var_name(), + ); state.push1(result); } @@ -3899,6 +5131,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3906,18 +5139,32 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i8_ptr_ty, 1, )?; - let narrow_result = builder - .build_load(effective_address, &state.var_name()) - .into_int_value(); - let result = - builder.build_int_z_extend(narrow_result, intrinsics.i32_ty, &state.var_name()); - state.push1(result); + let narrow_result = builder.build_load(effective_address, &state.var_name()); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); + let result = builder.build_int_z_extend( + narrow_result.into_int_value(), + intrinsics.i32_ty, + &state.var_name(), + ); + state.push1_extra(result, ExtraInfo::arithmetic_f32()); } Operator::I32Load16U { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3925,18 +5172,32 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i16_ptr_ty, 2, )?; - let narrow_result = builder - .build_load(effective_address, &state.var_name()) - .into_int_value(); - let result = - builder.build_int_z_extend(narrow_result, intrinsics.i32_ty, &state.var_name()); - state.push1(result); + let narrow_result = builder.build_load(effective_address, &state.var_name()); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); + let result = builder.build_int_z_extend( + narrow_result.into_int_value(), + intrinsics.i32_ty, + &state.var_name(), + ); + state.push1_extra(result, ExtraInfo::arithmetic_f32()); } Operator::I64Load8U { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3944,18 +5205,32 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i8_ptr_ty, 1, )?; - let narrow_result = builder - .build_load(effective_address, &state.var_name()) - .into_int_value(); - let result = - builder.build_int_z_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); - state.push1(result); + let narrow_result = builder.build_load(effective_address, &state.var_name()); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); + let result = builder.build_int_z_extend( + narrow_result.into_int_value(), + intrinsics.i64_ty, + &state.var_name(), + ); + state.push1_extra(result, ExtraInfo::arithmetic_f64()); } Operator::I64Load16U { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3963,18 +5238,32 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i16_ptr_ty, 2, )?; - let narrow_result = builder - .build_load(effective_address, &state.var_name()) - .into_int_value(); - let result = - builder.build_int_z_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); - state.push1(result); + let narrow_result = builder.build_load(effective_address, &state.var_name()); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); + let result = builder.build_int_z_extend( + narrow_result.into_int_value(), + intrinsics.i64_ty, + &state.var_name(), + ); + state.push1_extra(result, ExtraInfo::arithmetic_f64()); } Operator::I64Load32U { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -3982,12 +5271,25 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i32_ptr_ty, 4, )?; - let narrow_result = builder - .build_load(effective_address, &state.var_name()) - .into_int_value(); - let result = - builder.build_int_z_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); - state.push1(result); + let narrow_result = builder.build_load(effective_address, &state.var_name()); + narrow_result + .as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + narrow_result.as_instruction_value().unwrap(), + Some(0), + ); + let result = builder.build_int_z_extend( + narrow_result.into_int_value(), + intrinsics.i64_ty, + &state.var_name(), + ); + state.push1_extra(result, ExtraInfo::arithmetic_f64()); } Operator::I32Store8 { ref memarg } | Operator::I64Store8 { ref memarg } => { @@ -3996,6 +5298,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4005,7 +5308,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { )?; let narrow_value = builder.build_int_truncate(value, intrinsics.i8_ty, &state.var_name()); - builder.build_store(effective_address, narrow_value); + let store = builder.build_store(effective_address, narrow_value); + store.set_alignment(1).unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I32Store16 { ref memarg } | Operator::I64Store16 { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4013,6 +5318,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4022,7 +5328,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { )?; let narrow_value = builder.build_int_truncate(value, intrinsics.i16_ty, &state.var_name()); - builder.build_store(effective_address, narrow_value); + let store = builder.build_store(effective_address, narrow_value); + store.set_alignment(1).unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I64Store32 { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4030,6 +5338,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4039,46 +5348,41 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { )?; let narrow_value = builder.build_int_truncate(value, intrinsics.i32_ty, &state.var_name()); - builder.build_store(effective_address, narrow_value); + let store = builder.build_store(effective_address, narrow_value); + store.set_alignment(1).unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I8x16Neg => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i8x16_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_i8x16(builder, intrinsics, v, i); let res = builder.build_int_sub(v.get_type().const_zero(), v, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I16x8Neg => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i16x8_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_i16x8(builder, intrinsics, v, i); let res = builder.build_int_sub(v.get_type().const_zero(), v, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I32x4Neg => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i32x4_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_i32x4(builder, intrinsics, v, i); let res = builder.build_int_sub(v.get_type().const_zero(), v, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::I64x2Neg => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i64x2_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_i64x2(builder, intrinsics, v, i); let res = builder.build_int_sub(v.get_type().const_zero(), v, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); state.push1(res); } Operator::V128Not => { - let v = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i).into_int_value(); let res = builder.build_not(v, &state.var_name()); state.push1(res); } @@ -4086,6 +5390,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { | Operator::I16x8AnyTrue | Operator::I32x4AnyTrue | Operator::I64x2AnyTrue => { + // Skip canonicalization, it never changes non-zero values to zero or vice versa. let v = state.pop1()?.into_int_value(); let res = builder.build_int_compare( IntPredicate::NE, @@ -4094,7 +5399,10 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_int_z_extend(res, intrinsics.i32_ty, ""); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16AllTrue | Operator::I16x8AllTrue @@ -4107,7 +5415,8 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { Operator::I64x2AllTrue => intrinsics.i64x2_ty, _ => unreachable!(), }; - let v = state.pop1()?.into_int_value(); + let (v, i) = state.pop1_extra()?; + let v = apply_pending_canonicalization(builder, intrinsics, v, i).into_int_value(); let lane_int_ty = context.custom_width_int_type(vec_ty.get_size()); let vec = builder.build_bitcast(v, vec_ty, "vec").into_vector_value(); let mask = @@ -4122,13 +5431,14 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { &state.var_name(), ); let res = builder.build_int_z_extend(res, intrinsics.i32_ty, ""); - state.push1(res); + state.push1_extra( + res, + ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + ); } Operator::I8x16ExtractLaneS { lane } => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i8x16_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_i8x16(builder, intrinsics, v, i); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder .build_extract_element(v, idx, &state.var_name()) @@ -4137,22 +5447,18 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I8x16ExtractLaneU { lane } => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i8x16_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_i8x16(builder, intrinsics, v, i); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder .build_extract_element(v, idx, &state.var_name()) .into_int_value(); let res = builder.build_int_z_extend(res, intrinsics.i32_ty, ""); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::I16x8ExtractLaneS { lane } => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i16x8_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_i16x8(builder, intrinsics, v, i); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder .build_extract_element(v, idx, &state.var_name()) @@ -4161,58 +5467,46 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8ExtractLaneU { lane } => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i16x8_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, _) = v128_into_i16x8(builder, intrinsics, v, i); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder .build_extract_element(v, idx, &state.var_name()) .into_int_value(); let res = builder.build_int_z_extend(res, intrinsics.i32_ty, ""); - state.push1(res); + state.push1_extra(res, ExtraInfo::arithmetic_f32()); } Operator::I32x4ExtractLane { lane } => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i32x4_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, i) = v128_into_i32x4(builder, intrinsics, v, i); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder.build_extract_element(v, idx, &state.var_name()); - state.push1(res); + state.push1_extra(res, i); } Operator::I64x2ExtractLane { lane } => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.i64x2_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, i) = v128_into_i64x2(builder, intrinsics, v, i); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder.build_extract_element(v, idx, &state.var_name()); - state.push1(res); + state.push1_extra(res, i); } Operator::F32x4ExtractLane { lane } => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.f32x4_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, i) = v128_into_f32x4(builder, intrinsics, v, i); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder.build_extract_element(v, idx, &state.var_name()); - state.push1(res); + state.push1_extra(res, i); } Operator::F64x2ExtractLane { lane } => { - let v = state.pop1()?.into_int_value(); - let v = builder - .build_bitcast(v, intrinsics.f64x2_ty, "") - .into_vector_value(); + let (v, i) = state.pop1_extra()?; + let (v, i) = v128_into_f64x2(builder, intrinsics, v, i); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder.build_extract_element(v, idx, &state.var_name()); - state.push1(res); + state.push1_extra(res, i); } Operator::I8x16ReplaceLane { lane } => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i8x16_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, _)) = state.pop2_extra()?; + let (v1, _) = v128_into_i8x16(builder, intrinsics, v1, i1); let v2 = v2.into_int_value(); let v2 = builder.build_int_cast(v2, intrinsics.i8_ty, ""); let idx = intrinsics.i32_ty.const_int(lane.into(), false); @@ -4221,10 +5515,8 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I16x8ReplaceLane { lane } => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i16x8_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, _)) = state.pop2_extra()?; + let (v1, _) = v128_into_i16x8(builder, intrinsics, v1, i1); let v2 = v2.into_int_value(); let v2 = builder.build_int_cast(v2, intrinsics.i16_ty, ""); let idx = intrinsics.i32_ty.const_int(lane.into(), false); @@ -4233,54 +5525,104 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::I32x4ReplaceLane { lane } => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i32x4_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_i32x4(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); + let i2 = i2.strip_pending(); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder.build_insert_element(v1, v2, idx, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra(res, i1 & i2 & ExtraInfo::arithmetic_f32()); } Operator::I64x2ReplaceLane { lane } => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.i64x2_ty, "") - .into_vector_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_i64x2(builder, intrinsics, v1, i1); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = v2.into_int_value(); + let i2 = i2.strip_pending(); let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder.build_insert_element(v1, v2, idx, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + state.push1_extra(res, i1 & i2 & ExtraInfo::arithmetic_f64()); } Operator::F32x4ReplaceLane { lane } => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f32x4_ty, "") - .into_vector_value(); - let v2 = v2.into_float_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_f32x4(builder, intrinsics, v1, i1); + let push_pending_f32_nan_to_result = + i1.has_pending_f32_nan() && i2.has_pending_f32_nan(); + let (v1, v2) = if !push_pending_f32_nan_to_result { + ( + apply_pending_canonicalization( + builder, + intrinsics, + v1.as_basic_value_enum(), + i1, + ) + .into_vector_value(), + apply_pending_canonicalization( + builder, + intrinsics, + v2.as_basic_value_enum(), + i2, + ) + .into_float_value(), + ) + } else { + (v1, v2.into_float_value()) + }; let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder.build_insert_element(v1, v2, idx, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + let info = if push_pending_f32_nan_to_result { + ExtraInfo::pending_f32_nan() + } else { + i1.strip_pending() & i2.strip_pending() + }; + state.push1_extra(res, info); } Operator::F64x2ReplaceLane { lane } => { - let (v1, v2) = state.pop2()?; - let v1 = builder - .build_bitcast(v1, intrinsics.f64x2_ty, "") - .into_vector_value(); - let v2 = v2.into_float_value(); + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let (v1, i1) = v128_into_f64x2(builder, intrinsics, v1, i1); + let push_pending_f64_nan_to_result = + i1.has_pending_f64_nan() && i2.has_pending_f64_nan(); + let (v1, v2) = if !push_pending_f64_nan_to_result { + ( + apply_pending_canonicalization( + builder, + intrinsics, + v1.as_basic_value_enum(), + i1, + ) + .into_vector_value(), + apply_pending_canonicalization( + builder, + intrinsics, + v2.as_basic_value_enum(), + i2, + ) + .into_float_value(), + ) + } else { + (v1, v2.into_float_value()) + }; let idx = intrinsics.i32_ty.const_int(lane.into(), false); let res = builder.build_insert_element(v1, v2, idx, &state.var_name()); let res = builder.build_bitcast(res, intrinsics.i128_ty, ""); - state.push1(res); + let info = if push_pending_f64_nan_to_result { + ExtraInfo::pending_f64_nan() + } else { + i1.strip_pending() & i2.strip_pending() + }; + state.push1_extra(res, info); } Operator::V8x16Swizzle => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); let v1 = builder .build_bitcast(v1, intrinsics.i8x16_ty, "") .into_vector_value(); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = builder .build_bitcast(v2, intrinsics.i8x16_ty, "") .into_vector_value(); @@ -4338,10 +5680,12 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(res); } Operator::V8x16Shuffle { lanes } => { - let (v1, v2) = state.pop2()?; + let ((v1, i1), (v2, i2)) = state.pop2_extra()?; + let v1 = apply_pending_canonicalization(builder, intrinsics, v1, i1); let v1 = builder .build_bitcast(v1, intrinsics.i8x16_ty, "") .into_vector_value(); + let v2 = apply_pending_canonicalization(builder, intrinsics, v2, i2); let v2 = builder .build_bitcast(v2, intrinsics.i8x16_ty, "") .into_vector_value(); @@ -4361,6 +5705,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4368,11 +5713,22 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i8_ptr_ty, 1, )?; - let elem = builder.build_load(effective_address, "").into_int_value(); + let elem = builder.build_load(effective_address, ""); + elem.as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + elem.as_instruction_value().unwrap(), + Some(0), + ); let res = splat_vector( builder, intrinsics, - elem.as_basic_value_enum(), + elem, intrinsics.i8x16_ty, &state.var_name(), ); @@ -4384,6 +5740,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4391,11 +5748,22 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i16_ptr_ty, 2, )?; - let elem = builder.build_load(effective_address, "").into_int_value(); + let elem = builder.build_load(effective_address, ""); + elem.as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + elem.as_instruction_value().unwrap(), + Some(0), + ); let res = splat_vector( builder, intrinsics, - elem.as_basic_value_enum(), + elem, intrinsics.i16x8_ty, &state.var_name(), ); @@ -4407,6 +5775,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4414,11 +5783,22 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i32_ptr_ty, 4, )?; - let elem = builder.build_load(effective_address, "").into_int_value(); + let elem = builder.build_load(effective_address, ""); + elem.as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + elem.as_instruction_value().unwrap(), + Some(0), + ); let res = splat_vector( builder, intrinsics, - elem.as_basic_value_enum(), + elem, intrinsics.i32x4_ty, &state.var_name(), ); @@ -4430,6 +5810,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4437,11 +5818,22 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { intrinsics.i64_ptr_ty, 8, )?; - let elem = builder.build_load(effective_address, "").into_int_value(); + let elem = builder.build_load(effective_address, ""); + elem.as_instruction_value() + .unwrap() + .set_alignment(1) + .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + elem.as_instruction_value().unwrap(), + Some(0), + ); let res = splat_vector( builder, intrinsics, - elem.as_basic_value_enum(), + elem, intrinsics.i64x2_ty, &state.var_name(), ); @@ -4462,6 +5854,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4478,8 +5871,11 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { effective_address, ); let result = builder.build_load(effective_address, &state.var_name()); - // TODO: LLVMSetAlignment(result.as_value_ref(), 4); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let load = result.as_instruction_value().unwrap(); + load.set_alignment(4).unwrap(); + load.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", load, Some(0)); state.push1(result); } Operator::I64AtomicLoad { ref memarg } => { @@ -4487,6 +5883,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4503,8 +5900,11 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { effective_address, ); let result = builder.build_load(effective_address, &state.var_name()); - // TODO: LLVMSetAlignment(result.as_value_ref(), 8); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let load = result.as_instruction_value().unwrap(); + load.set_alignment(8).unwrap(); + load.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", load, Some(0)); state.push1(result); } Operator::I32AtomicLoad8U { ref memarg } => { @@ -4512,6 +5912,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4530,17 +5931,21 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let narrow_result = builder .build_load(effective_address, &state.var_name()) .into_int_value(); - // TODO: LLVMSetAlignment(result.as_value_ref(), 1); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let load = narrow_result.as_instruction_value().unwrap(); + load.set_alignment(1).unwrap(); + load.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", load, Some(0)); let result = builder.build_int_z_extend(narrow_result, intrinsics.i32_ty, &state.var_name()); - state.push1(result); + state.push1_extra(result, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicLoad16U { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4559,17 +5964,21 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let narrow_result = builder .build_load(effective_address, &state.var_name()) .into_int_value(); - // TODO: LLVMSetAlignment(result.as_value_ref(), 2); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let load = narrow_result.as_instruction_value().unwrap(); + load.set_alignment(2).unwrap(); + load.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", load, Some(0)); let result = builder.build_int_z_extend(narrow_result, intrinsics.i32_ty, &state.var_name()); - state.push1(result); + state.push1_extra(result, ExtraInfo::arithmetic_f32()); } Operator::I64AtomicLoad8U { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4588,17 +5997,21 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let narrow_result = builder .build_load(effective_address, &state.var_name()) .into_int_value(); - // TODO: LLVMSetAlignment(result.as_value_ref(), 1); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let load = narrow_result.as_instruction_value().unwrap(); + load.set_alignment(1).unwrap(); + load.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", load, Some(0)); let result = builder.build_int_z_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); - state.push1(result); + state.push1_extra(result, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicLoad16U { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4617,17 +6030,21 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let narrow_result = builder .build_load(effective_address, &state.var_name()) .into_int_value(); - // TODO: LLVMSetAlignment(result.as_value_ref(), 2); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let load = narrow_result.as_instruction_value().unwrap(); + load.set_alignment(2).unwrap(); + load.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", load, Some(0)); let result = builder.build_int_z_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); - state.push1(result); + state.push1_extra(result, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicLoad32U { ref memarg } => { let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4646,11 +6063,14 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let narrow_result = builder .build_load(effective_address, &state.var_name()) .into_int_value(); - // TODO: LLVMSetAlignment(result.as_value_ref(), 4); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let load = narrow_result.as_instruction_value().unwrap(); + load.set_alignment(4).unwrap(); + load.set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", load, Some(0)); let result = builder.build_int_z_extend(narrow_result, intrinsics.i64_ty, &state.var_name()); - state.push1(result); + state.push1_extra(result, ExtraInfo::arithmetic_f64()); } Operator::I32AtomicStore { ref memarg } => { let value = state.pop1()?; @@ -4658,6 +6078,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4673,9 +6094,12 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { memarg, effective_address, ); - builder.build_store(effective_address, value); - // TODO: LLVMSetAlignment(result.as_value_ref(), 4); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let store = builder.build_store(effective_address, value); + store.set_alignment(4).unwrap(); + store + .set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I64AtomicStore { ref memarg } => { let value = state.pop1()?; @@ -4683,6 +6107,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4698,9 +6123,12 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { memarg, effective_address, ); - builder.build_store(effective_address, value); - // TODO: LLVMSetAlignment(result.as_value_ref(), 8); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let store = builder.build_store(effective_address, value); + store.set_alignment(8).unwrap(); + store + .set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I32AtomicStore8 { ref memarg } | Operator::I64AtomicStore8 { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4708,6 +6136,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4725,9 +6154,12 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { ); let narrow_value = builder.build_int_truncate(value, intrinsics.i8_ty, &state.var_name()); - builder.build_store(effective_address, narrow_value); - // TODO: LLVMSetAlignment(result.as_value_ref(), 1); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let store = builder.build_store(effective_address, narrow_value); + store.set_alignment(1).unwrap(); + store + .set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I32AtomicStore16 { ref memarg } | Operator::I64AtomicStore16 { ref memarg } => { @@ -4736,6 +6168,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4753,9 +6186,12 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { ); let narrow_value = builder.build_int_truncate(value, intrinsics.i16_ty, &state.var_name()); - builder.build_store(effective_address, narrow_value); - // TODO: LLVMSetAlignment(result.as_value_ref(), 2); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let store = builder.build_store(effective_address, narrow_value); + store.set_alignment(2).unwrap(); + store + .set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I64AtomicStore32 { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4763,6 +6199,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4780,9 +6217,12 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { ); let narrow_value = builder.build_int_truncate(value, intrinsics.i32_ty, &state.var_name()); - builder.build_store(effective_address, narrow_value); - // TODO: LLVMSetAlignment(result.as_value_ref(), 4); - // TODO: LLVMSetOrdering(result.as_value_ref(), LLVMAtomicOrderingSequentiallyConsistent); + let store = builder.build_store(effective_address, narrow_value); + store.set_alignment(4).unwrap(); + store + .set_atomic_ordering(AtomicOrdering::SequentiallyConsistent) + .unwrap(); + tbaa_label(&self.module, intrinsics, "memory", store, Some(0)); } Operator::I32AtomicRmw8UAdd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4790,6 +6230,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4815,8 +6256,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmw16UAdd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4824,6 +6272,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4849,8 +6298,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmwAdd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4858,6 +6314,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4881,6 +6338,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I64AtomicRmw8UAdd { ref memarg } => { @@ -4889,6 +6353,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4914,8 +6379,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw16UAdd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4923,6 +6395,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4948,8 +6421,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw32UAdd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4957,6 +6437,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -4982,8 +6463,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmwAdd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -4991,6 +6479,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5014,6 +6503,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I32AtomicRmw8USub { ref memarg } => { @@ -5022,6 +6518,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5047,8 +6544,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmw16USub { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5056,6 +6560,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5081,8 +6586,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmwSub { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5090,6 +6602,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5113,6 +6626,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I64AtomicRmw8USub { ref memarg } => { @@ -5121,6 +6641,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5146,8 +6667,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I64AtomicRmw16USub { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5155,6 +6683,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5180,8 +6709,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw32USub { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5189,6 +6725,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5214,8 +6751,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmwSub { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5223,6 +6767,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5246,6 +6791,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I32AtomicRmw8UAnd { ref memarg } => { @@ -5254,6 +6806,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5279,8 +6832,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmw16UAnd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5288,6 +6848,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5313,8 +6874,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmwAnd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5322,6 +6890,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5345,6 +6914,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I64AtomicRmw8UAnd { ref memarg } => { @@ -5353,6 +6929,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5378,8 +6955,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw16UAnd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5387,6 +6971,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5412,8 +6997,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw32UAnd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5421,6 +7013,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5446,8 +7039,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmwAnd { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5455,6 +7055,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5478,6 +7079,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I32AtomicRmw8UOr { ref memarg } => { @@ -5486,6 +7094,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5511,8 +7120,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmw16UOr { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5520,6 +7136,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5545,8 +7162,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmwOr { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5554,6 +7178,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5577,8 +7202,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I64AtomicRmw8UOr { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5586,6 +7218,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5611,8 +7244,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw16UOr { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5620,6 +7260,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5645,8 +7286,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw32UOr { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5654,6 +7302,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5679,8 +7328,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmwOr { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5688,6 +7344,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5711,6 +7368,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I32AtomicRmw8UXor { ref memarg } => { @@ -5719,6 +7383,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5744,8 +7409,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmw16UXor { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5753,6 +7425,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5778,8 +7451,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmwXor { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5787,6 +7467,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5810,6 +7491,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I64AtomicRmw8UXor { ref memarg } => { @@ -5818,6 +7506,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5843,8 +7532,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw16UXor { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5852,6 +7548,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5877,8 +7574,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw32UXor { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5886,6 +7590,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5911,8 +7616,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmwXor { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5920,6 +7632,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5943,6 +7656,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I32AtomicRmw8UXchg { ref memarg } => { @@ -5951,6 +7671,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -5976,8 +7697,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmw16UXchg { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -5985,6 +7713,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6010,8 +7739,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmwXchg { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -6019,6 +7755,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6042,6 +7779,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I64AtomicRmw8UXchg { ref memarg } => { @@ -6050,6 +7794,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6075,8 +7820,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw16UXchg { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -6084,6 +7836,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6109,8 +7862,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw32UXchg { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -6118,6 +7878,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6143,8 +7904,15 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmwXchg { ref memarg } => { let value = state.pop1()?.into_int_value(); @@ -6152,6 +7920,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6175,15 +7944,25 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); state.push1(old); } Operator::I32AtomicRmw8UCmpxchg { ref memarg } => { - let (cmp, new) = state.pop2()?; + let ((cmp, cmp_info), (new, new_info)) = state.pop2_extra()?; + let cmp = apply_pending_canonicalization(builder, intrinsics, cmp, cmp_info); + let new = apply_pending_canonicalization(builder, intrinsics, new, new_info); let (cmp, new) = (cmp.into_int_value(), new.into_int_value()); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6212,20 +7991,30 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder .build_extract_value(old, 0, "") .unwrap() .into_int_value(); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmw16UCmpxchg { ref memarg } => { - let (cmp, new) = state.pop2()?; + let ((cmp, cmp_info), (new, new_info)) = state.pop2_extra()?; + let cmp = apply_pending_canonicalization(builder, intrinsics, cmp, cmp_info); + let new = apply_pending_canonicalization(builder, intrinsics, new, new_info); let (cmp, new) = (cmp.into_int_value(), new.into_int_value()); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6254,20 +8043,30 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder .build_extract_value(old, 0, "") .unwrap() .into_int_value(); let old = builder.build_int_z_extend(old, intrinsics.i32_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f32()); } Operator::I32AtomicRmwCmpxchg { ref memarg } => { - let (cmp, new) = state.pop2()?; + let ((cmp, cmp_info), (new, new_info)) = state.pop2_extra()?; + let cmp = apply_pending_canonicalization(builder, intrinsics, cmp, cmp_info); + let new = apply_pending_canonicalization(builder, intrinsics, new, new_info); let (cmp, new) = (cmp.into_int_value(), new.into_int_value()); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6292,16 +8091,26 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_extract_value(old, 0, "").unwrap(); state.push1(old); } Operator::I64AtomicRmw8UCmpxchg { ref memarg } => { - let (cmp, new) = state.pop2()?; + let ((cmp, cmp_info), (new, new_info)) = state.pop2_extra()?; + let cmp = apply_pending_canonicalization(builder, intrinsics, cmp, cmp_info); + let new = apply_pending_canonicalization(builder, intrinsics, new, new_info); let (cmp, new) = (cmp.into_int_value(), new.into_int_value()); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6330,20 +8139,30 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder .build_extract_value(old, 0, "") .unwrap() .into_int_value(); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw16UCmpxchg { ref memarg } => { - let (cmp, new) = state.pop2()?; + let ((cmp, cmp_info), (new, new_info)) = state.pop2_extra()?; + let cmp = apply_pending_canonicalization(builder, intrinsics, cmp, cmp_info); + let new = apply_pending_canonicalization(builder, intrinsics, new, new_info); let (cmp, new) = (cmp.into_int_value(), new.into_int_value()); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6372,20 +8191,30 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder .build_extract_value(old, 0, "") .unwrap() .into_int_value(); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmw32UCmpxchg { ref memarg } => { - let (cmp, new) = state.pop2()?; + let ((cmp, cmp_info), (new, new_info)) = state.pop2_extra()?; + let cmp = apply_pending_canonicalization(builder, intrinsics, cmp, cmp_info); + let new = apply_pending_canonicalization(builder, intrinsics, new, new_info); let (cmp, new) = (cmp.into_int_value(), new.into_int_value()); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6414,20 +8243,30 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder .build_extract_value(old, 0, "") .unwrap() .into_int_value(); let old = builder.build_int_z_extend(old, intrinsics.i64_ty, &state.var_name()); - state.push1(old); + state.push1_extra(old, ExtraInfo::arithmetic_f64()); } Operator::I64AtomicRmwCmpxchg { ref memarg } => { - let (cmp, new) = state.pop2()?; + let ((cmp, cmp_info), (new, new_info)) = state.pop2_extra()?; + let cmp = apply_pending_canonicalization(builder, intrinsics, cmp, cmp_info); + let new = apply_pending_canonicalization(builder, intrinsics, new, new_info); let (cmp, new) = (cmp.into_int_value(), new.into_int_value()); let effective_address = resolve_memory_ptr( builder, intrinsics, context, + self.module.clone(), &function, &mut state, &mut ctx, @@ -6452,6 +8291,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { AtomicOrdering::SequentiallyConsistent, ) .unwrap(); + tbaa_label( + &self.module, + intrinsics, + "memory", + old.as_instruction_value().unwrap(), + Some(0), + ); let old = builder.build_extract_value(old, 0, "").unwrap(); state.push1(old); } @@ -6523,7 +8369,9 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { state.push1(result.try_as_basic_value().left().unwrap()); } _ => { - unimplemented!("{:?}", op); + return Err(CodegenError { + message: format!("Operator {:?} unimplemented", op), + }); } } @@ -6531,16 +8379,32 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { } fn finalize(&mut self) -> Result<(), CodegenError> { - let results = self.state.popn_save(self.func_sig.returns().len())?; + let results = self.state.popn_save_extra(self.func_sig.returns().len())?; match results.as_slice() { [] => { self.builder.as_ref().unwrap().build_return(None); } - [one_value] => { - self.builder.as_ref().unwrap().build_return(Some(one_value)); + [(one_value, one_value_info)] => { + let builder = self.builder.as_ref().unwrap(); + let intrinsics = self.intrinsics.as_ref().unwrap(); + let one_value = apply_pending_canonicalization( + builder, + intrinsics, + *one_value, + *one_value_info, + ); + builder.build_return(Some(&builder.build_bitcast( + one_value.as_basic_value_enum(), + type_to_llvm_int_only(intrinsics, self.func_sig.returns()[0]), + "return", + ))); + } + _ => { + return Err(CodegenError { + message: "multi-value returns not yet implemented".to_string(), + }); } - _ => unimplemented!("multi-value returns not yet implemented"), } Ok(()) } @@ -6554,12 +8418,76 @@ impl From for CodegenError { } } -impl ModuleCodeGenerator - for LLVMModuleCodeGenerator +impl Drop for LLVMModuleCodeGenerator<'_> { + fn drop(&mut self) { + // Ensure that all members of the context are dropped before we drop the context. + drop(self.builder.take()); + drop(self.intrinsics.take()); + self.functions.clear(); + self.signatures.clear(); + assert!( + Rc::strong_count(&*self.module) == 1, + "references to module live while dropping LLVMModuleCodeGenerator" + ); + unsafe { + ManuallyDrop::drop(&mut self.personality_func); + ManuallyDrop::drop(&mut self.module); + }; + let context = self.context.take(); + match context { + None => {} + Some(context_ref) => unsafe { + Box::from_raw(context_ref as *const Context as *mut Context); + }, + } + } +} + +impl<'ctx> ModuleCodeGenerator, LLVMBackend, CodegenError> + for LLVMModuleCodeGenerator<'ctx> { - fn new() -> LLVMModuleCodeGenerator { - let context = Context::create(); + fn new() -> LLVMModuleCodeGenerator<'ctx> { + Self::new_with_target(None, None, None) + } + + fn new_with_target( + triple: Option, + cpu_name: Option, + cpu_features: Option, + ) -> LLVMModuleCodeGenerator<'ctx> { + let context_ptr = Box::into_raw(Box::new(Context::create())); + let context = unsafe { &*context_ptr }; let module = context.create_module("module"); + + let triple = triple.unwrap_or(TargetMachine::get_default_triple().to_string()); + + match triple { + _ if triple.starts_with("x86") => Target::initialize_x86(&InitializationConfig { + asm_parser: true, + asm_printer: true, + base: true, + disassembler: true, + info: true, + machine_code: true, + }), + _ => unimplemented!("compile to target other than x86-64 is not supported"), + } + + let target = Target::from_triple(&triple).unwrap(); + let target_machine = target + .create_target_machine( + &triple, + &cpu_name.unwrap_or(TargetMachine::get_host_cpu_name().to_string()), + &cpu_features.unwrap_or(TargetMachine::get_host_cpu_features().to_string()), + OptimizationLevel::Aggressive, + RelocMode::Static, + CodeModel::Large, + ) + .unwrap(); + + module.set_target(&target); + module.set_data_layout(&target_machine.get_target_data().get_data_layout()); + let builder = context.create_builder(); let intrinsics = Intrinsics::declare(&module, &context); @@ -6570,19 +8498,21 @@ impl ModuleCodeGenerator Some(Linkage::External), ); - let signatures = Map::new(); - LLVMModuleCodeGenerator { context: Some(context), builder: Some(builder), intrinsics: Some(intrinsics), - module, + module: ManuallyDrop::new(Rc::new(RefCell::new(module))), functions: vec![], - signatures, + signatures: Map::new(), signatures_raw: Map::new(), function_signatures: None, + llvm_functions: Rc::new(RefCell::new(HashMap::new())), func_import_count: 0, - personality_func, + personality_func: ManuallyDrop::new(personality_func), + stackmaps: Rc::new(RefCell::new(StackmapRegistry::default())), + track_state: false, + target_machine, } } @@ -6597,7 +8527,7 @@ impl ModuleCodeGenerator fn next_function( &mut self, _module_info: Arc>, - ) -> Result<&mut LLVMFunctionCodeGenerator, CodegenError> { + ) -> Result<&mut LLVMFunctionCodeGenerator<'ctx>, CodegenError> { // Creates a new function and returns the function-scope code generator for it. let (context, builder, intrinsics) = match self.functions.last_mut() { Some(x) => ( @@ -6612,21 +8542,19 @@ impl ModuleCodeGenerator ), }; - let sig_id = self.function_signatures.as_ref().unwrap() - [FuncIndex::new(self.func_import_count + self.functions.len())]; + let func_index = FuncIndex::new(self.func_import_count + self.functions.len()); + let sig_id = self.function_signatures.as_ref().unwrap()[func_index]; let func_sig = self.signatures_raw[sig_id].clone(); - let function = self.module.add_function( - &format!("fn{}", self.func_import_count + self.functions.len()), - self.signatures[sig_id], - Some(Linkage::External), - ); - function.set_personality_function(self.personality_func); + let function = &self.llvm_functions.borrow_mut()[&func_index]; + function.set_personality_function(*self.personality_func); - let mut state = State::new(); - let entry_block = context.append_basic_block(&function, "entry"); + let mut state: State<'ctx> = State::new(); + let entry_block = context.append_basic_block(*function, "entry"); + let alloca_builder = context.create_builder(); + alloca_builder.position_at_end(&entry_block); - let return_block = context.append_basic_block(&function, "return"); + let return_block = context.append_basic_block(*function, "return"); builder.position_at_end(&return_block); let phis: SmallVec<[PhiValue; 1]> = func_sig @@ -6646,27 +8574,49 @@ impl ModuleCodeGenerator .skip(1) .enumerate() .map(|(index, param)| { - let ty = param.get_type(); - - let alloca = builder.build_alloca(ty, &format!("local{}", index)); - builder.build_store(alloca, param); + let real_ty = func_sig.params()[index]; + let real_ty_llvm = type_to_llvm(&intrinsics, real_ty); + let alloca = + alloca_builder.build_alloca(real_ty_llvm, &format!("local{}", index)); + builder.build_store( + alloca, + builder.build_bitcast(param, real_ty_llvm, &state.var_name()), + ); + if index == 0 { + alloca_builder.position_before( + &alloca + .as_instruction() + .unwrap() + .get_next_instruction() + .unwrap(), + ); + } alloca }), ); let num_params = locals.len(); + let local_func_index = self.functions.len(); + let code = LLVMFunctionCodeGenerator { state, context: Some(context), builder: Some(builder), + alloca_builder: Some(alloca_builder), intrinsics: Some(intrinsics), - function, + llvm_functions: self.llvm_functions.clone(), + function: *function, func_sig: func_sig, locals, signatures: self.signatures.clone(), num_params, ctx: None, unreachable_depth: 0, + stackmaps: self.stackmaps.clone(), + index: local_func_index, + opcode_offset: 0, + track_state: self.track_state, + module: (*self.module).clone(), }; self.functions.push(code); Ok(self.functions.last_mut().unwrap()) @@ -6695,43 +8645,77 @@ impl ModuleCodeGenerator generate_trampolines( module_info, &self.signatures, - &self.module, + &self.module.borrow_mut(), self.context.as_ref().unwrap(), self.builder.as_ref().unwrap(), self.intrinsics.as_ref().unwrap(), - ); + ) + .map_err(|e| CodegenError { + message: format!("trampolines generation error: {:?}", e), + })?; if let Some(path) = unsafe { &crate::GLOBAL_OPTIONS.pre_opt_ir } { - self.module.print_to_file(path).unwrap(); + self.module.borrow_mut().print_to_file(path).unwrap(); } let pass_manager = PassManager::create(()); - if cfg!(test) { - pass_manager.add_verifier_pass(); - } + + #[cfg(feature = "test")] + pass_manager.add_verifier_pass(); + + pass_manager.add_type_based_alias_analysis_pass(); + pass_manager.add_ipsccp_pass(); + pass_manager.add_prune_eh_pass(); + pass_manager.add_dead_arg_elimination_pass(); + pass_manager.add_function_inlining_pass(); pass_manager.add_lower_expect_intrinsic_pass(); pass_manager.add_scalar_repl_aggregates_pass(); pass_manager.add_instruction_combining_pass(); - pass_manager.add_cfg_simplification_pass(); - pass_manager.add_gvn_pass(); pass_manager.add_jump_threading_pass(); pass_manager.add_correlated_value_propagation_pass(); - pass_manager.add_sccp_pass(); + pass_manager.add_cfg_simplification_pass(); + pass_manager.add_reassociate_pass(); + pass_manager.add_loop_rotate_pass(); + pass_manager.add_loop_unswitch_pass(); + pass_manager.add_ind_var_simplify_pass(); + pass_manager.add_licm_pass(); + pass_manager.add_loop_vectorize_pass(); + pass_manager.add_instruction_combining_pass(); + pass_manager.add_ipsccp_pass(); + pass_manager.add_reassociate_pass(); + pass_manager.add_cfg_simplification_pass(); + pass_manager.add_gvn_pass(); + pass_manager.add_memcpy_optimize_pass(); + pass_manager.add_dead_store_elimination_pass(); + pass_manager.add_bit_tracking_dce_pass(); pass_manager.add_instruction_combining_pass(); pass_manager.add_reassociate_pass(); pass_manager.add_cfg_simplification_pass(); - pass_manager.add_bit_tracking_dce_pass(); pass_manager.add_slp_vectorize_pass(); - pass_manager.run_on(&self.module); + pass_manager.add_early_cse_pass(); + pass_manager.run_on(&*self.module.borrow_mut()); if let Some(path) = unsafe { &crate::GLOBAL_OPTIONS.post_opt_ir } { - self.module.print_to_file(path).unwrap(); + self.module.borrow_mut().print_to_file(path).unwrap(); } - let (backend, cache_gen) = LLVMBackend::new(self.module, self.intrinsics.take().unwrap()); + let stackmaps = self.stackmaps.borrow(); + + let (backend, cache_gen) = LLVMBackend::new( + (*self.module).clone(), + self.intrinsics.take().unwrap(), + &*stackmaps, + module_info, + &self.target_machine, + ); Ok((backend, Box::new(cache_gen))) } + fn feed_compiler_config(&mut self, config: &CompilerConfig) -> Result<(), CodegenError> { + self.track_state = config.track_state; + Ok(()) + } + fn feed_signatures(&mut self, signatures: Map) -> Result<(), CodegenError> { self.signatures = signatures .iter() @@ -6740,6 +8724,7 @@ impl ModuleCodeGenerator self.context.as_ref().unwrap(), self.intrinsics.as_ref().unwrap(), sig, + type_to_llvm_int_only, ) }) .collect(); @@ -6751,6 +8736,16 @@ impl ModuleCodeGenerator &mut self, assoc: Map, ) -> Result<(), CodegenError> { + for (index, sig_id) in &assoc { + if index.index() >= self.func_import_count { + let function = self.module.borrow_mut().add_function( + &format!("fn{}", index.index()), + self.signatures[*sig_id], + Some(Linkage::External), + ); + self.llvm_functions.borrow_mut().insert(index, function); + } + } self.function_signatures = Some(Arc::new(assoc)); Ok(()) } @@ -6773,3 +8768,15 @@ impl ModuleCodeGenerator }) } } + +fn is_f32_arithmetic(bits: u32) -> bool { + // Mask off sign bit. + let bits = bits & 0x7FFF_FFFF; + bits < 0x7FC0_0000 +} + +fn is_f64_arithmetic(bits: u64) -> bool { + // Mask off sign bit. + let bits = bits & 0x7FFF_FFFF_FFFF_FFFF; + bits < 0x7FF8_0000_0000_0000 +} diff --git a/lib/llvm-backend/src/intrinsics.rs b/lib/llvm-backend/src/intrinsics.rs index 22f1b3cfd..c983e4175 100644 --- a/lib/llvm-backend/src/intrinsics.rs +++ b/lib/llvm-backend/src/intrinsics.rs @@ -3,26 +3,28 @@ use inkwell::{ builder::Builder, context::Context, module::Module, - types::{ - BasicType, FloatType, FunctionType, IntType, PointerType, StructType, VectorType, VoidType, + types::{BasicType, FloatType, IntType, PointerType, StructType, VectorType, VoidType}, + values::{ + BasicValue, BasicValueEnum, FloatValue, FunctionValue, InstructionValue, IntValue, + PointerValue, VectorValue, }, - values::{BasicValue, BasicValueEnum, FloatValue, FunctionValue, IntValue, PointerValue}, AddressSpace, }; +use std::cell::RefCell; use std::collections::HashMap; -use std::marker::PhantomData; +use std::rc::Rc; use wasmer_runtime_core::{ memory::MemoryType, module::ModuleInfo, structures::TypedIndex, types::{ - GlobalIndex, ImportedFuncIndex, LocalFuncIndex, LocalOrImport, MemoryIndex, SigIndex, - TableIndex, Type, + GlobalIndex, ImportedFuncIndex, LocalOrImport, MemoryIndex, SigIndex, TableIndex, Type, }, + units::Pages, vm::{Ctx, INTERNALS_SIZE}, }; -fn type_to_llvm_ptr(intrinsics: &Intrinsics, ty: Type) -> PointerType { +fn type_to_llvm_ptr<'ctx>(intrinsics: &Intrinsics<'ctx>, ty: Type) -> PointerType<'ctx> { match ty { Type::I32 => intrinsics.i32_ptr_ty, Type::I64 => intrinsics.i64_ptr_ty, @@ -32,130 +34,124 @@ fn type_to_llvm_ptr(intrinsics: &Intrinsics, ty: Type) -> PointerType { } } -pub struct Intrinsics { - pub ctlz_i32: FunctionValue, - pub ctlz_i64: FunctionValue, +pub struct Intrinsics<'ctx> { + pub ctlz_i32: FunctionValue<'ctx>, + pub ctlz_i64: FunctionValue<'ctx>, - pub cttz_i32: FunctionValue, - pub cttz_i64: FunctionValue, + pub cttz_i32: FunctionValue<'ctx>, + pub cttz_i64: FunctionValue<'ctx>, - pub ctpop_i32: FunctionValue, - pub ctpop_i64: FunctionValue, + pub ctpop_i32: FunctionValue<'ctx>, + pub ctpop_i64: FunctionValue<'ctx>, - pub sqrt_f32: FunctionValue, - pub sqrt_f64: FunctionValue, - pub sqrt_f32x4: FunctionValue, - pub sqrt_f64x2: FunctionValue, + pub sqrt_f32: FunctionValue<'ctx>, + pub sqrt_f64: FunctionValue<'ctx>, + pub sqrt_f32x4: FunctionValue<'ctx>, + pub sqrt_f64x2: FunctionValue<'ctx>, - pub minimum_f32: FunctionValue, - pub minimum_f64: FunctionValue, - pub minimum_f32x4: FunctionValue, - pub minimum_f64x2: FunctionValue, + pub ceil_f32: FunctionValue<'ctx>, + pub ceil_f64: FunctionValue<'ctx>, - pub maximum_f32: FunctionValue, - pub maximum_f64: FunctionValue, - pub maximum_f32x4: FunctionValue, - pub maximum_f64x2: FunctionValue, + pub floor_f32: FunctionValue<'ctx>, + pub floor_f64: FunctionValue<'ctx>, - pub ceil_f32: FunctionValue, - pub ceil_f64: FunctionValue, + pub trunc_f32: FunctionValue<'ctx>, + pub trunc_f64: FunctionValue<'ctx>, - pub floor_f32: FunctionValue, - pub floor_f64: FunctionValue, + pub nearbyint_f32: FunctionValue<'ctx>, + pub nearbyint_f64: FunctionValue<'ctx>, - pub trunc_f32: FunctionValue, - pub trunc_f64: FunctionValue, + pub fabs_f32: FunctionValue<'ctx>, + pub fabs_f64: FunctionValue<'ctx>, + pub fabs_f32x4: FunctionValue<'ctx>, + pub fabs_f64x2: FunctionValue<'ctx>, - pub nearbyint_f32: FunctionValue, - pub nearbyint_f64: FunctionValue, + pub copysign_f32: FunctionValue<'ctx>, + pub copysign_f64: FunctionValue<'ctx>, - pub fabs_f32: FunctionValue, - pub fabs_f64: FunctionValue, - pub fabs_f32x4: FunctionValue, - pub fabs_f64x2: FunctionValue, + pub sadd_sat_i8x16: FunctionValue<'ctx>, + pub sadd_sat_i16x8: FunctionValue<'ctx>, + pub uadd_sat_i8x16: FunctionValue<'ctx>, + pub uadd_sat_i16x8: FunctionValue<'ctx>, - pub copysign_f32: FunctionValue, - pub copysign_f64: FunctionValue, + pub ssub_sat_i8x16: FunctionValue<'ctx>, + pub ssub_sat_i16x8: FunctionValue<'ctx>, + pub usub_sat_i8x16: FunctionValue<'ctx>, + pub usub_sat_i16x8: FunctionValue<'ctx>, - pub sadd_sat_i8x16: FunctionValue, - pub sadd_sat_i16x8: FunctionValue, - pub uadd_sat_i8x16: FunctionValue, - pub uadd_sat_i16x8: FunctionValue, + pub expect_i1: FunctionValue<'ctx>, + pub trap: FunctionValue<'ctx>, - pub ssub_sat_i8x16: FunctionValue, - pub ssub_sat_i16x8: FunctionValue, - pub usub_sat_i8x16: FunctionValue, - pub usub_sat_i16x8: FunctionValue, + pub void_ty: VoidType<'ctx>, + pub i1_ty: IntType<'ctx>, + pub i8_ty: IntType<'ctx>, + pub i16_ty: IntType<'ctx>, + pub i32_ty: IntType<'ctx>, + pub i64_ty: IntType<'ctx>, + pub i128_ty: IntType<'ctx>, + pub f32_ty: FloatType<'ctx>, + pub f64_ty: FloatType<'ctx>, - pub expect_i1: FunctionValue, - pub trap: FunctionValue, + pub i1x128_ty: VectorType<'ctx>, + pub i8x16_ty: VectorType<'ctx>, + pub i16x8_ty: VectorType<'ctx>, + pub i32x4_ty: VectorType<'ctx>, + pub i64x2_ty: VectorType<'ctx>, + pub f32x4_ty: VectorType<'ctx>, + pub f64x2_ty: VectorType<'ctx>, - pub void_ty: VoidType, - pub i1_ty: IntType, - pub i8_ty: IntType, - pub i16_ty: IntType, - pub i32_ty: IntType, - pub i64_ty: IntType, - pub i128_ty: IntType, - pub f32_ty: FloatType, - pub f64_ty: FloatType, + pub i8_ptr_ty: PointerType<'ctx>, + pub i16_ptr_ty: PointerType<'ctx>, + pub i32_ptr_ty: PointerType<'ctx>, + pub i64_ptr_ty: PointerType<'ctx>, + pub i128_ptr_ty: PointerType<'ctx>, + pub f32_ptr_ty: PointerType<'ctx>, + pub f64_ptr_ty: PointerType<'ctx>, - pub i1x128_ty: VectorType, - pub i8x16_ty: VectorType, - pub i16x8_ty: VectorType, - pub i32x4_ty: VectorType, - pub i64x2_ty: VectorType, - pub f32x4_ty: VectorType, - pub f64x2_ty: VectorType, + pub anyfunc_ty: StructType<'ctx>, - pub i8_ptr_ty: PointerType, - pub i16_ptr_ty: PointerType, - pub i32_ptr_ty: PointerType, - pub i64_ptr_ty: PointerType, - pub i128_ptr_ty: PointerType, - pub f32_ptr_ty: PointerType, - pub f64_ptr_ty: PointerType, + pub i1_zero: IntValue<'ctx>, + pub i8_zero: IntValue<'ctx>, + pub i32_zero: IntValue<'ctx>, + pub i64_zero: IntValue<'ctx>, + pub i128_zero: IntValue<'ctx>, + pub f32_zero: FloatValue<'ctx>, + pub f64_zero: FloatValue<'ctx>, + pub f32x4_zero: VectorValue<'ctx>, + pub f64x2_zero: VectorValue<'ctx>, - pub anyfunc_ty: StructType, - - pub i1_zero: IntValue, - pub i8_zero: IntValue, - pub i32_zero: IntValue, - pub i64_zero: IntValue, - pub i128_zero: IntValue, - pub f32_zero: FloatValue, - pub f64_zero: FloatValue, - - pub trap_unreachable: BasicValueEnum, - pub trap_call_indirect_sig: BasicValueEnum, - pub trap_call_indirect_oob: BasicValueEnum, - pub trap_memory_oob: BasicValueEnum, - pub trap_illegal_arithmetic: BasicValueEnum, - pub trap_misaligned_atomic: BasicValueEnum, + pub trap_unreachable: BasicValueEnum<'ctx>, + pub trap_call_indirect_sig: BasicValueEnum<'ctx>, + pub trap_call_indirect_oob: BasicValueEnum<'ctx>, + pub trap_memory_oob: BasicValueEnum<'ctx>, + pub trap_illegal_arithmetic: BasicValueEnum<'ctx>, + pub trap_misaligned_atomic: BasicValueEnum<'ctx>, // VM intrinsics. - pub memory_grow_dynamic_local: FunctionValue, - pub memory_grow_static_local: FunctionValue, - pub memory_grow_shared_local: FunctionValue, - pub memory_grow_dynamic_import: FunctionValue, - pub memory_grow_static_import: FunctionValue, - pub memory_grow_shared_import: FunctionValue, + pub memory_grow_dynamic_local: FunctionValue<'ctx>, + pub memory_grow_static_local: FunctionValue<'ctx>, + pub memory_grow_shared_local: FunctionValue<'ctx>, + pub memory_grow_dynamic_import: FunctionValue<'ctx>, + pub memory_grow_static_import: FunctionValue<'ctx>, + pub memory_grow_shared_import: FunctionValue<'ctx>, - pub memory_size_dynamic_local: FunctionValue, - pub memory_size_static_local: FunctionValue, - pub memory_size_shared_local: FunctionValue, - pub memory_size_dynamic_import: FunctionValue, - pub memory_size_static_import: FunctionValue, - pub memory_size_shared_import: FunctionValue, + pub memory_size_dynamic_local: FunctionValue<'ctx>, + pub memory_size_static_local: FunctionValue<'ctx>, + pub memory_size_shared_local: FunctionValue<'ctx>, + pub memory_size_dynamic_import: FunctionValue<'ctx>, + pub memory_size_static_import: FunctionValue<'ctx>, + pub memory_size_shared_import: FunctionValue<'ctx>, - pub throw_trap: FunctionValue, - pub throw_breakpoint: FunctionValue, + pub throw_trap: FunctionValue<'ctx>, + pub throw_breakpoint: FunctionValue<'ctx>, - pub ctx_ptr_ty: PointerType, + pub experimental_stackmap: FunctionValue<'ctx>, + + pub ctx_ptr_ty: PointerType<'ctx>, } -impl Intrinsics { - pub fn declare(module: &Module, context: &Context) -> Self { +impl<'ctx> Intrinsics<'ctx> { + pub fn declare(module: &Module<'ctx>, context: &'ctx Context) -> Self { let void_ty = context.void_type(); let i1_ty = context.bool_type(); let i8_ty = context.i8_type(); @@ -189,6 +185,8 @@ impl Intrinsics { let i128_zero = i128_ty.const_int(0, false); let f32_zero = f32_ty.const_float(0.0); let f64_zero = f64_ty.const_float(0.0); + let f32x4_zero = f32x4_ty.const_zero(); + let f64x2_zero = f64x2_ty.const_zero(); let i1_ty_basic = i1_ty.as_basic_type_enum(); let i32_ty_basic = i32_ty.as_basic_type_enum(); @@ -208,8 +206,13 @@ impl Intrinsics { context.struct_type(&[i8_ptr_ty_basic, i64_ty_basic, i8_ptr_ty_basic], false); let local_table_ty = local_memory_ty; let local_global_ty = i64_ty; - let imported_func_ty = - context.struct_type(&[i8_ptr_ty_basic, ctx_ptr_ty.as_basic_type_enum()], false); + let func_ctx_ty = + context.struct_type(&[ctx_ptr_ty.as_basic_type_enum(), i8_ptr_ty_basic], false); + let func_ctx_ptr_ty = func_ctx_ty.ptr_type(AddressSpace::Generic); + let imported_func_ty = context.struct_type( + &[i8_ptr_ty_basic, func_ctx_ptr_ty.as_basic_type_enum()], + false, + ); let sigindex_ty = i32_ty; let rt_intrinsics_ty = i8_ty; let stack_lower_bound_ty = i8_ty; @@ -301,8 +304,6 @@ impl Intrinsics { let ret_f32_take_f32_f32 = f32_ty.fn_type(&[f32_ty_basic, f32_ty_basic], false); let ret_f64_take_f64_f64 = f64_ty.fn_type(&[f64_ty_basic, f64_ty_basic], false); - let ret_f32x4_take_f32x4_f32x4 = f32x4_ty.fn_type(&[f32x4_ty_basic, f32x4_ty_basic], false); - let ret_f64x2_take_f64x2_f64x2 = f64x2_ty.fn_type(&[f64x2_ty_basic, f64x2_ty_basic], false); let ret_i32_take_ctx_i32_i32 = i32_ty.fn_type( &[ctx_ptr_ty.as_basic_type_enum(), i32_ty_basic, i32_ty_basic], @@ -327,32 +328,6 @@ impl Intrinsics { sqrt_f32x4: module.add_function("llvm.sqrt.v4f32", ret_f32x4_take_f32x4, None), sqrt_f64x2: module.add_function("llvm.sqrt.v2f64", ret_f64x2_take_f64x2, None), - minimum_f32: module.add_function("llvm.minnum.f32", ret_f32_take_f32_f32, None), - minimum_f64: module.add_function("llvm.minnum.f64", ret_f64_take_f64_f64, None), - minimum_f32x4: module.add_function( - "llvm.minnum.v4f32", - ret_f32x4_take_f32x4_f32x4, - None, - ), - minimum_f64x2: module.add_function( - "llvm.minnum.v2f64", - ret_f64x2_take_f64x2_f64x2, - None, - ), - - maximum_f32: module.add_function("llvm.maxnum.f32", ret_f32_take_f32_f32, None), - maximum_f64: module.add_function("llvm.maxnum.f64", ret_f64_take_f64_f64, None), - maximum_f32x4: module.add_function( - "llvm.maxnum.v4f32", - ret_f32x4_take_f32x4_f32x4, - None, - ), - maximum_f64x2: module.add_function( - "llvm.maxnum.v2f64", - ret_f64x2_take_f64x2_f64x2, - None, - ), - ceil_f32: module.add_function("llvm.ceil.f32", ret_f32_take_f32, None), ceil_f64: module.add_function("llvm.ceil.f64", ret_f64_take_f64, None), @@ -453,6 +428,8 @@ impl Intrinsics { i128_zero, f32_zero, f64_zero, + f32x4_zero, + f64x2_zero, trap_unreachable: i32_zero.as_basic_value_enum(), trap_call_indirect_sig: i32_ty.const_int(1, false).as_basic_value_enum(), @@ -528,6 +505,17 @@ impl Intrinsics { void_ty.fn_type(&[i32_ty_basic], false), None, ), + experimental_stackmap: module.add_function( + "llvm.experimental.stackmap", + void_ty.fn_type( + &[ + i64_ty_basic, /* id */ + i32_ty_basic, /* numShadowBytes */ + ], + true, + ), + None, + ), throw_breakpoint: module.add_function( "vm.breakpoint", void_ty.fn_type(&[i64_ty_basic], false), @@ -571,81 +559,111 @@ impl Intrinsics { } #[derive(Clone, Copy)] -pub enum MemoryCache { +pub enum MemoryCache<'ctx> { /// The memory moves around. Dynamic { - ptr_to_base_ptr: PointerValue, - ptr_to_bounds: PointerValue, + ptr_to_base_ptr: PointerValue<'ctx>, + ptr_to_bounds: PointerValue<'ctx>, + minimum: Pages, + maximum: Option, }, /// The memory is always in the same place. Static { - base_ptr: PointerValue, - bounds: IntValue, + base_ptr: PointerValue<'ctx>, + bounds: IntValue<'ctx>, + minimum: Pages, + maximum: Option, }, } -struct TableCache { - ptr_to_base_ptr: PointerValue, - ptr_to_bounds: PointerValue, +struct TableCache<'ctx> { + ptr_to_base_ptr: PointerValue<'ctx>, + ptr_to_bounds: PointerValue<'ctx>, } #[derive(Clone, Copy)] -pub enum GlobalCache { - Mut { ptr_to_value: PointerValue }, - Const { value: BasicValueEnum }, +pub enum GlobalCache<'ctx> { + Mut { ptr_to_value: PointerValue<'ctx> }, + Const { value: BasicValueEnum<'ctx> }, } -struct ImportedFuncCache { - func_ptr: PointerValue, - ctx_ptr: PointerValue, +struct ImportedFuncCache<'ctx> { + func_ptr: PointerValue<'ctx>, + ctx_ptr: PointerValue<'ctx>, } -pub struct CtxType<'a> { - ctx_ptr_value: PointerValue, +pub struct CtxType<'a, 'ctx> { + ctx_ptr_value: PointerValue<'ctx>, info: &'a ModuleInfo, - cache_builder: Builder, + cache_builder: Builder<'ctx>, - cached_memories: HashMap, - cached_tables: HashMap, - cached_sigindices: HashMap, - cached_globals: HashMap, - cached_imported_functions: HashMap, + cached_signal_mem: Option>, - _phantom: PhantomData<&'a FunctionValue>, + cached_memories: HashMap>, + cached_tables: HashMap>, + cached_sigindices: HashMap>, + cached_globals: HashMap>, + cached_imported_functions: HashMap>, } fn offset_to_index(offset: u8) -> u32 { (offset as usize / ::std::mem::size_of::()) as u32 } -impl<'a> CtxType<'a> { +impl<'a, 'ctx> CtxType<'a, 'ctx> { pub fn new( info: &'a ModuleInfo, - func_value: &'a FunctionValue, - cache_builder: Builder, - ) -> CtxType<'a> { + func_value: &FunctionValue<'ctx>, + cache_builder: Builder<'ctx>, + ) -> CtxType<'a, 'ctx> { CtxType { ctx_ptr_value: func_value.get_nth_param(0).unwrap().into_pointer_value(), info, cache_builder, + cached_signal_mem: None, + cached_memories: HashMap::new(), cached_tables: HashMap::new(), cached_sigindices: HashMap::new(), cached_globals: HashMap::new(), cached_imported_functions: HashMap::new(), - - _phantom: PhantomData, } } - pub fn basic(&self) -> BasicValueEnum { + pub fn basic(&self) -> BasicValueEnum<'ctx> { self.ctx_ptr_value.as_basic_value_enum() } - pub fn memory(&mut self, index: MemoryIndex, intrinsics: &Intrinsics) -> MemoryCache { + pub fn signal_mem(&mut self) -> PointerValue<'ctx> { + if let Some(x) = self.cached_signal_mem { + return x; + } + + let (ctx_ptr_value, cache_builder) = (self.ctx_ptr_value, &self.cache_builder); + + let ptr_ptr = unsafe { + cache_builder.build_struct_gep( + ctx_ptr_value, + offset_to_index(Ctx::offset_interrupt_signal_mem()), + "interrupt_signal_mem_ptr", + ) + }; + let ptr = cache_builder + .build_load(ptr_ptr, "interrupt_signal_mem") + .into_pointer_value(); + self.cached_signal_mem = Some(ptr); + ptr + } + + pub fn memory( + &mut self, + index: MemoryIndex, + intrinsics: &Intrinsics<'ctx>, + module: Rc>>, + ) -> MemoryCache<'ctx> { let (cached_memories, info, ctx_ptr_value, cache_builder) = ( &mut self.cached_memories, self.info, @@ -654,34 +672,48 @@ impl<'a> CtxType<'a> { ); *cached_memories.entry(index).or_insert_with(|| { - let (memory_array_ptr_ptr, index, memory_type) = match index.local_or_import(info) { - LocalOrImport::Local(local_mem_index) => ( - unsafe { - cache_builder.build_struct_gep( - ctx_ptr_value, - offset_to_index(Ctx::offset_memories()), - "memory_array_ptr_ptr", - ) - }, - local_mem_index.index() as u64, - info.memories[local_mem_index].memory_type(), - ), - LocalOrImport::Import(import_mem_index) => ( - unsafe { - cache_builder.build_struct_gep( - ctx_ptr_value, - offset_to_index(Ctx::offset_imported_memories()), - "memory_array_ptr_ptr", - ) - }, - import_mem_index.index() as u64, - info.imported_memories[import_mem_index].1.memory_type(), - ), - }; + let (memory_array_ptr_ptr, index, memory_type, minimum, maximum, field_name) = + match index.local_or_import(info) { + LocalOrImport::Local(local_mem_index) => ( + unsafe { + cache_builder.build_struct_gep( + ctx_ptr_value, + offset_to_index(Ctx::offset_memories()), + "memory_array_ptr_ptr", + ) + }, + local_mem_index.index() as u64, + info.memories[local_mem_index].memory_type(), + info.memories[local_mem_index].minimum, + info.memories[local_mem_index].maximum, + "context_field_ptr_to_local_memory", + ), + LocalOrImport::Import(import_mem_index) => ( + unsafe { + cache_builder.build_struct_gep( + ctx_ptr_value, + offset_to_index(Ctx::offset_imported_memories()), + "memory_array_ptr_ptr", + ) + }, + import_mem_index.index() as u64, + info.imported_memories[import_mem_index].1.memory_type(), + info.imported_memories[import_mem_index].1.minimum, + info.imported_memories[import_mem_index].1.maximum, + "context_field_ptr_to_imported_memory", + ), + }; let memory_array_ptr = cache_builder .build_load(memory_array_ptr_ptr, "memory_array_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + field_name, + memory_array_ptr.as_instruction_value().unwrap(), + None, + ); let const_index = intrinsics.i32_ty.const_int(index, false); let memory_ptr_ptr = unsafe { cache_builder.build_in_bounds_gep( @@ -693,6 +725,13 @@ impl<'a> CtxType<'a> { let memory_ptr = cache_builder .build_load(memory_ptr_ptr, "memory_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + "memory_ptr", + memory_ptr.as_instruction_value().unwrap(), + Some(index as u32), + ); let (ptr_to_base_ptr, ptr_to_bounds) = unsafe { ( @@ -705,25 +744,47 @@ impl<'a> CtxType<'a> { MemoryType::Dynamic => MemoryCache::Dynamic { ptr_to_base_ptr, ptr_to_bounds, + minimum, + maximum, }, - MemoryType::Static | MemoryType::SharedStatic => MemoryCache::Static { - base_ptr: cache_builder + MemoryType::Static | MemoryType::SharedStatic => { + let base_ptr = cache_builder .build_load(ptr_to_base_ptr, "base") - .into_pointer_value(), - bounds: cache_builder + .into_pointer_value(); + let bounds = cache_builder .build_load(ptr_to_bounds, "bounds") - .into_int_value(), - }, + .into_int_value(); + tbaa_label( + &module, + intrinsics, + "static_memory_base", + base_ptr.as_instruction_value().unwrap(), + Some(index as u32), + ); + tbaa_label( + &module, + intrinsics, + "static_memory_bounds", + bounds.as_instruction_value().unwrap(), + Some(index as u32), + ); + MemoryCache::Static { + base_ptr, + bounds, + minimum, + maximum, + } + } } }) } - pub fn table( + pub fn table_prepare( &mut self, index: TableIndex, - intrinsics: &Intrinsics, - builder: &Builder, - ) -> (PointerValue, IntValue) { + intrinsics: &Intrinsics<'ctx>, + module: Rc>>, + ) -> (PointerValue<'ctx>, PointerValue<'ctx>) { let (cached_tables, info, ctx_ptr_value, cache_builder) = ( &mut self.cached_tables, self.info, @@ -735,7 +796,7 @@ impl<'a> CtxType<'a> { ptr_to_base_ptr, ptr_to_bounds, } = *cached_tables.entry(index).or_insert_with(|| { - let (table_array_ptr_ptr, index) = match index.local_or_import(info) { + let (table_array_ptr_ptr, index, field_name) = match index.local_or_import(info) { LocalOrImport::Local(local_table_index) => ( unsafe { cache_builder.build_struct_gep( @@ -745,6 +806,7 @@ impl<'a> CtxType<'a> { ) }, local_table_index.index() as u64, + "context_field_ptr_to_local_table", ), LocalOrImport::Import(import_table_index) => ( unsafe { @@ -755,12 +817,20 @@ impl<'a> CtxType<'a> { ) }, import_table_index.index() as u64, + "context_field_ptr_to_import_table", ), }; let table_array_ptr = cache_builder .build_load(table_array_ptr_ptr, "table_array_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + field_name, + table_array_ptr.as_instruction_value().unwrap(), + None, + ); let const_index = intrinsics.i32_ty.const_int(index, false); let table_ptr_ptr = unsafe { cache_builder.build_in_bounds_gep(table_array_ptr, &[const_index], "table_ptr_ptr") @@ -768,6 +838,13 @@ impl<'a> CtxType<'a> { let table_ptr = cache_builder .build_load(table_ptr_ptr, "table_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + "table_ptr", + table_array_ptr.as_instruction_value().unwrap(), + Some(index as u32), + ); let (ptr_to_base_ptr, ptr_to_bounds) = unsafe { ( @@ -782,49 +859,44 @@ impl<'a> CtxType<'a> { } }); - ( - builder - .build_load(ptr_to_base_ptr, "base_ptr") - .into_pointer_value(), - builder.build_load(ptr_to_bounds, "bounds").into_int_value(), - ) + (ptr_to_base_ptr, ptr_to_bounds) } - pub fn local_func( + pub fn table( &mut self, - index: LocalFuncIndex, - fn_ty: FunctionType, - intrinsics: &Intrinsics, - builder: &Builder, - ) -> PointerValue { - let local_func_array_ptr_ptr = unsafe { - builder.build_struct_gep( - self.ctx_ptr_value, - offset_to_index(Ctx::offset_local_functions()), - "local_func_array_ptr_ptr", - ) - }; - let local_func_array_ptr = builder - .build_load(local_func_array_ptr_ptr, "local_func_array_ptr") + index: TableIndex, + intrinsics: &Intrinsics<'ctx>, + module: Rc>>, + builder: &Builder<'ctx>, + ) -> (PointerValue<'ctx>, IntValue<'ctx>) { + let (ptr_to_base_ptr, ptr_to_bounds) = + self.table_prepare(index, intrinsics, module.clone()); + let base_ptr = builder + .build_load(ptr_to_base_ptr, "base_ptr") .into_pointer_value(); - let local_func_ptr_ptr = unsafe { - builder.build_in_bounds_gep( - local_func_array_ptr, - &[intrinsics.i32_ty.const_int(index.index() as u64, false)], - "local_func_ptr_ptr", - ) - }; - let local_func_ptr = builder - .build_load(local_func_ptr_ptr, "local_func_ptr") - .into_pointer_value(); - builder.build_pointer_cast( - local_func_ptr, - fn_ty.ptr_type(AddressSpace::Generic), - "local_func_ptr", - ) + let bounds = builder.build_load(ptr_to_bounds, "bounds").into_int_value(); + tbaa_label( + &module, + intrinsics, + "table_base_ptr", + base_ptr.as_instruction_value().unwrap(), + Some(index.index() as u32), + ); + tbaa_label( + &module, + intrinsics, + "table_bounds", + bounds.as_instruction_value().unwrap(), + Some(index.index() as u32), + ); + (base_ptr, bounds) } - pub fn dynamic_sigindex(&mut self, index: SigIndex, intrinsics: &Intrinsics) -> IntValue { + pub fn dynamic_sigindex( + &mut self, + index: SigIndex, + intrinsics: &Intrinsics<'ctx>, + ) -> IntValue<'ctx> { let (cached_sigindices, ctx_ptr_value, cache_builder) = ( &mut self.cached_sigindices, self.ctx_ptr_value, @@ -858,7 +930,12 @@ impl<'a> CtxType<'a> { }) } - pub fn global_cache(&mut self, index: GlobalIndex, intrinsics: &Intrinsics) -> GlobalCache { + pub fn global_cache( + &mut self, + index: GlobalIndex, + intrinsics: &Intrinsics<'ctx>, + module: Rc>>, + ) -> GlobalCache<'ctx> { let (cached_globals, ctx_ptr_value, info, cache_builder) = ( &mut self.cached_globals, self.ctx_ptr_value, @@ -867,7 +944,7 @@ impl<'a> CtxType<'a> { ); *cached_globals.entry(index).or_insert_with(|| { - let (globals_array_ptr_ptr, index, mutable, wasmer_ty) = + let (globals_array_ptr_ptr, index, mutable, wasmer_ty, field_name) = match index.local_or_import(info) { LocalOrImport::Local(local_global_index) => { let desc = info.globals[local_global_index].desc; @@ -882,6 +959,7 @@ impl<'a> CtxType<'a> { local_global_index.index() as u64, desc.mutable, desc.ty, + "context_field_ptr_to_local_globals", ) } LocalOrImport::Import(import_global_index) => { @@ -897,6 +975,7 @@ impl<'a> CtxType<'a> { import_global_index.index() as u64, desc.mutable, desc.ty, + "context_field_ptr_to_imported_globals", ) } }; @@ -906,6 +985,13 @@ impl<'a> CtxType<'a> { let global_array_ptr = cache_builder .build_load(globals_array_ptr_ptr, "global_array_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + field_name, + global_array_ptr.as_instruction_value().unwrap(), + None, + ); let const_index = intrinsics.i32_ty.const_int(index, false); let global_ptr_ptr = unsafe { cache_builder.build_in_bounds_gep( @@ -917,6 +1003,13 @@ impl<'a> CtxType<'a> { let global_ptr = cache_builder .build_load(global_ptr_ptr, "global_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + "global_ptr", + global_ptr.as_instruction_value().unwrap(), + Some(index as u32), + ); let global_ptr_typed = cache_builder.build_pointer_cast(global_ptr, llvm_ptr_ty, "global_ptr_typed"); @@ -926,9 +1019,15 @@ impl<'a> CtxType<'a> { ptr_to_value: global_ptr_typed, } } else { - GlobalCache::Const { - value: cache_builder.build_load(global_ptr_typed, "global_value"), - } + let value = cache_builder.build_load(global_ptr_typed, "global_value"); + tbaa_label( + &module, + intrinsics, + "global", + value.as_instruction_value().unwrap(), + Some(index as u32), + ); + GlobalCache::Const { value } } }) } @@ -936,8 +1035,9 @@ impl<'a> CtxType<'a> { pub fn imported_func( &mut self, index: ImportedFuncIndex, - intrinsics: &Intrinsics, - ) -> (PointerValue, PointerValue) { + intrinsics: &Intrinsics<'ctx>, + module: Rc>>, + ) -> (PointerValue<'ctx>, PointerValue<'ctx>) { let (cached_imported_functions, ctx_ptr_value, cache_builder) = ( &mut self.cached_imported_functions, self.ctx_ptr_value, @@ -955,6 +1055,13 @@ impl<'a> CtxType<'a> { let func_array_ptr = cache_builder .build_load(func_array_ptr_ptr, "func_array_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + "context_field_ptr_to_imported_funcs", + func_array_ptr.as_instruction_value().unwrap(), + None, + ); let const_index = intrinsics.i32_ty.const_int(index.index() as u64, false); let imported_func_ptr = unsafe { cache_builder.build_in_bounds_gep( @@ -963,19 +1070,37 @@ impl<'a> CtxType<'a> { "imported_func_ptr", ) }; - let (func_ptr_ptr, ctx_ptr_ptr) = unsafe { + let (func_ptr_ptr, func_ctx_ptr_ptr) = unsafe { ( cache_builder.build_struct_gep(imported_func_ptr, 0, "func_ptr_ptr"), - cache_builder.build_struct_gep(imported_func_ptr, 1, "ctx_ptr_ptr"), + cache_builder.build_struct_gep(imported_func_ptr, 1, "func_ctx_ptr_ptr"), ) }; let func_ptr = cache_builder .build_load(func_ptr_ptr, "func_ptr") .into_pointer_value(); + let func_ctx_ptr = cache_builder + .build_load(func_ctx_ptr_ptr, "func_ctx_ptr") + .into_pointer_value(); + let ctx_ptr_ptr = unsafe { cache_builder.build_struct_gep(func_ctx_ptr, 0, "ctx_ptr") }; let ctx_ptr = cache_builder .build_load(ctx_ptr_ptr, "ctx_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + "imported_func_ptr", + func_ptr.as_instruction_value().unwrap(), + Some(index.index() as u32), + ); + tbaa_label( + &module, + intrinsics, + "imported_func_ctx_ptr", + ctx_ptr.as_instruction_value().unwrap(), + Some(index.index() as u32), + ); ImportedFuncCache { func_ptr, ctx_ptr } }); @@ -986,9 +1111,10 @@ impl<'a> CtxType<'a> { pub fn internal_field( &mut self, index: usize, - intrinsics: &Intrinsics, - builder: &Builder, - ) -> PointerValue { + intrinsics: &Intrinsics<'ctx>, + module: Rc>>, + builder: &Builder<'ctx>, + ) -> PointerValue<'ctx> { assert!(index < INTERNALS_SIZE); let local_internals_ptr_ptr = unsafe { @@ -1001,6 +1127,13 @@ impl<'a> CtxType<'a> { let local_internals_ptr = builder .build_load(local_internals_ptr_ptr, "local_internals_ptr") .into_pointer_value(); + tbaa_label( + &module, + intrinsics, + "context_field_ptr_to_internals", + local_internals_ptr.as_instruction_value().unwrap(), + None, + ); unsafe { builder.build_in_bounds_gep( local_internals_ptr, @@ -1010,3 +1143,89 @@ impl<'a> CtxType<'a> { } } } + +// Given an instruction that operates on memory, mark the access as not aliasing +// other memory accesses which have a different (label, index) pair. +pub fn tbaa_label<'ctx>( + module: &Rc>>, + intrinsics: &Intrinsics<'ctx>, + label: &str, + instruction: InstructionValue<'ctx>, + index: Option, +) { + // To convey to LLVM that two pointers must be pointing to distinct memory, + // we use LLVM's Type Based Aliasing Analysis, or TBAA, to mark the memory + // operations as having different types whose pointers may not alias. + // + // See the LLVM documentation at + // https://llvm.org/docs/LangRef.html#tbaa-metadata + // + // LLVM TBAA supports many features, but we use it in a simple way, with + // only scalar types that are children of the root node. Every TBAA type we + // declare is NoAlias with the others. See NoAlias, PartialAlias, + // MayAlias and MustAlias in the LLVM documentation: + // https://llvm.org/docs/AliasAnalysis.html#must-may-and-no-alias-responses + + let module = module.borrow_mut(); + let context = module.get_context(); + + // TODO: ContextRef can't return us the lifetime from module through Deref. + // This could be fixed once generic_associated_types is stable. + let context2 = &*context; + let context = unsafe { std::mem::transmute::<&Context, &'ctx Context>(context2) }; + std::mem::forget(context2); + + // `!wasmer_tbaa_root = {}`, the TBAA root node for wasmer. + let tbaa_root = module + .get_global_metadata("wasmer_tbaa_root") + .pop() + .unwrap_or_else(|| { + module.add_global_metadata("wasmer_tbaa_root", &context.metadata_node(&[])); + module.get_global_metadata("wasmer_tbaa_root")[0] + }); + + // Construct (or look up) the type descriptor, for example + // `!"local 0" = !{!"local 0", !wasmer_tbaa_root}`. + let label = if let Some(idx) = index { + format!("{}{}", label, idx) + } else { + label.to_string() + }; + let type_label = context.metadata_string(label.as_str()); + let type_tbaa = module + .get_global_metadata(label.as_str()) + .pop() + .unwrap_or_else(|| { + module.add_global_metadata( + label.as_str(), + &context.metadata_node(&[type_label.into(), tbaa_root.into()]), + ); + module.get_global_metadata(label.as_str())[0] + }); + + // Construct (or look up) the access tag, which is a struct of the form + // (base type, access type, offset). + // + // "If BaseTy is a scalar type, Offset must be 0 and BaseTy and AccessTy + // must be the same". + // -- https://llvm.org/docs/LangRef.html#tbaa-metadata + let label = label + "_memop"; + let type_tbaa = module + .get_global_metadata(label.as_str()) + .pop() + .unwrap_or_else(|| { + module.add_global_metadata( + label.as_str(), + &context.metadata_node(&[ + type_tbaa.into(), + type_tbaa.into(), + intrinsics.i64_zero.into(), + ]), + ); + module.get_global_metadata(label.as_str())[0] + }); + + // Attach the access tag to the instruction. + let tbaa_kind = context.get_kind_id("tbaa"); + instruction.set_metadata(type_tbaa, tbaa_kind); +} diff --git a/lib/llvm-backend/src/lib.rs b/lib/llvm-backend/src/lib.rs index 454fad100..2b3f7d2e6 100644 --- a/lib/llvm-backend/src/lib.rs +++ b/lib/llvm-backend/src/lib.rs @@ -1,5 +1,4 @@ #![deny( - dead_code, nonstandard_style, unused_imports, unused_mut, @@ -7,13 +6,17 @@ unused_unsafe, unreachable_patterns )] +#![cfg_attr(not(target_os = "windows"), deny(dead_code))] #![cfg_attr(nightly, feature(unwind_attributes))] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] mod backend; mod code; mod intrinsics; mod platform; mod read_info; +mod stackmap; mod state; mod structs; mod trampolines; @@ -26,8 +29,8 @@ pub use code::LLVMModuleCodeGenerator as ModuleCodeGenerator; use wasmer_runtime_core::codegen::SimpleStreamingCompilerGen; pub type LLVMCompiler = SimpleStreamingCompilerGen< - code::LLVMModuleCodeGenerator, - code::LLVMFunctionCodeGenerator, + code::LLVMModuleCodeGenerator<'static>, + code::LLVMFunctionCodeGenerator<'static>, backend::LLVMBackend, code::CodegenError, >; diff --git a/lib/llvm-backend/src/platform/unix.rs b/lib/llvm-backend/src/platform/unix.rs index a07afa130..88ceb6854 100644 --- a/lib/llvm-backend/src/platform/unix.rs +++ b/lib/llvm-backend/src/platform/unix.rs @@ -4,7 +4,9 @@ use libc::{ c_void, mmap, mprotect, munmap, siginfo_t, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_NONE, PROT_READ, PROT_WRITE, }; -use nix::sys::signal::{sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGBUS, SIGSEGV}; +use nix::sys::signal::{ + sigaction, SaFlags, SigAction, SigHandler, SigSet, SIGBUS, SIGILL, SIGSEGV, +}; use std::ptr; /// `__register_frame` and `__deregister_frame` on macos take a single fde as an @@ -57,6 +59,7 @@ pub unsafe fn install_signal_handler() { ); sigaction(SIGSEGV, &sa).unwrap(); sigaction(SIGBUS, &sa).unwrap(); + sigaction(SIGILL, &sa).unwrap(); } #[cfg_attr(nightly, unwind(allowed))] @@ -66,6 +69,9 @@ extern "C" fn signal_trap_handler( _ucontext: *mut c_void, ) { unsafe { + if SigSet::all().thread_unblock().is_err() { + std::process::abort(); + } // Apparently, we can unwind from arbitary instructions, as long // as we don't need to catch the exception inside the function that // was interrupted. diff --git a/lib/llvm-backend/src/stackmap.rs b/lib/llvm-backend/src/stackmap.rs new file mode 100644 index 000000000..4a9dbf816 --- /dev/null +++ b/lib/llvm-backend/src/stackmap.rs @@ -0,0 +1,568 @@ +// https://llvm.org/docs/StackMaps.html#stackmap-section + +use byteorder::{LittleEndian, ReadBytesExt}; +use std::io::{self, Cursor}; +use wasmer_runtime_core::vm::Ctx; +use wasmer_runtime_core::{ + module::ModuleInfo, + structures::TypedIndex, + types::{GlobalIndex, LocalOrImport, TableIndex}, +}; + +#[derive(Default, Debug, Clone)] +pub struct StackmapRegistry { + pub entries: Vec, +} + +#[derive(Debug, Clone)] +pub struct StackmapEntry { + pub kind: StackmapEntryKind, + pub local_function_id: usize, + pub opcode_offset: usize, + pub value_semantics: Vec, + pub local_count: usize, + pub stack_count: usize, + pub is_start: bool, +} + +#[derive(Debug, Clone)] +pub enum ValueSemantic { + WasmLocal(usize), + WasmStack(usize), + Ctx, + SignalMem, + PointerToMemoryBase, + PointerToMemoryBound, // 64-bit + MemoryBase, + MemoryBound, // 64-bit + PointerToGlobal(usize), + Global(usize), + PointerToTableBase, + PointerToTableBound, + ImportedFuncPointer(usize), + ImportedFuncCtx(usize), + DynamicSigindice(usize), +} + +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +pub enum StackmapEntryKind { + FunctionHeader, + Loop, + Call, + Trappable, +} + +impl StackmapEntry { + #[cfg(all(any(target_os = "linux", target_os = "macos"), target_arch = "x86_64"))] + pub fn populate_msm( + &self, + module_info: &ModuleInfo, + code_addr: usize, + llvm_map: &StackMap, + size_record: &StkSizeRecord, + map_record: &StkMapRecord, + end: Option<(&StackmapEntry, &StkMapRecord)>, + msm: &mut wasmer_runtime_core::state::ModuleStateMap, + ) { + use std::collections::{BTreeMap, HashMap}; + use wasmer_runtime_core::state::{ + x64::{new_machine_state, X64Register, GPR}, + FunctionStateMap, MachineStateDiff, MachineValue, OffsetInfo, RegisterIndex, + SuspendOffset, WasmAbstractValue, + }; + use wasmer_runtime_core::vm; + + let func_base_addr = (size_record.function_address as usize) + .checked_sub(code_addr) + .unwrap(); + let target_offset = func_base_addr + map_record.instruction_offset as usize; + assert!(self.is_start); + + if msm.local_functions.len() == self.local_function_id { + assert_eq!(self.kind, StackmapEntryKind::FunctionHeader); + msm.local_functions.insert( + target_offset, + FunctionStateMap::new(new_machine_state(), self.local_function_id, 0, vec![]), + ); + } else if msm.local_functions.len() == self.local_function_id + 1 { + } else { + panic!("unordered local functions"); + } + + let (_, fsm) = msm.local_functions.iter_mut().last().unwrap(); + + assert_eq!(self.value_semantics.len(), map_record.locations.len()); + + // System V requires 16-byte alignment before each call instruction. + // Considering the saved rbp we need to ensure the stack size % 16 always equals to 8. + assert!(size_record.stack_size % 16 == 8); + + // Layout begins just below saved rbp. (push rbp; mov rbp, rsp) + let mut machine_stack_half_layout: Vec = + vec![MachineValue::Undefined; (size_record.stack_size - 8) as usize / 4]; + let mut regs: Vec<(RegisterIndex, MachineValue)> = vec![]; + let mut stack_constants: HashMap = HashMap::new(); + + let mut prev_frame_diff: BTreeMap> = BTreeMap::new(); + + let mut wasm_locals: Vec = vec![]; + let mut wasm_stack: Vec = vec![]; + + for (i, loc) in map_record.locations.iter().enumerate() { + let mv = match self.value_semantics[i] { + ValueSemantic::WasmLocal(x) => { + if x != wasm_locals.len() { + panic!("unordered local values"); + } + wasm_locals.push(WasmAbstractValue::Runtime); + MachineValue::WasmLocal(x) + } + ValueSemantic::WasmStack(x) => { + if x != wasm_stack.len() { + panic!("unordered stack values"); + } + wasm_stack.push(WasmAbstractValue::Runtime); + MachineValue::WasmStack(x) + } + ValueSemantic::Ctx => MachineValue::Vmctx, + ValueSemantic::SignalMem => { + MachineValue::VmctxDeref(vec![Ctx::offset_interrupt_signal_mem() as usize, 0]) + } + ValueSemantic::PointerToMemoryBase => { + MachineValue::VmctxDeref(vec![Ctx::offset_memory_base() as usize]) + } + ValueSemantic::PointerToMemoryBound => { + MachineValue::VmctxDeref(vec![Ctx::offset_memory_bound() as usize]) + } + ValueSemantic::MemoryBase => { + MachineValue::VmctxDeref(vec![Ctx::offset_memory_base() as usize, 0]) + } + ValueSemantic::MemoryBound => { + MachineValue::VmctxDeref(vec![Ctx::offset_memory_bound() as usize, 0]) + } + ValueSemantic::PointerToGlobal(idx) => { + MachineValue::VmctxDeref(deref_global(module_info, idx, false)) + } + ValueSemantic::Global(idx) => { + MachineValue::VmctxDeref(deref_global(module_info, idx, true)) + } + ValueSemantic::PointerToTableBase => { + MachineValue::VmctxDeref(deref_table_base(module_info, 0, false)) + } + ValueSemantic::PointerToTableBound => { + MachineValue::VmctxDeref(deref_table_bound(module_info, 0, false)) + } + ValueSemantic::ImportedFuncPointer(idx) => MachineValue::VmctxDeref(vec![ + Ctx::offset_imported_funcs() as usize, + vm::ImportedFunc::size() as usize * idx + + vm::ImportedFunc::offset_func() as usize, + 0, + ]), + ValueSemantic::ImportedFuncCtx(idx) => MachineValue::VmctxDeref(vec![ + Ctx::offset_imported_funcs() as usize, + vm::ImportedFunc::size() as usize * idx + + vm::ImportedFunc::offset_func_ctx() as usize, + 0, + ]), + ValueSemantic::DynamicSigindice(idx) => { + MachineValue::VmctxDeref(vec![Ctx::offset_signatures() as usize, idx * 4, 0]) + } + }; + match loc.ty { + LocationType::Register => { + let index = X64Register::from_dwarf_regnum(loc.dwarf_regnum) + .expect("invalid regnum") + .to_index(); + regs.push((index, mv)); + } + LocationType::Constant => { + let v = loc.offset_or_small_constant as u32 as u64; + match mv { + MachineValue::WasmStack(x) => { + stack_constants.insert(x, v); + *wasm_stack.last_mut().unwrap() = WasmAbstractValue::Const(v); + } + _ => {} // TODO + } + } + LocationType::ConstantIndex => { + let v = + llvm_map.constants[loc.offset_or_small_constant as usize].large_constant; + match mv { + MachineValue::WasmStack(x) => { + stack_constants.insert(x, v); + *wasm_stack.last_mut().unwrap() = WasmAbstractValue::Const(v); + } + _ => {} // TODO + } + } + LocationType::Direct => match mv { + MachineValue::WasmLocal(_) => { + assert_eq!(loc.location_size, 8); // the pointer itself + assert!( + X64Register::from_dwarf_regnum(loc.dwarf_regnum).unwrap() + == X64Register::GPR(GPR::RBP) + ); + if loc.offset_or_small_constant >= 0 { + assert!(loc.offset_or_small_constant >= 16); // (saved_rbp, return_address) + assert!(loc.offset_or_small_constant % 8 == 0); + prev_frame_diff + .insert((loc.offset_or_small_constant as usize - 16) / 8, Some(mv)); + } else { + let stack_offset = ((-loc.offset_or_small_constant) / 4) as usize; + assert!( + stack_offset > 0 && stack_offset <= machine_stack_half_layout.len() + ); + machine_stack_half_layout[stack_offset - 1] = mv; + } + } + _ => unreachable!( + "Direct location type is not expected for values other than local" + ), + }, + LocationType::Indirect => { + assert!(loc.offset_or_small_constant < 0); + assert!( + X64Register::from_dwarf_regnum(loc.dwarf_regnum).unwrap() + == X64Register::GPR(GPR::RBP) + ); + let stack_offset = ((-loc.offset_or_small_constant) / 4) as usize; + assert!(stack_offset > 0 && stack_offset <= machine_stack_half_layout.len()); + machine_stack_half_layout[stack_offset - 1] = mv; + } + } + } + + assert_eq!(wasm_stack.len(), self.stack_count); + assert_eq!(wasm_locals.len(), self.local_count); + + let mut machine_stack_layout: Vec = + Vec::with_capacity(machine_stack_half_layout.len() / 2); + + for i in 0..machine_stack_half_layout.len() / 2 { + let major = &machine_stack_half_layout[i * 2 + 1]; // mod 8 == 0 + let minor = &machine_stack_half_layout[i * 2]; // mod 8 == 4 + let only_major = match *minor { + MachineValue::Undefined => true, + _ => false, + }; + if only_major { + machine_stack_layout.push(major.clone()); + } else { + machine_stack_layout.push(MachineValue::TwoHalves(Box::new(( + major.clone(), + minor.clone(), + )))); + } + } + + let diff = MachineStateDiff { + last: None, + stack_push: machine_stack_layout, + stack_pop: 0, + prev_frame_diff, + reg_diff: regs, + wasm_stack_push: wasm_stack, + wasm_stack_pop: 0, + wasm_stack_private_depth: 0, + wasm_inst_offset: self.opcode_offset, + }; + let diff_id = fsm.diffs.len(); + fsm.diffs.push(diff); + + match self.kind { + StackmapEntryKind::FunctionHeader => { + fsm.locals = wasm_locals; + } + _ => { + assert_eq!(fsm.locals, wasm_locals); + } + } + + let end_offset = { + if let Some(end) = end { + let (end_entry, end_record) = end; + assert_eq!(end_entry.is_start, false); + assert_eq!(self.opcode_offset, end_entry.opcode_offset); + let end_offset = func_base_addr + end_record.instruction_offset as usize; + assert!(end_offset >= target_offset); + end_offset + } else { + target_offset + 1 + } + }; + + match self.kind { + StackmapEntryKind::Loop => { + fsm.wasm_offset_to_target_offset + .insert(self.opcode_offset, SuspendOffset::Loop(target_offset)); + fsm.loop_offsets.insert( + target_offset, + OffsetInfo { + end_offset, + diff_id, + activate_offset: target_offset, + }, + ); + } + StackmapEntryKind::Call => { + fsm.wasm_offset_to_target_offset + .insert(self.opcode_offset, SuspendOffset::Call(target_offset)); + fsm.call_offsets.insert( + target_offset, + OffsetInfo { + end_offset: end_offset + 1, // The return address is just after 'call' instruction. Offset by one here. + diff_id, + activate_offset: target_offset, + }, + ); + } + StackmapEntryKind::Trappable => { + fsm.wasm_offset_to_target_offset + .insert(self.opcode_offset, SuspendOffset::Trappable(target_offset)); + fsm.trappable_offsets.insert( + target_offset, + OffsetInfo { + end_offset, + diff_id, + activate_offset: target_offset, + }, + ); + } + StackmapEntryKind::FunctionHeader => { + fsm.wasm_function_header_target_offset = Some(SuspendOffset::Loop(target_offset)); + fsm.loop_offsets.insert( + target_offset, + OffsetInfo { + end_offset, + diff_id, + activate_offset: target_offset, + }, + ); + } + } + } +} + +#[derive(Clone, Debug, Default)] +pub struct StackMap { + pub version: u8, + pub stk_size_records: Vec, + pub constants: Vec, + pub stk_map_records: Vec, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct StkSizeRecord { + pub function_address: u64, + pub stack_size: u64, + pub record_count: u64, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct Constant { + pub large_constant: u64, +} + +#[derive(Clone, Debug, Default)] +pub struct StkMapRecord { + pub patchpoint_id: u64, + pub instruction_offset: u32, + pub locations: Vec, + pub live_outs: Vec, +} + +#[derive(Copy, Clone, Debug)] +pub struct Location { + pub ty: LocationType, + pub location_size: u16, + pub dwarf_regnum: u16, + pub offset_or_small_constant: i32, +} + +#[derive(Copy, Clone, Debug, Default)] +pub struct LiveOut { + pub dwarf_regnum: u16, + pub size_in_bytes: u8, +} + +#[derive(Copy, Clone, Debug)] +pub enum LocationType { + Register, + Direct, + Indirect, + Constant, + ConstantIndex, +} + +impl StackMap { + pub fn parse(raw: &[u8]) -> io::Result { + let mut reader = Cursor::new(raw); + let mut map = StackMap::default(); + + let version = reader.read_u8()?; + if version != 3 { + return Err(io::Error::new(io::ErrorKind::Other, "version is not 3")); + } + map.version = version; + if reader.read_u8()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (1)", + )); + } + if reader.read_u16::()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (2)", + )); + } + let num_functions = reader.read_u32::()?; + let num_constants = reader.read_u32::()?; + let num_records = reader.read_u32::()?; + for _ in 0..num_functions { + let mut record = StkSizeRecord::default(); + record.function_address = reader.read_u64::()?; + record.stack_size = reader.read_u64::()?; + record.record_count = reader.read_u64::()?; + map.stk_size_records.push(record); + } + for _ in 0..num_constants { + map.constants.push(Constant { + large_constant: reader.read_u64::()?, + }); + } + for _ in 0..num_records { + let mut record = StkMapRecord::default(); + + record.patchpoint_id = reader.read_u64::()?; + record.instruction_offset = reader.read_u32::()?; + if reader.read_u16::()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (3)", + )); + } + let num_locations = reader.read_u16::()?; + for _ in 0..num_locations { + let ty = reader.read_u8()?; + + let mut location = Location { + ty: match ty { + 1 => LocationType::Register, + 2 => LocationType::Direct, + 3 => LocationType::Indirect, + 4 => LocationType::Constant, + 5 => LocationType::ConstantIndex, + _ => { + return Err(io::Error::new( + io::ErrorKind::Other, + "unknown location type", + )) + } + }, + location_size: 0, + dwarf_regnum: 0, + offset_or_small_constant: 0, + }; + + if reader.read_u8()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (4)", + )); + } + location.location_size = reader.read_u16::()?; + location.dwarf_regnum = reader.read_u16::()?; + if reader.read_u16::()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (5)", + )); + } + location.offset_or_small_constant = reader.read_i32::()?; + + record.locations.push(location); + } + if reader.position() % 8 != 0 { + if reader.read_u32::()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (6)", + )); + } + } + if reader.read_u16::()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (7)", + )); + } + let num_live_outs = reader.read_u16::()?; + for _ in 0..num_live_outs { + let mut liveout = LiveOut::default(); + + liveout.dwarf_regnum = reader.read_u16::()?; + if reader.read_u8()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (8)", + )); + } + liveout.size_in_bytes = reader.read_u8()?; + + record.live_outs.push(liveout); + } + if reader.position() % 8 != 0 { + if reader.read_u32::()? != 0 { + return Err(io::Error::new( + io::ErrorKind::Other, + "reserved field is not zero (9)", + )); + } + } + + map.stk_map_records.push(record); + } + Ok(map) + } +} + +fn deref_global(info: &ModuleInfo, idx: usize, deref_into_value: bool) -> Vec { + let mut x: Vec = match GlobalIndex::new(idx).local_or_import(info) { + LocalOrImport::Local(idx) => vec![Ctx::offset_globals() as usize, idx.index() * 8, 0], + LocalOrImport::Import(idx) => { + vec![Ctx::offset_imported_globals() as usize, idx.index() * 8, 0] + } + }; + if deref_into_value { + x.push(0); + } + x +} + +fn deref_table_base(info: &ModuleInfo, idx: usize, deref_into_value: bool) -> Vec { + let mut x: Vec = match TableIndex::new(idx).local_or_import(info) { + LocalOrImport::Local(idx) => vec![Ctx::offset_tables() as usize, idx.index() * 8, 0], + LocalOrImport::Import(idx) => { + vec![Ctx::offset_imported_tables() as usize, idx.index() * 8, 0] + } + }; + if deref_into_value { + x.push(0); + } + x +} + +fn deref_table_bound(info: &ModuleInfo, idx: usize, deref_into_value: bool) -> Vec { + let mut x: Vec = match TableIndex::new(idx).local_or_import(info) { + LocalOrImport::Local(idx) => vec![Ctx::offset_tables() as usize, idx.index() * 8, 8], + LocalOrImport::Import(idx) => { + vec![Ctx::offset_imported_tables() as usize, idx.index() * 8, 8] + } + }; + if deref_into_value { + x.push(0); + } + x +} diff --git a/lib/llvm-backend/src/state.rs b/lib/llvm-backend/src/state.rs index 47da54a6d..e870b6627 100644 --- a/lib/llvm-backend/src/state.rs +++ b/lib/llvm-backend/src/state.rs @@ -4,26 +4,27 @@ use inkwell::{ }; use smallvec::SmallVec; use std::cell::Cell; +use std::ops::{BitAnd, BitOr, BitOrAssign}; use wasmparser::BinaryReaderError; #[derive(Debug)] -pub enum ControlFrame { +pub enum ControlFrame<'ctx> { Block { next: BasicBlock, - phis: SmallVec<[PhiValue; 1]>, + phis: SmallVec<[PhiValue<'ctx>; 1]>, stack_size_snapshot: usize, }, Loop { body: BasicBlock, next: BasicBlock, - phis: SmallVec<[PhiValue; 1]>, + phis: SmallVec<[PhiValue<'ctx>; 1]>, stack_size_snapshot: usize, }, IfElse { if_then: BasicBlock, if_else: BasicBlock, next: BasicBlock, - phis: SmallVec<[PhiValue; 1]>, + phis: SmallVec<[PhiValue<'ctx>; 1]>, stack_size_snapshot: usize, if_else_state: IfElseState, }, @@ -35,7 +36,7 @@ pub enum IfElseState { Else, } -impl ControlFrame { +impl<'ctx> ControlFrame<'ctx> { pub fn code_after(&self) -> &BasicBlock { match self { ControlFrame::Block { ref next, .. } @@ -51,7 +52,7 @@ impl ControlFrame { } } - pub fn phis(&self) -> &[PhiValue] { + pub fn phis(&self) -> &[PhiValue<'ctx>] { match self { ControlFrame::Block { ref phis, .. } | ControlFrame::Loop { ref phis, .. } @@ -67,16 +68,140 @@ impl ControlFrame { } } +#[derive(Debug, Default, Eq, PartialEq, Copy, Clone, Hash)] +pub struct ExtraInfo { + state: u8, +} +impl ExtraInfo { + // This value is required to be arithmetic 32-bit NaN (or 32x4) by the WAsm + // machine, but which might not be in the LLVM value. The conversion to + // arithmetic NaN is pending. It is required for correctness. + // + // When applied to a 64-bit value, this flag has no meaning and must be + // ignored. It may be set in such cases to allow for common handling of + // 32 and 64-bit operations. + pub const fn pending_f32_nan() -> ExtraInfo { + ExtraInfo { state: 1 } + } + + // This value is required to be arithmetic 64-bit NaN (or 64x2) by the WAsm + // machine, but which might not be in the LLVM value. The conversion to + // arithmetic NaN is pending. It is required for correctness. + // + // When applied to a 32-bit value, this flag has no meaning and must be + // ignored. It may be set in such cases to allow for common handling of + // 32 and 64-bit operations. + pub const fn pending_f64_nan() -> ExtraInfo { + ExtraInfo { state: 2 } + } + + // This value either does not contain a 32-bit NaN, or it contains an + // arithmetic NaN. In SIMD, applies to all 4 lanes. + pub const fn arithmetic_f32() -> ExtraInfo { + ExtraInfo { state: 4 } + } + + // This value either does not contain a 64-bit NaN, or it contains an + // arithmetic NaN. In SIMD, applies to both lanes. + pub const fn arithmetic_f64() -> ExtraInfo { + ExtraInfo { state: 8 } + } + + pub const fn has_pending_f32_nan(&self) -> bool { + self.state & ExtraInfo::pending_f32_nan().state != 0 + } + pub const fn has_pending_f64_nan(&self) -> bool { + self.state & ExtraInfo::pending_f64_nan().state != 0 + } + pub const fn is_arithmetic_f32(&self) -> bool { + self.state & ExtraInfo::arithmetic_f32().state != 0 + } + pub const fn is_arithmetic_f64(&self) -> bool { + self.state & ExtraInfo::arithmetic_f64().state != 0 + } + + pub const fn strip_pending(&self) -> ExtraInfo { + ExtraInfo { + state: self.state + & !(ExtraInfo::pending_f32_nan().state | ExtraInfo::pending_f64_nan().state), + } + } +} + +// Union two ExtraInfos. +impl BitOr for ExtraInfo { + type Output = Self; + + fn bitor(self, other: Self) -> Self { + debug_assert!(!(self.has_pending_f32_nan() && other.has_pending_f64_nan())); + debug_assert!(!(self.has_pending_f64_nan() && other.has_pending_f32_nan())); + ExtraInfo { + state: if self.is_arithmetic_f32() || other.is_arithmetic_f32() { + ExtraInfo::arithmetic_f32().state + } else if self.has_pending_f32_nan() || other.has_pending_f32_nan() { + ExtraInfo::pending_f32_nan().state + } else { + 0 + } + if self.is_arithmetic_f64() || other.is_arithmetic_f64() { + ExtraInfo::arithmetic_f64().state + } else if self.has_pending_f64_nan() || other.has_pending_f64_nan() { + ExtraInfo::pending_f64_nan().state + } else { + 0 + }, + } + } +} +impl BitOrAssign for ExtraInfo { + fn bitor_assign(&mut self, other: Self) { + *self = *self | other; + } +} + +// Intersection for ExtraInfo. +impl BitAnd for ExtraInfo { + type Output = Self; + fn bitand(self, other: Self) -> Self { + // Pending canonicalizations are not safe to discard, or even reorder. + debug_assert!( + self.has_pending_f32_nan() == other.has_pending_f32_nan() + || self.is_arithmetic_f32() + || other.is_arithmetic_f32() + ); + debug_assert!( + self.has_pending_f64_nan() == other.has_pending_f64_nan() + || self.is_arithmetic_f64() + || other.is_arithmetic_f64() + ); + let info = match ( + self.is_arithmetic_f32() && other.is_arithmetic_f32(), + self.is_arithmetic_f64() && other.is_arithmetic_f64(), + ) { + (false, false) => Default::default(), + (true, false) => ExtraInfo::arithmetic_f32(), + (false, true) => ExtraInfo::arithmetic_f64(), + (true, true) => ExtraInfo::arithmetic_f32() | ExtraInfo::arithmetic_f64(), + }; + let info = match (self.has_pending_f32_nan(), self.has_pending_f64_nan()) { + (false, false) => info, + (true, false) => info | ExtraInfo::pending_f32_nan(), + (false, true) => info | ExtraInfo::pending_f64_nan(), + (true, true) => unreachable!("Can't form ExtraInfo with two pending canonicalizations"), + }; + info + } +} + #[derive(Debug)] -pub struct State { - stack: Vec, - control_stack: Vec, +pub struct State<'ctx> { + pub stack: Vec<(BasicValueEnum<'ctx>, ExtraInfo)>, + control_stack: Vec>, value_counter: Cell, pub reachable: bool, } -impl State { +impl<'ctx> State<'ctx> { pub fn new() -> Self { Self { stack: vec![], @@ -86,7 +211,7 @@ impl State { } } - pub fn reset_stack(&mut self, frame: &ControlFrame) { + pub fn reset_stack(&mut self, frame: &ControlFrame<'ctx>) { let stack_size_snapshot = match frame { ControlFrame::Block { stack_size_snapshot, @@ -104,14 +229,14 @@ impl State { self.stack.truncate(stack_size_snapshot); } - pub fn outermost_frame(&self) -> Result<&ControlFrame, BinaryReaderError> { + pub fn outermost_frame(&self) -> Result<&ControlFrame<'ctx>, BinaryReaderError> { self.control_stack.get(0).ok_or(BinaryReaderError { message: "invalid control stack depth", offset: -1isize as usize, }) } - pub fn frame_at_depth(&self, depth: u32) -> Result<&ControlFrame, BinaryReaderError> { + pub fn frame_at_depth(&self, depth: u32) -> Result<&ControlFrame<'ctx>, BinaryReaderError> { let index = self.control_stack.len() - 1 - (depth as usize); self.control_stack.get(index).ok_or(BinaryReaderError { message: "invalid control stack depth", @@ -122,7 +247,7 @@ impl State { pub fn frame_at_depth_mut( &mut self, depth: u32, - ) -> Result<&mut ControlFrame, BinaryReaderError> { + ) -> Result<&mut ControlFrame<'ctx>, BinaryReaderError> { let index = self.control_stack.len() - 1 - (depth as usize); self.control_stack.get_mut(index).ok_or(BinaryReaderError { message: "invalid control stack depth", @@ -130,7 +255,7 @@ impl State { }) } - pub fn pop_frame(&mut self) -> Result { + pub fn pop_frame(&mut self) -> Result, BinaryReaderError> { self.control_stack.pop().ok_or(BinaryReaderError { message: "cannot pop from control stack", offset: -1isize as usize, @@ -144,33 +269,64 @@ impl State { s } - pub fn push1(&mut self, value: T) { - self.stack.push(value.as_basic_value_enum()) + pub fn push1>(&mut self, value: T) { + self.push1_extra(value, Default::default()); } - pub fn pop1(&mut self) -> Result { + pub fn push1_extra>(&mut self, value: T, info: ExtraInfo) { + self.stack.push((value.as_basic_value_enum(), info)); + } + + pub fn pop1(&mut self) -> Result, BinaryReaderError> { + Ok(self.pop1_extra()?.0) + } + + pub fn pop1_extra(&mut self) -> Result<(BasicValueEnum<'ctx>, ExtraInfo), BinaryReaderError> { self.stack.pop().ok_or(BinaryReaderError { message: "invalid value stack", offset: -1isize as usize, }) } - pub fn pop2(&mut self) -> Result<(BasicValueEnum, BasicValueEnum), BinaryReaderError> { + pub fn pop2( + &mut self, + ) -> Result<(BasicValueEnum<'ctx>, BasicValueEnum<'ctx>), BinaryReaderError> { let v2 = self.pop1()?; let v1 = self.pop1()?; Ok((v1, v2)) } - pub fn pop3( + pub fn pop2_extra( &mut self, - ) -> Result<(BasicValueEnum, BasicValueEnum, BasicValueEnum), BinaryReaderError> { - let v3 = self.pop1()?; - let v2 = self.pop1()?; - let v1 = self.pop1()?; + ) -> Result< + ( + (BasicValueEnum<'ctx>, ExtraInfo), + (BasicValueEnum<'ctx>, ExtraInfo), + ), + BinaryReaderError, + > { + let v2 = self.pop1_extra()?; + let v1 = self.pop1_extra()?; + Ok((v1, v2)) + } + + pub fn pop3_extra( + &mut self, + ) -> Result< + ( + (BasicValueEnum<'ctx>, ExtraInfo), + (BasicValueEnum<'ctx>, ExtraInfo), + (BasicValueEnum<'ctx>, ExtraInfo), + ), + BinaryReaderError, + > { + let v3 = self.pop1_extra()?; + let v2 = self.pop1_extra()?; + let v1 = self.pop1_extra()?; Ok((v1, v2, v3)) } - pub fn peek1(&self) -> Result { + pub fn peek1_extra(&self) -> Result<(BasicValueEnum<'ctx>, ExtraInfo), BinaryReaderError> { self.stack .get(self.stack.len() - 1) .ok_or(BinaryReaderError { @@ -180,17 +336,27 @@ impl State { .map(|v| *v) } - pub fn peekn(&self, n: usize) -> Result<&[BasicValueEnum], BinaryReaderError> { - self.stack - .get(self.stack.len() - n..) - .ok_or(BinaryReaderError { - message: "invalid value stack", - offset: -1isize as usize, - }) + pub fn peekn(&self, n: usize) -> Result>, BinaryReaderError> { + Ok(self.peekn_extra(n)?.iter().map(|x| x.0).collect()) } - pub fn popn_save(&mut self, n: usize) -> Result, BinaryReaderError> { - let v = self.peekn(n)?.to_vec(); + pub fn peekn_extra( + &self, + n: usize, + ) -> Result<&[(BasicValueEnum<'ctx>, ExtraInfo)], BinaryReaderError> { + let new_len = self.stack.len().checked_sub(n).ok_or(BinaryReaderError { + message: "invalid value stack", + offset: -1isize as usize, + })?; + + Ok(&self.stack[new_len..]) + } + + pub fn popn_save_extra( + &mut self, + n: usize, + ) -> Result, ExtraInfo)>, BinaryReaderError> { + let v = self.peekn_extra(n)?.to_vec(); self.popn(n)?; Ok(v) } @@ -208,7 +374,7 @@ impl State { Ok(()) } - pub fn push_block(&mut self, next: BasicBlock, phis: SmallVec<[PhiValue; 1]>) { + pub fn push_block(&mut self, next: BasicBlock, phis: SmallVec<[PhiValue<'ctx>; 1]>) { self.control_stack.push(ControlFrame::Block { next, phis, @@ -216,7 +382,12 @@ impl State { }); } - pub fn push_loop(&mut self, body: BasicBlock, next: BasicBlock, phis: SmallVec<[PhiValue; 1]>) { + pub fn push_loop( + &mut self, + body: BasicBlock, + next: BasicBlock, + phis: SmallVec<[PhiValue<'ctx>; 1]>, + ) { self.control_stack.push(ControlFrame::Loop { body, next, @@ -230,7 +401,7 @@ impl State { if_then: BasicBlock, if_else: BasicBlock, next: BasicBlock, - phis: SmallVec<[PhiValue; 1]>, + phis: SmallVec<[PhiValue<'ctx>; 1]>, ) { self.control_stack.push(ControlFrame::IfElse { if_then, diff --git a/lib/llvm-backend/src/trampolines.rs b/lib/llvm-backend/src/trampolines.rs index 95fd676b9..c4894ef84 100644 --- a/lib/llvm-backend/src/trampolines.rs +++ b/lib/llvm-backend/src/trampolines.rs @@ -13,14 +13,14 @@ use wasmer_runtime_core::{ types::{FuncSig, SigIndex, Type}, }; -pub fn generate_trampolines( +pub fn generate_trampolines<'ctx>( info: &ModuleInfo, - signatures: &SliceMap, - module: &Module, - context: &Context, - builder: &Builder, - intrinsics: &Intrinsics, -) { + signatures: &SliceMap>, + module: &Module<'ctx>, + context: &'ctx Context, + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, +) -> Result<(), String> { for (sig_index, sig) in info.signatures.iter() { let func_type = signatures[sig_index]; @@ -42,18 +42,19 @@ pub fn generate_trampolines( Some(Linkage::External), ); - generate_trampoline(trampoline_func, sig, context, builder, intrinsics); + generate_trampoline(trampoline_func, sig, context, builder, intrinsics)?; } + Ok(()) } -fn generate_trampoline( +fn generate_trampoline<'ctx>( trampoline_func: FunctionValue, func_sig: &FuncSig, - context: &Context, - builder: &Builder, - intrinsics: &Intrinsics, -) { - let entry_block = context.append_basic_block(&trampoline_func, "entry"); + context: &'ctx Context, + builder: &Builder<'ctx>, + intrinsics: &Intrinsics<'ctx>, +) -> Result<(), String> { + let entry_block = context.append_basic_block(trampoline_func, "entry"); builder.position_at_end(&entry_block); let (vmctx_ptr, func_ptr, args_ptr, returns_ptr) = match trampoline_func.get_params().as_slice() @@ -64,14 +65,12 @@ fn generate_trampoline( args_ptr.into_pointer_value(), returns_ptr.into_pointer_value(), ), - _ => unimplemented!(), + _ => return Err("trampoline function unimplemented".to_string()), }; let cast_ptr_ty = |wasmer_ty| match wasmer_ty { - Type::I32 => intrinsics.i32_ptr_ty, - Type::I64 => intrinsics.i64_ptr_ty, - Type::F32 => intrinsics.f32_ptr_ty, - Type::F64 => intrinsics.f64_ptr_ty, + Type::I32 | Type::F32 => intrinsics.i32_ptr_ty, + Type::I64 | Type::F64 => intrinsics.i64_ptr_ty, Type::V128 => intrinsics.i128_ptr_ty, }; @@ -110,8 +109,11 @@ fn generate_trampoline( call_site.try_as_basic_value().left().unwrap(), ); } - _ => unimplemented!("multi-value returns"), + _ => { + return Err("trampoline function multi-value returns unimplemented".to_string()); + } } builder.build_return(None); + Ok(()) } diff --git a/lib/middleware-common-tests/Cargo.toml b/lib/middleware-common-tests/Cargo.toml new file mode 100644 index 000000000..1541a096c --- /dev/null +++ b/lib/middleware-common-tests/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "wasmer-middleware-common-tests" +version = "0.11.0" +authors = ["The Wasmer Engineering Team "] +edition = "2018" +repository = "https://github.com/wasmerio/wasmer" +license = "MIT" +publish = false + +[dependencies] +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } +wasmer-middleware-common = { path = "../middleware-common", version = "0.11.0" } +wasmer-clif-backend = { path = "../clif-backend", version = "0.11.0" } +wasmer-llvm-backend = { path = "../llvm-backend", version = "0.11.0", features = ["test"], optional = true } +wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.11.0", optional = true } + +[features] +clif = [] +llvm = ["wasmer-llvm-backend"] +singlepass = ["wasmer-singlepass-backend"] + +[dev-dependencies] +wabt = "0.9.1" +criterion = "0.2" + +[[bench]] +name = "metering_benchmark" +harness = false diff --git a/lib/middleware-common/benches/metering_benchmark.rs b/lib/middleware-common-tests/benches/metering_benchmark.rs similarity index 97% rename from lib/middleware-common/benches/metering_benchmark.rs rename to lib/middleware-common-tests/benches/metering_benchmark.rs index 79796646a..7c425ce99 100644 --- a/lib/middleware-common/benches/metering_benchmark.rs +++ b/lib/middleware-common-tests/benches/metering_benchmark.rs @@ -189,7 +189,7 @@ fn bench_metering(c: &mut Criterion) { let wasm_binary = wat2wasm(WAT).unwrap(); let module = compile_with(&wasm_binary, &compiler).unwrap(); let import_object = imports! {}; - let mut instance = module.instantiate(&import_object).unwrap(); + let instance = module.instantiate(&import_object).unwrap(); let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); b.iter(|| black_box(add_to.call(100, 4))) }) @@ -202,7 +202,7 @@ fn bench_metering(c: &mut Criterion) { "gas" => Func::new(gas), }, }; - let mut gas_instance = gas_module.instantiate(&gas_import_object).unwrap(); + let gas_instance = gas_module.instantiate(&gas_import_object).unwrap(); let gas_add_to: Func<(i32, i32), i32> = gas_instance.func("add_to").unwrap(); b.iter(|| black_box(gas_add_to.call(100, 4))) }) diff --git a/lib/middleware-common-tests/src/lib.rs b/lib/middleware-common-tests/src/lib.rs new file mode 100644 index 000000000..1515f5cd2 --- /dev/null +++ b/lib/middleware-common-tests/src/lib.rs @@ -0,0 +1,189 @@ +#[cfg(all(test, any(feature = "singlepass", feature = "llvm")))] +mod tests { + use wabt::wat2wasm; + + use wasmer_middleware_common::metering::*; + use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; + use wasmer_runtime_core::fault::{pop_code_version, push_code_version}; + use wasmer_runtime_core::state::CodeVersion; + use wasmer_runtime_core::{ + backend::{Backend, Compiler}, + compile_with, imports, Func, + }; + + #[cfg(feature = "llvm")] + fn get_compiler(limit: u64) -> (impl Compiler, Backend) { + use wasmer_llvm_backend::ModuleCodeGenerator as LLVMMCG; + let c: StreamingCompiler = StreamingCompiler::new(move || { + let mut chain = MiddlewareChain::new(); + chain.push(Metering::new(limit)); + chain + }); + (c, Backend::LLVM) + } + + #[cfg(feature = "singlepass")] + fn get_compiler(limit: u64) -> (impl Compiler, Backend) { + use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; + let c: StreamingCompiler = StreamingCompiler::new(move || { + let mut chain = MiddlewareChain::new(); + chain.push(Metering::new(limit)); + chain + }); + (c, Backend::Singlepass) + } + + #[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] + compile_error!("compiler not specified, activate a compiler via features"); + + #[cfg(feature = "clif")] + fn get_compiler(_limit: u64) -> (impl Compiler, Backend) { + compile_error!("cranelift does not implement metering"); + use wasmer_clif_backend::CraneliftCompiler; + (CraneliftCompiler::new(), Backend::Cranelift) + } + + // Assemblyscript + // export function add_to(x: i32, y: i32): i32 { + // for(var i = 0; i < x; i++){ + // if(i % 1 == 0){ + // y += i; + // } else { + // y *= i + // } + // } + // return y; + // } + static WAT: &'static str = r#" + (module + (type $t0 (func (param i32 i32) (result i32))) + (type $t1 (func)) + (func $add_to (export "add_to") (type $t0) (param $p0 i32) (param $p1 i32) (result i32) + (local $l0 i32) + block $B0 + i32.const 0 + set_local $l0 + loop $L1 + get_local $l0 + get_local $p0 + i32.lt_s + i32.eqz + br_if $B0 + get_local $l0 + i32.const 1 + i32.rem_s + i32.const 0 + i32.eq + if $I2 + get_local $p1 + get_local $l0 + i32.add + set_local $p1 + else + get_local $p1 + get_local $l0 + i32.mul + set_local $p1 + end + get_local $l0 + i32.const 1 + i32.add + set_local $l0 + br $L1 + unreachable + end + unreachable + end + get_local $p1) + (func $f1 (type $t1)) + (table $table (export "table") 1 anyfunc) + (memory $memory (export "memory") 0) + (global $g0 i32 (i32.const 8)) + (elem (i32.const 0) $f1)) + "#; + + #[test] + fn test_points_reduced_after_call() { + let wasm_binary = wat2wasm(WAT).unwrap(); + + let limit = 100u64; + + let (compiler, backend_id) = get_compiler(limit); + let module = compile_with(&wasm_binary, &compiler).unwrap(); + + let import_object = imports! {}; + let mut instance = module.instantiate(&import_object).unwrap(); + + set_points_used(&mut instance, 0u64); + + let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); + + let cv_pushed = if let Some(msm) = instance.module.runnable_module.get_module_state_map() { + push_code_version(CodeVersion { + baseline: true, + msm: msm, + base: instance.module.runnable_module.get_code().unwrap().as_ptr() as usize, + backend: backend_id, + }); + true + } else { + false + }; + + let value = add_to.call(3, 4).unwrap(); + if cv_pushed { + pop_code_version().unwrap(); + } + + // verify it returns the correct value + assert_eq!(value, 7); + + // verify it used the correct number of points + assert_eq!(get_points_used(&instance), 74); + } + + #[test] + fn test_traps_after_costly_call() { + use wasmer_runtime_core::error::RuntimeError; + let wasm_binary = wat2wasm(WAT).unwrap(); + + let limit = 100u64; + + let (compiler, backend_id) = get_compiler(limit); + let module = compile_with(&wasm_binary, &compiler).unwrap(); + + let import_object = imports! {}; + let mut instance = module.instantiate(&import_object).unwrap(); + + set_points_used(&mut instance, 0u64); + + let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); + + let cv_pushed = if let Some(msm) = instance.module.runnable_module.get_module_state_map() { + push_code_version(CodeVersion { + baseline: true, + msm: msm, + base: instance.module.runnable_module.get_code().unwrap().as_ptr() as usize, + backend: backend_id, + }); + true + } else { + false + }; + let result = add_to.call(10_000_000, 4); + if cv_pushed { + pop_code_version().unwrap(); + } + + let err = result.unwrap_err(); + match err { + RuntimeError::Error { data } => { + assert!(data.downcast_ref::().is_some()); + } + _ => unreachable!(), + } + + // verify it used the correct number of points + assert_eq!(get_points_used(&instance), 109); // Used points will be slightly more than `limit` because of the way we do gas checking. + } +} diff --git a/lib/middleware-common/Cargo.toml b/lib/middleware-common/Cargo.toml index 8944bfa21..3cddcefcc 100644 --- a/lib/middleware-common/Cargo.toml +++ b/lib/middleware-common/Cargo.toml @@ -1,27 +1,13 @@ [package] name = "wasmer-middleware-common" -version = "0.6.0" +version = "0.11.0" repository = "https://github.com/wasmerio/wasmer" description = "Wasmer runtime common middlewares" license = "MIT" authors = ["The Wasmer Engineering Team "] +keywords = ["wasm", "webassembly", "middleware", "metering"] +categories = ["wasm"] edition = "2018" [dependencies] -wasmer-runtime-core = { path = "../runtime-core" } -wasmer-clif-backend = { path = "../clif-backend", version = "0.6.0" } -wasmer-llvm-backend = { path = "../llvm-backend", version = "0.6.0", optional = true } -wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.6.0", optional = true } - -[dev-dependencies] -wabt = "0.9.1" -criterion = "0.2" - -[features] -clif = [] -llvm = ["wasmer-llvm-backend"] -singlepass = ["wasmer-singlepass-backend"] - -[[bench]] -name = "metering_benchmark" -harness = false +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } diff --git a/lib/middleware-common/src/block_trace.rs b/lib/middleware-common/src/block_trace.rs new file mode 100644 index 000000000..f104ab086 --- /dev/null +++ b/lib/middleware-common/src/block_trace.rs @@ -0,0 +1,153 @@ +use wasmer_runtime_core::{ + codegen::{Event, EventSink, FunctionMiddleware, InternalEvent}, + module::ModuleInfo, + wasmparser::Operator, +}; + +pub struct BlockTrace { + func_idx: usize, + evt_idx: usize, +} + +impl BlockTrace { + pub fn new() -> BlockTrace { + BlockTrace { + func_idx: std::usize::MAX, + evt_idx: 0, + } + } +} + +impl FunctionMiddleware for BlockTrace { + type Error = String; + fn feed_event<'a, 'b: 'a>( + &mut self, + op: Event<'a, 'b>, + _module_info: &ModuleInfo, + sink: &mut EventSink<'a, 'b>, + ) -> Result<(), Self::Error> { + match op { + Event::Internal(InternalEvent::FunctionBegin(_)) => { + self.func_idx = self.func_idx.wrapping_add(1); + self.evt_idx = 0; + let func_idx = self.func_idx; + let evt_idx = self.evt_idx; + sink.push(op); + sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new( + move |info| { + eprintln!( + "[BlockTrace] ({}, {}) -> enter_func % {:?}", + func_idx, + evt_idx, + info.fault + .and_then(|x| unsafe { x.read_stack(Some(1)) }) + .unwrap() + .frames[0] + ); + Ok(()) + }, + )))) + } + Event::Wasm(Operator::Call { .. }) => { + let func_idx = self.func_idx; + let evt_idx = self.evt_idx; + sink.push(op); + sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new( + move |info| { + eprintln!( + "[BlockTrace] ({}, {}) -> leave_call % {:?}", + func_idx, + evt_idx, + info.fault + .and_then(|x| unsafe { x.read_stack(Some(1)) }) + .unwrap() + .frames[0] + ); + Ok(()) + }, + )))) + } + Event::Wasm(Operator::Block { .. }) => { + let func_idx = self.func_idx; + let evt_idx = self.evt_idx; + sink.push(op); + sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new( + move |info| { + eprintln!( + "[BlockTrace] ({}, {}) -> block % {:?}", + func_idx, + evt_idx, + info.fault + .and_then(|x| unsafe { x.read_stack(Some(1)) }) + .unwrap() + .frames[0] + ); + Ok(()) + }, + )))) + } + Event::Wasm(Operator::Loop { .. }) => { + let func_idx = self.func_idx; + let evt_idx = self.evt_idx; + sink.push(op); + sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new( + move |info| { + eprintln!( + "[BlockTrace] ({}, {}) -> loop % {:?}", + func_idx, + evt_idx, + info.fault + .and_then(|x| unsafe { x.read_stack(Some(1)) }) + .unwrap() + .frames[0] + ); + Ok(()) + }, + )))) + } + Event::Wasm(Operator::If { .. }) => { + let func_idx = self.func_idx; + let evt_idx = self.evt_idx; + sink.push(op); + sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new( + move |info| { + eprintln!( + "[BlockTrace] ({}, {}) -> if % {:?}", + func_idx, + evt_idx, + info.fault + .and_then(|x| unsafe { x.read_stack(Some(1)) }) + .unwrap() + .frames[0] + ); + Ok(()) + }, + )))) + } + Event::Wasm(Operator::Else { .. }) => { + let func_idx = self.func_idx; + let evt_idx = self.evt_idx; + sink.push(op); + sink.push(Event::Internal(InternalEvent::Breakpoint(Box::new( + move |info| { + eprintln!( + "[BlockTrace] ({}, {}) -> else % {:?}", + func_idx, + evt_idx, + info.fault + .and_then(|x| unsafe { x.read_stack(Some(1)) }) + .unwrap() + .frames[0] + ); + Ok(()) + }, + )))) + } + _ => { + sink.push(op); + } + } + self.evt_idx += 1; + Ok(()) + } +} diff --git a/lib/middleware-common/src/call_trace.rs b/lib/middleware-common/src/call_trace.rs index 04a763abc..5cb77534c 100644 --- a/lib/middleware-common/src/call_trace.rs +++ b/lib/middleware-common/src/call_trace.rs @@ -1,9 +1,23 @@ +use std::sync::{ + atomic::{AtomicU32, Ordering}, + Arc, +}; use wasmer_runtime_core::{ codegen::{Event, EventSink, FunctionMiddleware, InternalEvent}, module::ModuleInfo, }; -pub struct CallTrace; +pub struct CallTrace { + counter: Arc, +} + +impl CallTrace { + pub fn new() -> CallTrace { + CallTrace { + counter: Arc::new(AtomicU32::new(0)), + } + } +} impl FunctionMiddleware for CallTrace { type Error = String; @@ -13,10 +27,13 @@ impl FunctionMiddleware for CallTrace { _module_info: &ModuleInfo, sink: &mut EventSink<'a, 'b>, ) -> Result<(), Self::Error> { + let counter = self.counter.clone(); + match op { Event::Internal(InternalEvent::FunctionBegin(id)) => sink.push(Event::Internal( InternalEvent::Breakpoint(Box::new(move |_| { - eprintln!("func ({})", id); + let idx = counter.fetch_add(1, Ordering::SeqCst); + eprintln!("[{}] func ({})", idx, id); Ok(()) })), )), diff --git a/lib/middleware-common/src/lib.rs b/lib/middleware-common/src/lib.rs index 929558f25..85885383f 100644 --- a/lib/middleware-common/src/lib.rs +++ b/lib/middleware-common/src/lib.rs @@ -7,5 +7,10 @@ unused_unsafe, unreachable_patterns )] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + +#[cfg(unix)] +pub mod block_trace; pub mod call_trace; pub mod metering; diff --git a/lib/middleware-common/src/metering.rs b/lib/middleware-common/src/metering.rs index e9f02d509..a7d10aece 100644 --- a/lib/middleware-common/src/metering.rs +++ b/lib/middleware-common/src/metering.rs @@ -128,156 +128,3 @@ pub fn get_points_used_ctx(ctx: &Ctx) -> u64 { pub fn set_points_used_ctx(ctx: &mut Ctx, value: u64) { ctx.set_internal(&INTERNAL_FIELD, value); } - -#[cfg(all(test, any(feature = "singlepass", feature = "llvm")))] -mod tests { - use super::*; - use wabt::wat2wasm; - - use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; - use wasmer_runtime_core::{backend::Compiler, compile_with, imports, Func}; - - #[cfg(feature = "llvm")] - fn get_compiler(limit: u64) -> impl Compiler { - use wasmer_llvm_backend::ModuleCodeGenerator as LLVMMCG; - let c: StreamingCompiler = StreamingCompiler::new(move || { - let mut chain = MiddlewareChain::new(); - chain.push(Metering::new(limit)); - chain - }); - c - } - - #[cfg(feature = "singlepass")] - fn get_compiler(limit: u64) -> impl Compiler { - use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; - let c: StreamingCompiler = StreamingCompiler::new(move || { - let mut chain = MiddlewareChain::new(); - chain.push(Metering::new(limit)); - chain - }); - c - } - - #[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] - compile_error!("compiler not specified, activate a compiler via features"); - - #[cfg(feature = "clif")] - fn get_compiler(_limit: u64) -> impl Compiler { - compile_error!("cranelift does not implement metering"); - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() - } - - // Assemblyscript - // export function add_to(x: i32, y: i32): i32 { - // for(var i = 0; i < x; i++){ - // if(i % 1 == 0){ - // y += i; - // } else { - // y *= i - // } - // } - // return y; - // } - static WAT: &'static str = r#" - (module - (type $t0 (func (param i32 i32) (result i32))) - (type $t1 (func)) - (func $add_to (export "add_to") (type $t0) (param $p0 i32) (param $p1 i32) (result i32) - (local $l0 i32) - block $B0 - i32.const 0 - set_local $l0 - loop $L1 - get_local $l0 - get_local $p0 - i32.lt_s - i32.eqz - br_if $B0 - get_local $l0 - i32.const 1 - i32.rem_s - i32.const 0 - i32.eq - if $I2 - get_local $p1 - get_local $l0 - i32.add - set_local $p1 - else - get_local $p1 - get_local $l0 - i32.mul - set_local $p1 - end - get_local $l0 - i32.const 1 - i32.add - set_local $l0 - br $L1 - unreachable - end - unreachable - end - get_local $p1) - (func $f1 (type $t1)) - (table $table (export "table") 1 anyfunc) - (memory $memory (export "memory") 0) - (global $g0 i32 (i32.const 8)) - (elem (i32.const 0) $f1)) - "#; - - #[test] - fn test_points_reduced_after_call() { - let wasm_binary = wat2wasm(WAT).unwrap(); - - let limit = 100u64; - - let module = compile_with(&wasm_binary, &get_compiler(limit)).unwrap(); - - let import_object = imports! {}; - let mut instance = module.instantiate(&import_object).unwrap(); - - set_points_used(&mut instance, 0u64); - - let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); - let value = add_to.call(3, 4).unwrap(); - - // verify it returns the correct value - assert_eq!(value, 7); - - // verify it used the correct number of points - assert_eq!(get_points_used(&instance), 74); - } - - #[test] - fn test_traps_after_costly_call() { - use wasmer_runtime_core::error::RuntimeError; - let wasm_binary = wat2wasm(WAT).unwrap(); - - let limit = 100u64; - - let module = compile_with(&wasm_binary, &get_compiler(limit)).unwrap(); - - let import_object = imports! {}; - let mut instance = module.instantiate(&import_object).unwrap(); - - set_points_used(&mut instance, 0u64); - - let add_to: Func<(i32, i32), i32> = instance.func("add_to").unwrap(); - let result = add_to.call(10_000_000, 4); - - let err = result.unwrap_err(); - match err { - RuntimeError::Error { data } => { - assert!(data.downcast_ref::().is_some()); - } - _ => unreachable!(), - } - - // verify it used the correct number of points - assert_eq!(get_points_used(&instance), 109); // Used points will be slightly more than `limit` because of the way we do gas checking. - } - -} diff --git a/lib/runtime-abi/Cargo.toml b/lib/runtime-abi/Cargo.toml deleted file mode 100644 index c1f35c801..000000000 --- a/lib/runtime-abi/Cargo.toml +++ /dev/null @@ -1,27 +0,0 @@ -[package] -name = "wasmer-runtime-abi" -version = "0.6.0" -description = "Wasmer runtime core library" -license = "MIT" -authors = ["The Wasmer Engineering Team "] -repository = "https://github.com/wasmerio/wasmer" -edition = "2018" - -[dependencies] -libc = "0.2.60" -wasmer-runtime-core = { path = "../runtime-core" } -failure = "0.1" -tar = "0.4" -wasmparser = "0.35.1" -zstd = "0.4" - -# [target.'cfg(unix)'.dependencies.zbox] -# git = "https://github.com/wasmerio/zbox" -# branch = "bundle-libsodium" -# features = ["libsodium-bundled"] - -[dev-dependencies] -tempdir = "0.3" - -[features] -debug = [] diff --git a/lib/runtime-abi/README.md b/lib/runtime-abi/README.md deleted file mode 100644 index fe906c2dd..000000000 --- a/lib/runtime-abi/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# runtime-abi - -This crate has ABI functions (like syscalls) and extensions to the runtime for enabling ABIs (e.g. virtual filesystem). - -## Virtual Filesystem (experimental) - -The virtual filesystem allows the runtime to read bundled wasm data as if they were files. Data that is stored in a -custom section compressed with [zstd][1] compression and archived with [tar][2] will be exposed as files and mounted -in the `/` root. - -The only current supported operation is the `read` syscall. - -The virtual filesystem is not enabled by default. Build with `--features vfs` to use it. - -[Zbox][3] is a virtual filesystem that depends on [libsodium][4]. See [installation instructions][5] for libsodium here. One can -statically link libsodium with the [instructions][6] on Zbox's readme. - -[1]: https://facebook.github.io/zstd/ -[2]: https://www.gnu.org/software/tar/ -[3]: https://zbox.io/ -[4]: https://download.libsodium.org/doc/ -[5]: https://download.libsodium.org/doc/installation -[6]: https://github.com/zboxfs/zbox#static-linking-with-libsodium diff --git a/lib/runtime-abi/src/lib.rs b/lib/runtime-abi/src/lib.rs deleted file mode 100644 index 237e351b1..000000000 --- a/lib/runtime-abi/src/lib.rs +++ /dev/null @@ -1,7 +0,0 @@ -#![deny(dead_code, unused_imports, unused_variables, unused_unsafe, unreachable_patterns)] -#[cfg(not(target_os = "windows"))] -#[macro_use] -extern crate failure; - -#[cfg(not(target_os = "windows"))] -pub mod vfs; diff --git a/lib/runtime-abi/src/vfs/device_file.rs b/lib/runtime-abi/src/vfs/device_file.rs deleted file mode 100644 index 3013d32b2..000000000 --- a/lib/runtime-abi/src/vfs/device_file.rs +++ /dev/null @@ -1,111 +0,0 @@ -use crate::vfs::file_like::{FileLike, Metadata}; -use failure::Error; -use std::io; - -pub struct Stdin; -pub struct Stdout; -pub struct Stderr; - -impl FileLike for Stdin { - fn metadata(&self) -> Result { - unimplemented!() - } - - fn set_file_len(&mut self, _len: usize) -> Result<(), failure::Error> { - panic!("Cannot set length of stdin"); - } -} - -impl io::Read for Stdin { - fn read(&mut self, _buf: &mut [u8]) -> Result { - unimplemented!() - } -} - -impl io::Write for Stdin { - fn write(&mut self, _buf: &[u8]) -> Result { - unimplemented!() - } - - fn flush(&mut self) -> Result<(), io::Error> { - unimplemented!() - } -} - -impl io::Seek for Stdin { - fn seek(&mut self, _pos: io::SeekFrom) -> Result { - unimplemented!() - } -} - -impl FileLike for Stdout { - fn metadata(&self) -> Result { - unimplemented!() - } - - fn set_file_len(&mut self, _len: usize) -> Result<(), failure::Error> { - panic!("Cannot set length of stdout"); - } -} - -impl io::Read for Stdout { - fn read(&mut self, _buf: &mut [u8]) -> Result { - unimplemented!() - } -} - -impl io::Write for Stdout { - fn write(&mut self, buf: &[u8]) -> Result { - let stdout = io::stdout(); - let mut handle = stdout.lock(); - handle.write(buf) - } - - fn flush(&mut self) -> Result<(), io::Error> { - let stdout = io::stdout(); - let mut handle = stdout.lock(); - handle.flush() - } -} - -impl io::Seek for Stdout { - fn seek(&mut self, _pos: io::SeekFrom) -> Result { - unimplemented!() - } -} - -impl FileLike for Stderr { - fn metadata(&self) -> Result { - unimplemented!() - } - - fn set_file_len(&mut self, _len: usize) -> Result<(), failure::Error> { - panic!("Cannot set length of stderr"); - } -} - -impl io::Read for Stderr { - fn read(&mut self, _buf: &mut [u8]) -> Result { - unimplemented!() - } -} - -impl io::Write for Stderr { - fn write(&mut self, buf: &[u8]) -> Result { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - handle.write(buf) - } - - fn flush(&mut self) -> Result<(), io::Error> { - let stderr = io::stderr(); - let mut handle = stderr.lock(); - handle.flush() - } -} - -impl io::Seek for Stderr { - fn seek(&mut self, _pos: io::SeekFrom) -> Result { - unimplemented!() - } -} diff --git a/lib/runtime-abi/src/vfs/file_like.rs b/lib/runtime-abi/src/vfs/file_like.rs deleted file mode 100644 index 4b9b8771c..000000000 --- a/lib/runtime-abi/src/vfs/file_like.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub type Fd = isize; - -#[derive(Debug)] -pub struct Metadata { - pub len: usize, - pub is_file: bool, -} - -pub trait FileLike: std::io::Write + std::io::Read + std::io::Seek { - // get metadata - fn metadata(&self) -> Result; - - // write - // fn write_file(&mut self, buf: &[u8]) -> Result; - - // read - // fn read_file(&mut self, buf: &mut [u8]) -> Result; - - // set_file_len - fn set_file_len(&mut self, len: usize) -> Result<(), failure::Error>; -} diff --git a/lib/runtime-abi/src/vfs/mod.rs b/lib/runtime-abi/src/vfs/mod.rs deleted file mode 100644 index 0a152b5b3..000000000 --- a/lib/runtime-abi/src/vfs/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod device_file; -pub mod file_like; -pub mod vfs; -pub mod vfs_header; -pub mod virtual_file; diff --git a/lib/runtime-abi/src/vfs/vfs.rs b/lib/runtime-abi/src/vfs/vfs.rs deleted file mode 100644 index c4e01a380..000000000 --- a/lib/runtime-abi/src/vfs/vfs.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::vfs::file_like::FileLike; -use crate::vfs::vfs_header::{header_from_bytes, ArchiveType, CompressionType}; -use crate::vfs::virtual_file::VirtualFile; -use std::collections::HashMap; -use std::cell::RefCell; -use std::io; -use std::io::Read; -use std::path::{Path, PathBuf}; -use std::rc::Rc; -use tar::EntryType; -use zbox::{init_env, OpenOptions, Repo, RepoOpener}; - -pub struct Vfs { - repo: Repo, - device_files: HashMap>>, -} - -impl Vfs { - /// Like `VfsBacking::from_tar_bytes` except it also decompresses from the zstd format. - pub fn from_tar_zstd_bytes(tar_bytes: Reader) -> Result { - let result = zstd::decode_all(tar_bytes); - let decompressed_data = result.unwrap(); - Self::from_tar_bytes(&decompressed_data[..]) - } - - /// Match on the type of the compressed-archive and select the correct unpack method - pub fn from_compressed_bytes(compressed_data_slice: &[u8]) -> Result { - let data_bytes = &compressed_data_slice[4..]; - match header_from_bytes(compressed_data_slice)? { - (_, CompressionType::ZSTD, ArchiveType::TAR) => Self::from_tar_zstd_bytes(data_bytes), - (_, CompressionType::NONE, ArchiveType::TAR) => Self::from_tar_bytes(data_bytes), - } - } - - /// Create a vfs from raw bytes in tar format - pub fn from_tar_bytes(tar_bytes: Reader) -> Result { - init_env(); - let mut repo = RepoOpener::new() - .create(true) - .open("mem://wasmer_fs", "") - .unwrap(); - let _errors = tar::Archive::new(tar_bytes) - .entries()? - .map(|entry| { - let mut entry: tar::Entry = entry?; - let path = entry.path()?; - let path = convert_to_absolute_path(path); - let _result = match (entry.header().entry_type(), path.parent()) { - (EntryType::Regular, Some(parent)) => { - if let Err(e) = repo.create_dir_all(parent) { - if e == zbox::Error::AlreadyExists || e == zbox::Error::IsRoot { - } else { - return Err(VfsAggregateError::ZboxError(e)); - } - } else { - } - let mut file = repo.create_file(&path)?; - if entry.header().size().unwrap_or(0) > 0 { - io::copy(&mut entry, &mut file)?; - file.finish()?; - } - } - (EntryType::Directory, _) => { - if let Err(e) = repo.create_dir_all(path) { - if e == zbox::Error::AlreadyExists || e == zbox::Error::IsRoot { - } else { - return Err(VfsAggregateError::ZboxError(e)); - } - } else { - } - } - _ => return Err(VfsAggregateError::UnsupportedFileType), - }; - Ok(()) - }) - .collect::>>(); - - // let import_errors = errors.iter().filter_map(|e| e.err()).collect::>(); - - let vfs = Self { - repo, - device_files: HashMap::new(), - // import_errors: vec![], - }; - Ok(vfs) - } - - pub fn new() -> Result<(Self, Vec), failure::Error> { - init_env(); - let repo = RepoOpener::new() - .create(true) - .open("mem://wasmer_fs", "") - .unwrap(); - Ok(( - Vfs { - repo, - device_files: HashMap::new(), - }, - vec![], - )) - } - - pub fn open_file>(&mut self, path: P) -> Option>> { - init_env(); - let path = convert_to_absolute_path(path); - if let Ok(file) = OpenOptions::new().write(true).open(&mut self.repo, &path) { - Some(Rc::new(RefCell::new(VirtualFile::new(file)))) - } else if let Some(dev_file) = self.device_files.get(&path) { - Some(dev_file.clone()) - } else { - None - } - } - - pub fn make_dir>(&mut self, path: P) { - self.repo.create_dir_all(path).unwrap(); - } - - pub fn create_device_file>(&mut self, path: P, file: Rc>) { - self.device_files.insert(path.as_ref().to_path_buf(), file); - } -} - -fn convert_to_absolute_path>(path: P) -> PathBuf { - let path = path.as_ref(); - if path.is_relative() { - std::path::PathBuf::from("/").join(path) - } else { - path.to_path_buf() - } -} - -pub type Handle = i32; -#[derive(Debug, Fail)] -pub enum VfsError { - #[fail(display = "File with file descriptor \"{}\" does not exist.", _0)] - FileWithFileDescriptorNotExist(Handle), - #[fail(display = "File descriptor does not exist.")] - FileDescriptorNotExist(Handle), - #[fail(display = "Source file descriptor does not exist.")] - SourceFileDescriptorDoesNotExist, - #[fail(display = "Target file descriptor already exists.")] - TargetFileDescriptorAlreadyExists, - #[fail(display = "Could not get a mutable reference to the file because it is in use.")] - CouldNotGetMutableReferenceToFile, -} - -#[derive(Debug, Fail)] -pub enum VfsAggregateError { - #[fail(display = "Entry error.")] - EntryError(std::io::Error), - #[fail(display = "IO error.")] - IoError(std::io::Error), - #[fail(display = "Zbox error.")] - ZboxError(zbox::Error), - #[fail(display = "Unsupported file type.")] - UnsupportedFileType, -} - -impl std::convert::From for VfsAggregateError { - fn from(error: std::io::Error) -> VfsAggregateError { - VfsAggregateError::EntryError(error) - } -} - -impl std::convert::From for VfsAggregateError { - fn from(error: zbox::Error) -> VfsAggregateError { - VfsAggregateError::ZboxError(error) - } -} diff --git a/lib/runtime-abi/src/vfs/vfs_header.rs b/lib/runtime-abi/src/vfs/vfs_header.rs deleted file mode 100644 index c90c658b0..000000000 --- a/lib/runtime-abi/src/vfs/vfs_header.rs +++ /dev/null @@ -1,57 +0,0 @@ -/// Represents the version of this header schema. -#[repr(u8)] -#[derive(Debug, PartialEq)] -pub enum HeaderVersion { - Version1 = 1, -} - -/// Represents the compression type of the file data. Only Zstd or no-compression is supported. -#[repr(u8)] -#[derive(Debug, PartialEq)] -pub enum CompressionType { - NONE = 0, - ZSTD = 1, -} - -/// Represents the type of archive. The only supported archive is the Tar format. -#[repr(u8)] -#[derive(Debug, PartialEq)] -pub enum ArchiveType { - TAR = 0, -} - -// extract the header data from bytes -pub fn header_from_bytes( - bytes: &[u8], -) -> Result<(HeaderVersion, CompressionType, ArchiveType), HeaderError> { - if let Some(bytes) = bytes.get(..4) { - let version = match bytes[0] { - 1 => HeaderVersion::Version1, - x => return Err(HeaderError::UnknownHeaderVersion(x)), - }; - let compression_type = match bytes[1] { - 0 => CompressionType::NONE, - 1 => CompressionType::ZSTD, - x => return Err(HeaderError::UnknownCompressionType(x)), - }; - let archive_type = match bytes[2] { - 0 => ArchiveType::TAR, - x => return Err(HeaderError::UnknownArchiveType(x)), - }; - Ok((version, compression_type, archive_type)) - } else { - Err(HeaderError::HeaderTooSmall) - } -} - -#[derive(Debug, Fail)] -pub enum HeaderError { - #[fail(display = "The version is not supported: \"{}\"", _0)] - UnknownHeaderVersion(u8), - #[fail(display = "The compression type is unknown: \"{}\"", _0)] - UnknownCompressionType(u8), - #[fail(display = "The archive type is unknown: \"{}\"", _0)] - UnknownArchiveType(u8), - #[fail(display = "The header is too small.")] - HeaderTooSmall, -} diff --git a/lib/runtime-abi/src/vfs/virtual_file.rs b/lib/runtime-abi/src/vfs/virtual_file.rs deleted file mode 100644 index cd72231a1..000000000 --- a/lib/runtime-abi/src/vfs/virtual_file.rs +++ /dev/null @@ -1,51 +0,0 @@ -use crate::vfs::file_like::{FileLike, Metadata}; -use failure::Error; -use std::io; - -pub struct VirtualFile(zbox::File); - -impl VirtualFile { - pub fn new(file: zbox::File) -> Self { - VirtualFile(file) - } -} - -impl FileLike for VirtualFile { - fn metadata(&self) -> Result { - self.0 - .metadata() - .map(|m| Metadata { - len: m.content_len(), - is_file: m.is_file(), - }) - .map_err(|e: zbox::Error| e.into()) - } - - fn set_file_len(&mut self, len: usize) -> Result<(), failure::Error> { - self.0.set_len(len).map_err(|e| e.into()) - } -} - -impl io::Write for VirtualFile { - fn write(&mut self, buf: &[u8]) -> Result { - let result = self.0.write(buf)?; - self.0.finish().unwrap(); - Ok(result) - } - - fn flush(&mut self) -> Result<(), io::Error> { - self.0.flush() - } -} - -impl io::Read for VirtualFile { - fn read(&mut self, buf: &mut [u8]) -> Result { - self.0.read(buf) - } -} - -impl io::Seek for VirtualFile { - fn seek(&mut self, pos: io::SeekFrom) -> Result { - self.0.seek(pos) - } -} diff --git a/lib/runtime-c-api/Cargo.toml b/lib/runtime-c-api/Cargo.toml index c95442c62..0a278b6ba 100644 --- a/lib/runtime-c-api/Cargo.toml +++ b/lib/runtime-c-api/Cargo.toml @@ -1,10 +1,12 @@ [package] name = "wasmer-runtime-c-api" -version = "0.6.0" +version = "0.11.0" description = "Wasmer C API library" license = "MIT" authors = ["The Wasmer Engineering Team "] repository = "https://github.com/wasmerio/wasmer" +keywords = ["wasm", "webassembly", "runtime"] +categories = ["wasm"] edition = "2018" readme = "README.md" @@ -17,19 +19,26 @@ libc = "0.2.60" [dependencies.wasmer-runtime] default-features = false path = "../runtime" -version = "0.6.0" +version = "0.11.0" [dependencies.wasmer-runtime-core] default-features = false path = "../runtime-core" -version = "0.6.0" +version = "0.11.0" + +[dependencies.wasmer-wasi] +default-features = false +path = "../wasi" +version = "0.11.0" +optional = true [features] -default = ["cranelift-backend"] +default = ["cranelift-backend", "wasi"] debug = ["wasmer-runtime/debug"] cranelift-backend = ["wasmer-runtime/cranelift", "wasmer-runtime/default-backend-cranelift"] llvm-backend = ["wasmer-runtime/llvm", "wasmer-runtime/default-backend-llvm"] singlepass-backend = ["wasmer-runtime/singlepass", "wasmer-runtime/default-backend-singlepass"] +wasi = ["wasmer-wasi"] [build-dependencies] -cbindgen = "0.9.0" +cbindgen = "0.9" diff --git a/lib/runtime-c-api/README.md b/lib/runtime-c-api/README.md index 1e741cfd1..06e545bf7 100644 --- a/lib/runtime-c-api/README.md +++ b/lib/runtime-c-api/README.md @@ -1,21 +1,21 @@

- Wasmer logo + Wasmer logo

- - Build Status + + Build Status - License + License Join the Wasmer Community - Number of downloads from crates.io + Number of downloads from crates.io Read our API documentation @@ -25,7 +25,7 @@ # Wasmer Runtime C API Wasmer is a standalone JIT WebAssembly runtime, aiming to be fully -compatible with Emscripten, Rust and Go. [Learn +compatible with WASI, Emscripten, Rust and Go. [Learn more](https://github.com/wasmerio/wasmer). This crate exposes a C and a C++ API for the Wasmer runtime. @@ -36,6 +36,7 @@ The C and C++ header files can be found in the source tree of this crate, respectively [`wasmer.h`][wasmer_h] and [`wasmer.hh`][wasmer_hh]. They are automatically generated, and always up-to-date in this repository. +The runtime shared library (so, dll, dylib) can also be downloaded in Wasmer [release page](https://github.com/wasmerio/wasmer/releases). Here is a simple example to use the C API: @@ -104,6 +105,10 @@ int main() # Testing +Tests are run using the release build of the library. If you make +changes or compile with non-default features, please ensure you +rebuild in release mode for the tests to see the changes. + The tests can be run via `cargo test`, such as: ```sh diff --git a/lib/runtime-c-api/build.rs b/lib/runtime-c-api/build.rs index 1db19a197..5897001b0 100644 --- a/lib/runtime-c-api/build.rs +++ b/lib/runtime-c-api/build.rs @@ -12,12 +12,33 @@ fn main() { let mut out_wasmer_header_file = PathBuf::from(&out_dir); out_wasmer_header_file.push("wasmer"); + const WASMER_PRE_HEADER: &str = r#" +#if !defined(WASMER_H_MACROS) +#define WASMER_H_MACROS + +#if defined(MSVC) +#if defined(_M_AMD64) +#define ARCH_X86_64 +#endif +#endif + +#if defined(GCC) || defined(__GNUC__) || defined(__clang__) +#if defined(__x86_64__) +#define ARCH_X86_64 +#endif +#endif + +#endif // WASMER_H_MACROS +"#; // Generate the C bindings in the `OUT_DIR`. out_wasmer_header_file.set_extension("h"); Builder::new() .with_crate(crate_dir.clone()) .with_language(Language::C) .with_include_guard("WASMER_H") + .with_header(WASMER_PRE_HEADER) + .with_define("target_family", "windows", "_WIN32") + .with_define("target_arch", "x86_64", "ARCH_X86_64") .generate() .expect("Unable to generate C bindings") .write_to_file(out_wasmer_header_file.as_path()); @@ -28,6 +49,9 @@ fn main() { .with_crate(crate_dir) .with_language(Language::Cxx) .with_include_guard("WASMER_H") + .with_header(WASMER_PRE_HEADER) + .with_define("target_family", "windows", "_WIN32") + .with_define("target_arch", "x86_64", "ARCH_X86_64") .generate() .expect("Unable to generate C++ bindings") .write_to_file(out_wasmer_header_file.as_path()); diff --git a/lib/runtime-c-api/src/export.rs b/lib/runtime-c-api/src/export.rs index 20bb91c7c..40d370111 100644 --- a/lib/runtime-c-api/src/export.rs +++ b/lib/runtime-c-api/src/export.rs @@ -11,9 +11,9 @@ use crate::{ value::{wasmer_value, wasmer_value_t, wasmer_value_tag}, wasmer_byte_array, wasmer_result_t, }; -use libc::c_int; +use libc::{c_int, c_uint}; use std::{ptr, slice}; -use wasmer_runtime::{Instance, Memory, Module, Value}; +use wasmer_runtime::{Instance, Module, Value}; use wasmer_runtime_core::{export::Export, module::ExportIndex}; /// Intermediate representation of an `Export` instance that is @@ -85,12 +85,41 @@ pub union wasmer_import_export_value { /// List of export/import kinds. #[allow(non_camel_case_types)] #[repr(u32)] -#[derive(Clone)] +#[derive(Clone, PartialEq, Eq)] +// ================ +// ! DANGER ! +// ================ +// Do not modify these values without updating the `TryFrom` implementation below pub enum wasmer_import_export_kind { - WASM_FUNCTION, - WASM_GLOBAL, - WASM_MEMORY, - WASM_TABLE, + WASM_FUNCTION = 0, + WASM_GLOBAL = 1, + WASM_MEMORY = 2, + WASM_TABLE = 3, +} + +impl wasmer_import_export_kind { + pub fn to_str(&self) -> &'static str { + match self { + Self::WASM_FUNCTION => "function", + Self::WASM_GLOBAL => "global", + Self::WASM_MEMORY => "memory", + Self::WASM_TABLE => "table", + } + } +} + +impl std::convert::TryFrom for wasmer_import_export_kind { + type Error = (); + + fn try_from(value: u32) -> Result { + Ok(match value { + 0 => Self::WASM_FUNCTION, + 1 => Self::WASM_GLOBAL, + 2 => Self::WASM_MEMORY, + 3 => Self::WASM_TABLE, + _ => return Err(()), + }) + } } /// Gets export descriptors for the given module @@ -355,7 +384,8 @@ pub unsafe extern "C" fn wasmer_export_to_memory( let export = &named_export.export; if let Export::Memory(exported_memory) = export { - *memory = exported_memory as *const Memory as *mut wasmer_memory_t; + let mem = Box::new(exported_memory.clone()); + *memory = Box::into_raw(mem) as *mut wasmer_memory_t; wasmer_result_t::WASMER_OK } else { update_last_error(CApiError { @@ -390,9 +420,9 @@ pub unsafe extern "C" fn wasmer_export_name(export: *mut wasmer_export_t) -> was pub unsafe extern "C" fn wasmer_export_func_call( func: *const wasmer_export_func_t, params: *const wasmer_value_t, - params_len: c_int, + params_len: c_uint, results: *mut wasmer_value_t, - results_len: c_int, + results_len: c_uint, ) -> wasmer_result_t { if func.is_null() { update_last_error(CApiError { @@ -400,15 +430,25 @@ pub unsafe extern "C" fn wasmer_export_func_call( }); return wasmer_result_t::WASMER_ERROR; } - if params.is_null() { + + if params_len > 0 && params.is_null() { update_last_error(CApiError { msg: "params ptr is null".to_string(), }); return wasmer_result_t::WASMER_ERROR; } - let params: &[wasmer_value_t] = slice::from_raw_parts(params, params_len as usize); - let params: Vec = params.iter().cloned().map(|x| x.into()).collect(); + let params: Vec = { + if params_len <= 0 { + vec![] + } else { + slice::from_raw_parts::(params, params_len as usize) + .iter() + .cloned() + .map(|x| x.into()) + .collect() + } + }; let named_export = &*(func as *mut NamedExport); diff --git a/lib/runtime-c-api/src/import.rs b/lib/runtime-c-api/src/import/mod.rs similarity index 56% rename from lib/runtime-c-api/src/import.rs rename to lib/runtime-c-api/src/import/mod.rs index 2d6f9e99d..5b44eb2b5 100644 --- a/lib/runtime-c-api/src/import.rs +++ b/lib/runtime-c-api/src/import/mod.rs @@ -9,11 +9,11 @@ use crate::{ wasmer_byte_array, wasmer_result_t, }; use libc::c_uint; -use std::{ffi::c_void, ptr, slice, sync::Arc}; +use std::{convert::TryFrom, ffi::c_void, ptr, slice, sync::Arc}; use wasmer_runtime::{Global, Memory, Module, Table}; use wasmer_runtime_core::{ export::{Context, Export, FuncPointer}, - import::ImportObject, + import::{ImportObject, ImportObjectIterator}, module::ImportName, types::{FuncSig, Type}, }; @@ -41,6 +41,10 @@ pub struct wasmer_import_descriptor_t; #[derive(Clone)] pub struct wasmer_import_descriptors_t; +#[repr(C)] +#[derive(Clone)] +pub struct wasmer_import_object_iter_t; + /// Creates a new empty import object. /// See also `wasmer_import_object_append` #[allow(clippy::cast_ptr_alignment)] @@ -51,12 +55,318 @@ pub unsafe extern "C" fn wasmer_import_object_new() -> *mut wasmer_import_object Box::into_raw(import_object) as *mut wasmer_import_object_t } +#[cfg(feature = "wasi")] +mod wasi; + +#[cfg(feature = "wasi")] +pub use self::wasi::*; + +/// Gets an entry from an ImportObject at the name and namespace. +/// Stores `name`, `namespace`, and `import_export_value` in `import`. +/// Thus these must remain valid for the lifetime of `import`. +/// +/// The caller owns all data involved. +/// `import_export_value` will be written to based on `tag`. +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_object_get_import( + import_object: *const wasmer_import_object_t, + namespace: wasmer_byte_array, + name: wasmer_byte_array, + import: *mut wasmer_import_t, + import_export_value: *mut wasmer_import_export_value, + tag: u32, +) -> wasmer_result_t { + let tag: wasmer_import_export_kind = if let Ok(t) = TryFrom::try_from(tag) { + t + } else { + update_last_error(CApiError { + msg: "wasmer_import_export_tag out of range".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + let import_object: &mut ImportObject = &mut *(import_object as *mut ImportObject); + let namespace_str = if let Ok(ns) = namespace.as_str() { + ns + } else { + update_last_error(CApiError { + msg: "error converting namespace to UTF-8 string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + let name_str = if let Ok(name) = name.as_str() { + name + } else { + update_last_error(CApiError { + msg: "error converting name to UTF-8 string".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + }; + if import.is_null() || import_export_value.is_null() { + update_last_error(CApiError { + msg: "pointers to import and import_export_value must not be null".to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + } + let import_out = &mut *import; + let import_export_value_out = &mut *import_export_value; + if let Some(export) = + import_object.maybe_with_namespace(namespace_str, |ns| ns.get_export(name_str)) + { + match export { + Export::Function { .. } => { + if tag != wasmer_import_export_kind::WASM_FUNCTION { + update_last_error(CApiError { + msg: format!("Found function, expected {}", tag.to_str()), + }); + return wasmer_result_t::WASMER_ERROR; + } + import_out.tag = wasmer_import_export_kind::WASM_FUNCTION; + let writer = import_export_value_out.func as *mut Export; + *writer = export.clone(); + } + Export::Memory(memory) => { + if tag != wasmer_import_export_kind::WASM_MEMORY { + update_last_error(CApiError { + msg: format!("Found memory, expected {}", tag.to_str()), + }); + return wasmer_result_t::WASMER_ERROR; + } + import_out.tag = wasmer_import_export_kind::WASM_MEMORY; + let writer = import_export_value_out.func as *mut Memory; + *writer = memory.clone(); + } + Export::Table(table) => { + if tag != wasmer_import_export_kind::WASM_TABLE { + update_last_error(CApiError { + msg: format!("Found table, expected {}", tag.to_str()), + }); + return wasmer_result_t::WASMER_ERROR; + } + import_out.tag = wasmer_import_export_kind::WASM_TABLE; + let writer = import_export_value_out.func as *mut Table; + *writer = table.clone(); + } + Export::Global(global) => { + if tag != wasmer_import_export_kind::WASM_GLOBAL { + update_last_error(CApiError { + msg: format!("Found global, expected {}", tag.to_str()), + }); + return wasmer_result_t::WASMER_ERROR; + } + import_out.tag = wasmer_import_export_kind::WASM_GLOBAL; + let writer = import_export_value_out.func as *mut Global; + *writer = global.clone(); + } + } + + import_out.value = *import_export_value; + import_out.module_name = namespace; + import_out.import_name = name; + + wasmer_result_t::WASMER_OK + } else { + update_last_error(CApiError { + msg: format!("Export {} {} not found", namespace_str, name_str), + }); + wasmer_result_t::WASMER_ERROR + } +} + +/// private wrapper data type used for casting +#[repr(C)] +struct WasmerImportObjectIterator( + std::iter::Peekable::Item>>>, +); + +/// Create an iterator over the functions in the import object. +/// Get the next import with `wasmer_import_object_iter_next` +/// Free the iterator with `wasmer_import_object_iter_destroy` +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_object_iterate_functions( + import_object: *const wasmer_import_object_t, +) -> *mut wasmer_import_object_iter_t { + if import_object.is_null() { + update_last_error(CApiError { + msg: "import_object must not be null".to_owned(), + }); + return std::ptr::null_mut(); + } + let import_object: &ImportObject = &*(import_object as *const ImportObject); + let iter_inner = Box::new(import_object.clone_ref().into_iter().filter(|(_, _, e)| { + if let Export::Function { .. } = e { + true + } else { + false + } + })) as Box::Item>>; + let iterator = Box::new(WasmerImportObjectIterator(iter_inner.peekable())); + + Box::into_raw(iterator) as *mut wasmer_import_object_iter_t +} + +/// Writes the next value to `import`. `WASMER_ERROR` is returned if there +/// was an error or there's nothing left to return. +/// +/// To free the memory allocated here, pass the import to `wasmer_import_object_imports_destroy`. +/// To check if the iterator is done, use `wasmer_import_object_iter_at_end`. +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_object_iter_next( + import_object_iter: *mut wasmer_import_object_iter_t, + import: *mut wasmer_import_t, +) -> wasmer_result_t { + if import_object_iter.is_null() || import.is_null() { + update_last_error(CApiError { + msg: "import_object_iter and import must not be null".to_owned(), + }); + return wasmer_result_t::WASMER_ERROR; + } + + let iter = &mut *(import_object_iter as *mut WasmerImportObjectIterator); + let out = &mut *import; + // TODO: the copying here can be optimized away, we just need to use a different type of + // iterator internally + if let Some((namespace, name, export)) = iter.0.next() { + let ns = { + let mut n = namespace.clone(); + n.shrink_to_fit(); + n.into_bytes() + }; + let ns_bytes = wasmer_byte_array { + bytes: ns.as_ptr(), + bytes_len: ns.len() as u32, + }; + + let name = { + let mut n = name.clone(); + n.shrink_to_fit(); + n.into_bytes() + }; + let name_bytes = wasmer_byte_array { + bytes: name.as_ptr(), + bytes_len: name.len() as u32, + }; + + out.module_name = ns_bytes; + out.import_name = name_bytes; + + std::mem::forget(ns); + std::mem::forget(name); + + match export { + Export::Function { .. } => { + let func = Box::new(export.clone()); + + out.tag = wasmer_import_export_kind::WASM_FUNCTION; + out.value = wasmer_import_export_value { + func: Box::into_raw(func) as *mut _ as *const _, + }; + } + Export::Global(global) => { + let glbl = Box::new(global.clone()); + + out.tag = wasmer_import_export_kind::WASM_GLOBAL; + out.value = wasmer_import_export_value { + global: Box::into_raw(glbl) as *mut _ as *const _, + }; + } + Export::Memory(memory) => { + let mem = Box::new(memory.clone()); + + out.tag = wasmer_import_export_kind::WASM_MEMORY; + out.value = wasmer_import_export_value { + memory: Box::into_raw(mem) as *mut _ as *const _, + }; + } + Export::Table(table) => { + let tbl = Box::new(table.clone()); + + out.tag = wasmer_import_export_kind::WASM_TABLE; + out.value = wasmer_import_export_value { + memory: Box::into_raw(tbl) as *mut _ as *const _, + }; + } + } + + wasmer_result_t::WASMER_OK + } else { + wasmer_result_t::WASMER_ERROR + } +} + +/// Returns true if further calls to `wasmer_import_object_iter_next` will +/// not return any new data +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_object_iter_at_end( + import_object_iter: *mut wasmer_import_object_iter_t, +) -> bool { + if import_object_iter.is_null() { + update_last_error(CApiError { + msg: "import_object_iter must not be null".to_owned(), + }); + return true; + } + let iter = &mut *(import_object_iter as *mut WasmerImportObjectIterator); + + iter.0.peek().is_none() +} + +/// Frees the memory allocated by `wasmer_import_object_iterate_functions` +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_object_iter_destroy( + import_object_iter: *mut wasmer_import_object_iter_t, +) { + if !import_object_iter.is_null() { + let _ = Box::from_raw(import_object_iter as *mut WasmerImportObjectIterator); + } +} + +/// Frees the memory allocated in `wasmer_import_object_iter_next` +/// +/// This function does not free the memory in `wasmer_import_object_t`; +/// it only frees memory allocated while querying a `wasmer_import_object_t`. +#[no_mangle] +pub unsafe extern "C" fn wasmer_import_object_imports_destroy( + imports: *mut wasmer_import_t, + imports_len: u32, +) { + if imports.is_null() { + return; + } + let imports: &[wasmer_import_t] = &*slice::from_raw_parts_mut(imports, imports_len as usize); + for import in imports { + let _namespace: Vec = Vec::from_raw_parts( + import.module_name.bytes as *mut u8, + import.module_name.bytes_len as usize, + import.module_name.bytes_len as usize, + ); + let _name: Vec = Vec::from_raw_parts( + import.import_name.bytes as *mut u8, + import.import_name.bytes_len as usize, + import.import_name.bytes_len as usize, + ); + match import.tag { + wasmer_import_export_kind::WASM_FUNCTION => { + let _: Box = Box::from_raw(import.value.func as *mut _); + } + wasmer_import_export_kind::WASM_GLOBAL => { + let _: Box = Box::from_raw(import.value.global as *mut _); + } + wasmer_import_export_kind::WASM_MEMORY => { + let _: Box = Box::from_raw(import.value.memory as *mut _); + } + wasmer_import_export_kind::WASM_TABLE => { + let _: Box = Box::from_raw(import.value.table as *mut _); + } + } + } +} + /// Extends an existing import object with new imports #[allow(clippy::cast_ptr_alignment)] #[no_mangle] pub unsafe extern "C" fn wasmer_import_object_extend( import_object: *mut wasmer_import_object_t, - imports: *mut wasmer_import_t, + imports: *const wasmer_import_t, imports_len: c_uint, ) -> wasmer_result_t { let import_object: &mut ImportObject = &mut *(import_object as *mut ImportObject); diff --git a/lib/runtime-c-api/src/import/wasi.rs b/lib/runtime-c-api/src/import/wasi.rs new file mode 100644 index 000000000..3df3c7f70 --- /dev/null +++ b/lib/runtime-c-api/src/import/wasi.rs @@ -0,0 +1,101 @@ +use super::*; +use crate::get_slice_checked; +use std::path::PathBuf; + +/// Opens a directory that's visible to the WASI module as `alias` but +/// is backed by the host file at `host_file_path` +#[repr(C)] +pub struct wasmer_wasi_map_dir_entry_t { + /// What the WASI module will see in its virtual root + pub alias: wasmer_byte_array, + /// The backing file that the WASI module will interact with via the alias + pub host_file_path: wasmer_byte_array, +} + +impl wasmer_wasi_map_dir_entry_t { + /// Converts the data into owned, Rust types + pub unsafe fn as_tuple(&self) -> Result<(String, PathBuf), std::str::Utf8Error> { + let alias = self.alias.as_str()?.to_owned(); + let host_path = std::path::PathBuf::from(self.host_file_path.as_str()?); + + Ok((alias, host_path)) + } +} + +/// Creates a WASI import object. +/// +/// This function treats null pointers as empty collections. +/// For example, passing null for a string in `args`, will lead to a zero +/// length argument in that position. +#[no_mangle] +pub unsafe extern "C" fn wasmer_wasi_generate_import_object( + args: *const wasmer_byte_array, + args_len: c_uint, + envs: *const wasmer_byte_array, + envs_len: c_uint, + preopened_files: *const wasmer_byte_array, + preopened_files_len: c_uint, + mapped_dirs: *const wasmer_wasi_map_dir_entry_t, + mapped_dirs_len: c_uint, +) -> *mut wasmer_import_object_t { + let arg_list = get_slice_checked(args, args_len as usize); + let env_list = get_slice_checked(envs, envs_len as usize); + let preopened_file_list = get_slice_checked(preopened_files, preopened_files_len as usize); + let mapped_dir_list = get_slice_checked(mapped_dirs, mapped_dirs_len as usize); + + wasmer_wasi_generate_import_object_inner( + arg_list, + env_list, + preopened_file_list, + mapped_dir_list, + ) + .unwrap_or(std::ptr::null_mut()) +} + +/// Inner function that wraps error handling +fn wasmer_wasi_generate_import_object_inner( + arg_list: &[wasmer_byte_array], + env_list: &[wasmer_byte_array], + preopened_file_list: &[wasmer_byte_array], + mapped_dir_list: &[wasmer_wasi_map_dir_entry_t], +) -> Result<*mut wasmer_import_object_t, std::str::Utf8Error> { + let arg_vec = arg_list.iter().map(|arg| unsafe { arg.as_vec() }).collect(); + let env_vec = env_list + .iter() + .map(|env_var| unsafe { env_var.as_vec() }) + .collect(); + let po_file_vec = preopened_file_list + .iter() + .map(|po_file| Ok(unsafe { PathBuf::from(po_file.as_str()?) }.to_owned())) + .collect::, _>>()?; + let mapped_dir_vec = mapped_dir_list + .iter() + .map(|entry| unsafe { entry.as_tuple() }) + .collect::, _>>()?; + + let import_object = Box::new(wasmer_wasi::generate_import_object( + arg_vec, + env_vec, + po_file_vec, + mapped_dir_vec, + )); + Ok(Box::into_raw(import_object) as *mut wasmer_import_object_t) +} + +/// Convenience function that creates a WASI import object with no arguments, +/// environment variables, preopened files, or mapped directories. +/// +/// This function is the same as calling [`wasmer_wasi_generate_import_object`] with all +/// empty values. +#[no_mangle] +pub unsafe extern "C" fn wasmer_wasi_generate_default_import_object() -> *mut wasmer_import_object_t +{ + let import_object = Box::new(wasmer_wasi::generate_import_object( + vec![], + vec![], + vec![], + vec![], + )); + + Box::into_raw(import_object) as *mut wasmer_import_object_t +} diff --git a/lib/runtime-c-api/src/instance.rs b/lib/runtime-c-api/src/instance.rs index 69fa0131a..b39e52d7e 100644 --- a/lib/runtime-c-api/src/instance.rs +++ b/lib/runtime-c-api/src/instance.rs @@ -75,6 +75,7 @@ pub unsafe extern "C" fn wasmer_instantiate( let namespace = namespaces.entry(module_name).or_insert_with(Namespace::new); + // TODO check that tag is actually in bounds here let export = match import.tag { wasmer_import_export_kind::WASM_MEMORY => { let mem = import.value.memory as *mut Memory; diff --git a/lib/runtime-c-api/src/lib.rs b/lib/runtime-c-api/src/lib.rs index 0fde6d330..f04640754 100644 --- a/lib/runtime-c-api/src/lib.rs +++ b/lib/runtime-c-api/src/lib.rs @@ -1,3 +1,6 @@ +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + //! # Wasmer Runtime C API //! //! Wasmer is a standalone JIT WebAssembly runtime, aiming to be fully @@ -98,7 +101,9 @@ pub mod instance; pub mod memory; pub mod module; pub mod table; -#[cfg(all(unix, target_arch = "x86_64"))] +// `not(target_family = "windows")` is simpler than `unix`. See build.rs +// if you want to change the meaning of these `cfg`s in the header file. +#[cfg(all(not(target_family = "windows"), target_arch = "x86_64"))] pub mod trampoline; pub mod value; @@ -126,3 +131,34 @@ pub struct wasmer_byte_array { pub bytes: *const u8, pub bytes_len: u32, } + +impl wasmer_byte_array { + /// Get the data as a slice + pub unsafe fn as_slice<'a>(&self) -> &'a [u8] { + get_slice_checked(self.bytes, self.bytes_len as usize) + } + + /// Copy the data into an owned Vec + pub unsafe fn as_vec(&self) -> Vec { + let mut out = Vec::with_capacity(self.bytes_len as usize); + out.extend_from_slice(self.as_slice()); + + out + } + + /// Read the data as a &str, returns an error if the string is not valid UTF8 + pub unsafe fn as_str<'a>(&self) -> Result<&'a str, std::str::Utf8Error> { + std::str::from_utf8(self.as_slice()) + } +} + +/// Gets a slice from a pointer and a length, returning an empty slice if the +/// pointer is null +#[inline] +pub(crate) unsafe fn get_slice_checked<'a, T>(ptr: *const T, len: usize) -> &'a [T] { + if ptr.is_null() { + &[] + } else { + std::slice::from_raw_parts(ptr, len) + } +} diff --git a/lib/runtime-c-api/src/memory.rs b/lib/runtime-c-api/src/memory.rs index 4628d8baa..21f14bbf7 100644 --- a/lib/runtime-c-api/src/memory.rs +++ b/lib/runtime-c-api/src/memory.rs @@ -1,6 +1,6 @@ //! Create, read, write, grow, destroy memory of an instance. -use crate::{error::update_last_error, wasmer_limits_t, wasmer_result_t}; +use crate::{error::update_last_error, error::CApiError, wasmer_limits_t, wasmer_result_t}; use std::cell::Cell; use wasmer_runtime::Memory; use wasmer_runtime_core::{ @@ -31,12 +31,17 @@ pub unsafe extern "C" fn wasmer_memory_new( } else { None }; - let desc = MemoryDescriptor { - minimum: Pages(limits.min), - maximum: max, - shared: false, + let desc = MemoryDescriptor::new(Pages(limits.min), max, false); + let new_desc = match desc { + Ok(desc) => desc, + Err(error) => { + update_last_error(CApiError { + msg: error.to_string(), + }); + return wasmer_result_t::WASMER_ERROR; + } }; - let result = Memory::new(desc); + let result = Memory::new(new_desc); let new_memory = match result { Ok(memory) => memory, Err(error) => { diff --git a/lib/runtime-c-api/src/trampoline.rs b/lib/runtime-c-api/src/trampoline.rs index 923622aa5..ed7b8971b 100644 --- a/lib/runtime-c-api/src/trampoline.rs +++ b/lib/runtime-c-api/src/trampoline.rs @@ -61,7 +61,7 @@ pub unsafe extern "C" fn wasmer_trampoline_buffer_builder_build( #[allow(clippy::cast_ptr_alignment)] pub unsafe extern "C" fn wasmer_trampoline_buffer_destroy(buffer: *mut wasmer_trampoline_buffer_t) { if !buffer.is_null() { - Box::from_raw(buffer); + Box::from_raw(buffer as *mut TrampolineBuffer); } } diff --git a/lib/runtime-c-api/tests/.gitignore b/lib/runtime-c-api/tests/.gitignore index 26a85acee..b64fa610c 100644 --- a/lib/runtime-c-api/tests/.gitignore +++ b/lib/runtime-c-api/tests/.gitignore @@ -25,3 +25,5 @@ test-tables test-validate test-context test-module-import-instantiate +test-wasi-import-object + diff --git a/lib/runtime-c-api/tests/CMakeLists.txt b/lib/runtime-c-api/tests/CMakeLists.txt index 1a3351123..e4a573e63 100644 --- a/lib/runtime-c-api/tests/CMakeLists.txt +++ b/lib/runtime-c-api/tests/CMakeLists.txt @@ -6,6 +6,7 @@ add_executable(test-exports test-exports.c) add_executable(test-globals test-globals.c) add_executable(test-import-function test-import-function.c) add_executable(test-imports test-imports.c) +add_executable(test-import-object test-import-object.c) add_executable(test-instantiate test-instantiate.c) add_executable(test-memory test-memory.c) add_executable(test-module test-module.c) @@ -17,8 +18,12 @@ add_executable(test-validate test-validate.c) add_executable(test-context test-context.c) add_executable(test-module-import-instantiate test-module-import-instantiate.c) +if (DEFINED WASI_TESTS) + add_executable(test-wasi-import-object test-wasi-import-object.c) +endif() + find_library( - WASMER_LIB NAMES libwasmer_runtime_c_api.dylib libwasmer_runtime_c_api.so libwasmer_runtime_c_api.dll + WASMER_LIB NAMES libwasmer_runtime_c_api.dylib libwasmer_runtime_c_api.so wasmer_runtime_c_api.dll PATHS ${CMAKE_SOURCE_DIR}/../../../target/release/ ) @@ -58,6 +63,17 @@ target_link_libraries(test-imports general ${WASMER_LIB}) target_compile_options(test-imports PRIVATE ${COMPILER_OPTIONS}) add_test(test-imports test-imports) +target_link_libraries(test-import-object general ${WASMER_LIB}) +target_compile_options(test-import-object PRIVATE ${COMPILER_OPTIONS}) +add_test(test-import-object test-import-object) + + +if (DEFINED WASI_TESTS) + target_link_libraries(test-wasi-import-object general ${WASMER_LIB}) + target_compile_options(test-wasi-import-object PRIVATE ${COMPILER_OPTIONS}) + add_test(test-wasi-import-object test-wasi-import-object) +endif() + target_link_libraries(test-instantiate general ${WASMER_LIB}) target_compile_options(test-instantiate PRIVATE ${COMPILER_OPTIONS}) add_test(test-instantiate test-instantiate) diff --git a/lib/runtime-c-api/tests/assets/README.md b/lib/runtime-c-api/tests/assets/README.md new file mode 100644 index 000000000..52e92c052 --- /dev/null +++ b/lib/runtime-c-api/tests/assets/README.md @@ -0,0 +1,3 @@ +These are used in tests in the parent directory. + +To keep the generated wasm small, use `wasm-opt` and `wasm-strip` from wabt-tools (can be installed via wapm). Addtionally, consider passing the `-C opt-level=z` flag to `rustc` to optimize for size. diff --git a/lib/runtime-c-api/tests/assets/exports.rs b/lib/runtime-c-api/tests/assets/exports.rs new file mode 100644 index 000000000..9e55985cf --- /dev/null +++ b/lib/runtime-c-api/tests/assets/exports.rs @@ -0,0 +1,37 @@ +#[no_mangle] +pub extern "C" fn sum(x: i32, y: i32) -> i32 { + x + y +} + +#[no_mangle] +pub extern "C" fn arity_0() -> i32 { + 42 +} + +#[no_mangle] +pub extern "C" fn i32_i32(x: i32) -> i32 { + x +} + +#[no_mangle] +pub extern "C" fn i64_i64(x: i64) -> i64 { + x +} + +#[no_mangle] +pub extern "C" fn f32_f32(x: f32) -> f32 { + x +} + +#[no_mangle] +pub extern "C" fn f64_f64(x: f64) -> f64 { + x +} + +#[no_mangle] +pub extern "C" fn string() -> *const u8 { + b"Hello, World!\0".as_ptr() +} + +#[no_mangle] +pub extern "C" fn void() {} diff --git a/lib/runtime-c-api/tests/assets/exports.wasm b/lib/runtime-c-api/tests/assets/exports.wasm new file mode 100644 index 000000000..6bea6de33 Binary files /dev/null and b/lib/runtime-c-api/tests/assets/exports.wasm differ diff --git a/lib/runtime-c-api/tests/assets/extended_wasi.rs b/lib/runtime-c-api/tests/assets/extended_wasi.rs new file mode 100644 index 000000000..31c659f8e --- /dev/null +++ b/lib/runtime-c-api/tests/assets/extended_wasi.rs @@ -0,0 +1,31 @@ +extern "C" { + fn host_print(ptr: u32, len: u32); +} + +fn main() { + let args = std::env::args().collect::>(); + + println!("Found {} args on program {}", args.len(), args[0]); + + let env_vars = std::env::vars() + .map(|(arg, val)| format!("{}={}", arg, val)) + .collect::>(); + let env_var_list = env_vars.join(", "); + + println!("Found {} env vars: {}", env_vars.len(), env_var_list); + + let dirs_in_root = std::fs::read_dir("/") + .unwrap() + .map(|e| e.map(|inner| format!("{:?}", inner))) + .collect::, _>>() + .unwrap(); + + println!( + "Found {} pre opened dirs: {}", + dirs_in_root.len(), + dirs_in_root.join(", ") + ); + + const HOST_STR: &str = "This string came from a WASI module"; + unsafe { host_print(HOST_STR.as_ptr() as u32, HOST_STR.len() as u32) }; +} diff --git a/lib/runtime-c-api/tests/assets/extended_wasi.wasm b/lib/runtime-c-api/tests/assets/extended_wasi.wasm new file mode 100755 index 000000000..b8eedd722 Binary files /dev/null and b/lib/runtime-c-api/tests/assets/extended_wasi.wasm differ diff --git a/lib/runtime-c-api/tests/runtime_c_api_tests.rs b/lib/runtime-c-api/tests/runtime_c_api_tests.rs index 0e3257786..ed4146363 100644 --- a/lib/runtime-c-api/tests/runtime_c_api_tests.rs +++ b/lib/runtime-c-api/tests/runtime_c_api_tests.rs @@ -4,7 +4,14 @@ use std::process::Command; fn test_c_api() { let project_tests_dir = concat!(env!("CARGO_MANIFEST_DIR"), "/tests"); - run_command("cmake", project_tests_dir, vec!["."]); + let cmake_args = vec![ + ".", + #[cfg(feature = "wasi")] + "-DWASI_TESTS=ON", + ]; + // we use -f so it doesn't fail if the fiel doesn't exist + run_command("rm", project_tests_dir, vec!["-f", "CMakeCache.txt"]); + run_command("cmake", project_tests_dir, cmake_args); run_command("make", project_tests_dir, vec!["-Wdev", "-Werror=dev"]); run_command("make", project_tests_dir, vec!["test", "ARGS=\"-V\""]); } diff --git a/lib/runtime-c-api/tests/test-exports.c b/lib/runtime-c-api/tests/test-exports.c index 77ff6683b..e245414be 100644 --- a/lib/runtime-c-api/tests/test-exports.c +++ b/lib/runtime-c-api/tests/test-exports.c @@ -1,85 +1,445 @@ +#include #include #include "../wasmer.h" #include #include +#include int main() { - // Read the wasm file bytes - FILE *file = fopen("assets/sum.wasm", "r"); - fseek(file, 0, SEEK_END); - long len = ftell(file); - uint8_t *bytes = malloc(len); - fseek(file, 0, SEEK_SET); - fread(bytes, 1, len, file); - fclose(file); + // Read the WebAssembly bytes. + uint8_t *wasm_bytes = NULL; + long wasm_bytes_length = 0; + + { + FILE *file = fopen("assets/exports.wasm", "r"); + fseek(file, 0, SEEK_END); + wasm_bytes_length = ftell(file); + wasm_bytes = (uint8_t *) malloc(wasm_bytes_length); + fseek(file, 0, SEEK_SET); + fread(wasm_bytes, 1, wasm_bytes_length, file); + fclose(file); + } wasmer_import_t imports[] = {}; wasmer_instance_t *instance = NULL; - wasmer_result_t compile_result = wasmer_instantiate(&instance, bytes, len, imports, 0); - printf("Compile result: %d\n", compile_result); + wasmer_result_t compile_result = wasmer_instantiate(&instance, wasm_bytes, wasm_bytes_length, imports, 0); + assert(compile_result == WASMER_OK); wasmer_exports_t *exports = NULL; wasmer_instance_exports(instance, &exports); - int exports_len = wasmer_exports_len(exports); - printf("exports_len: %d\n", exports_len); - assert(exports_len == 1); + int exports_length = wasmer_exports_len(exports); + printf("Number of exports: %d\n", exports_length); - wasmer_export_t *export = wasmer_exports_get(exports, 0); + { + printf("\nCheck the `sum` exported function\n"); - wasmer_import_export_kind kind = wasmer_export_kind(export); - assert(kind == WASM_FUNCTION); - const wasmer_export_func_t *func = wasmer_export_to_func(export); + wasmer_export_t *export = wasmer_exports_get(exports, 3); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); - wasmer_byte_array name_bytes = wasmer_export_name(export); - assert(name_bytes.bytes_len == 3); - char expected[] = {'s', 'u', 'm'}; - for(int idx = 0; idx < 3; idx++){ - printf("%c\n", name_bytes.bytes[idx]); - assert(name_bytes.bytes[idx] == expected[idx]); + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("sum") - 1); + assert(memcmp(name_bytes.bytes, "sum", sizeof("sum") - 1) == 0); + + printf("Check arity\n"); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 2); + assert(outputs_arity == 1); + + printf("Check signature\n"); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_I32); + assert(input_types[1] == WASM_I32); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I32); + + free(output_types); + + printf("Call the exported function\n"); + + wasmer_value_t input_0; + input_0.tag = WASM_I32; + input_0.value.I32 = 7; + + wasmer_value_t input_1; + input_1.tag = WASM_I32; + input_1.value.I32 = 8; + + wasmer_value_t inputs[] = {input_0, input_1}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Call result: %d\n", call_result); + printf("Result: %d\n", outputs[0].value.I32); + + assert(outputs[0].value.I32 == 15); + assert(call_result == WASMER_OK); } - uint32_t params_arity; - wasmer_export_func_params_arity(func, ¶ms_arity); - assert(params_arity == 2); + { + printf("\nCheck the `arity_0` exported function\n"); - wasmer_value_tag *params_sig = malloc(sizeof(wasmer_value_tag) * params_arity); - wasmer_export_func_params(func, params_sig , params_arity); - assert(params_sig[0] == WASM_I32); - assert(params_sig[1] == WASM_I32); - free(params_sig); + wasmer_export_t *export = wasmer_exports_get(exports, 4); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); - uint32_t returns_arity; - wasmer_export_func_returns_arity(func, &returns_arity); - assert(returns_arity == 1); + assert(export_kind == WASM_FUNCTION); - wasmer_value_tag *returns_sig = malloc(sizeof(wasmer_value_tag) * returns_arity); - wasmer_export_func_returns(func, returns_sig , returns_arity); - assert(returns_sig[0] == WASM_I32); - free(returns_sig); + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); - wasmer_value_t param_one; - param_one.tag = WASM_I32; - param_one.value.I32 = 7; - wasmer_value_t param_two; - param_two.tag = WASM_I32; - param_two.value.I32 = 8; - wasmer_value_t params[] = {param_one, param_two}; - wasmer_value_t result_one; - wasmer_value_t results[] = {result_one}; + assert(name_bytes.bytes_len == sizeof("arity_0") - 1); + assert(memcmp(name_bytes.bytes, "arity_0", sizeof("arity_0") - 1) == 0); - wasmer_result_t call_result = wasmer_export_func_call(func, params, params_arity, results, returns_arity); - printf("Call result: %d\n", call_result); - printf("Result: %d\n", results[0].value.I32); - assert(results[0].value.I32 == 15); - assert(call_result == WASMER_OK); + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 0); + assert(outputs_arity == 1); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I32); + + free(output_types); + + wasmer_value_t inputs[] = {}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %d\n", outputs[0].value.I32); + + assert(outputs[0].value.I32 == 42); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `i32_i32` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 5); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("i32_i32") - 1); + assert(memcmp(name_bytes.bytes, "i32_i32", sizeof("i32_i32") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 1); + assert(outputs_arity == 1); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_I32); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I32); + + free(output_types); + + wasmer_value_t input_0; + input_0.tag = WASM_I32; + input_0.value.I32 = 7; + wasmer_value_t inputs[] = {input_0}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %d\n", outputs[0].value.I32); + + assert(outputs[0].value.I32 == 7); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `i64_i64` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 6); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("i64_i64") - 1); + assert(memcmp(name_bytes.bytes, "i64_i64", sizeof("i64_i64") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 1); + assert(outputs_arity == 1); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_I64); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I64); + + free(output_types); + + wasmer_value_t input_0; + input_0.tag = WASM_I64; + input_0.value.I64 = 7; + wasmer_value_t inputs[] = {input_0}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %" PRId64 "\n", outputs[0].value.I64); + + assert(outputs[0].value.I64 == 7); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `f32_f32` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 7); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("f32_f32") - 1); + assert(memcmp(name_bytes.bytes, "f32_f32", sizeof("f32_f32") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 1); + assert(outputs_arity == 1); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_F32); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_F32); + + free(output_types); + + wasmer_value_t input_0; + input_0.tag = WASM_F32; + input_0.value.F32 = 7.42; + wasmer_value_t inputs[] = {input_0}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %f\n", outputs[0].value.F32); + + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `f64_f64` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 8); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("f64_f64") - 1); + assert(memcmp(name_bytes.bytes, "f64_f64", sizeof("f64_f64") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 1); + assert(outputs_arity == 1); + + wasmer_value_tag *input_types = (wasmer_value_tag *) calloc(inputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_params(exported_function, input_types, inputs_arity); + + assert(input_types[0] == WASM_F64); + + free(input_types); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_F64); + + free(output_types); + + wasmer_value_t input_0; + input_0.tag = WASM_F64; + input_0.value.F64 = 7.42; + wasmer_value_t inputs[] = {input_0}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %f\n", outputs[0].value.F64); + + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `string` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 9); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("string") - 1); + assert(memcmp(name_bytes.bytes, "string", sizeof("string") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 0); + assert(outputs_arity == 1); + + wasmer_value_tag *output_types = (wasmer_value_tag *) calloc(outputs_arity, sizeof(wasmer_value_tag)); + wasmer_export_func_returns(exported_function, output_types, outputs_arity); + + assert(output_types[0] == WASM_I32); + + free(output_types); + + wasmer_value_t inputs[] = {}; + + wasmer_value_t output_0; + wasmer_value_t outputs[] = {output_0}; + + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + + printf("Result: %d\n", outputs[0].value.I32); + + assert(outputs[0].value.I32 == 1048576); + assert(call_result == WASMER_OK); + } + + { + printf("\nCheck the `void` exported function\n"); + + wasmer_export_t *export = wasmer_exports_get(exports, 10); + wasmer_import_export_kind export_kind = wasmer_export_kind(export); + + assert(export_kind == WASM_FUNCTION); + + const wasmer_export_func_t *exported_function = wasmer_export_to_func(export); + wasmer_byte_array name_bytes = wasmer_export_name(export); + + assert(name_bytes.bytes_len == sizeof("void") - 1); + assert(memcmp(name_bytes.bytes, "void", sizeof("void") - 1) == 0); + + uint32_t inputs_arity; + wasmer_export_func_params_arity(exported_function, &inputs_arity); + + uint32_t outputs_arity; + wasmer_export_func_returns_arity(exported_function, &outputs_arity); + + assert(inputs_arity == 0); + assert(outputs_arity == 0); + + wasmer_value_t inputs[] = {}; + wasmer_value_t outputs[] = {}; + + { + wasmer_result_t call_result = wasmer_export_func_call(exported_function, inputs, inputs_arity, outputs, outputs_arity); + assert(call_result == WASMER_OK); + } + + { + wasmer_result_t call_result = wasmer_export_func_call(exported_function, NULL, inputs_arity, NULL, outputs_arity); + assert(call_result == WASMER_OK); + } + } + + printf("\nDestroy instance\n"); - printf("Destroy instance\n"); wasmer_instance_destroy(instance); + printf("Destroy exports\n"); + wasmer_exports_destroy(exports); + return 0; } diff --git a/lib/runtime-c-api/tests/test-import-object b/lib/runtime-c-api/tests/test-import-object new file mode 100755 index 000000000..bb0a97db3 Binary files /dev/null and b/lib/runtime-c-api/tests/test-import-object differ diff --git a/lib/runtime-c-api/tests/test-import-object.c b/lib/runtime-c-api/tests/test-import-object.c new file mode 100644 index 000000000..640f16640 --- /dev/null +++ b/lib/runtime-c-api/tests/test-import-object.c @@ -0,0 +1,172 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +bool static print_str_called = false; + +// Host function that will be imported into the Web Assembly Instance +void print_str(const wasmer_instance_context_t *ctx, int32_t ptr, int32_t len) +{ + print_str_called = true; + const wasmer_memory_t *memory = wasmer_instance_context_memory(ctx, 0); + uint32_t mem_len = wasmer_memory_length(memory); + uint8_t *mem_bytes = wasmer_memory_data(memory); + printf("%.*s", len, mem_bytes + ptr); +} + +// Use the last_error API to retrieve error messages +void print_wasmer_error() +{ + int error_len = wasmer_last_error_length(); + printf("Error len: `%d`\n", error_len); + char *error_str = malloc(error_len); + wasmer_last_error_message(error_str, error_len); + printf("Error str: `%s`\n", error_str); +} + +int main() +{ + // Create a new func to hold the parameter and signature + // of our `print_str` host function + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) print_str, params_sig, 2, returns_sig, 0); + + // Create module name for our imports + // represented in bytes for UTF-8 compatability + const char *module_name = "env"; + wasmer_byte_array module_name_bytes; + module_name_bytes.bytes = (const uint8_t *) module_name; + module_name_bytes.bytes_len = strlen(module_name); + + // Define a function import + const char *import_name = "_print_str"; + wasmer_byte_array import_name_bytes; + import_name_bytes.bytes = (const uint8_t *) import_name; + import_name_bytes.bytes_len = strlen(import_name); + wasmer_import_t func_import; + func_import.module_name = module_name_bytes; + func_import.import_name = import_name_bytes; + func_import.tag = WASM_FUNCTION; + func_import.value.func = func; + + // Define a memory import + const char *import_memory_name = "memory"; + wasmer_byte_array import_memory_name_bytes; + import_memory_name_bytes.bytes = (const uint8_t *) import_memory_name; + import_memory_name_bytes.bytes_len = strlen(import_memory_name); + wasmer_import_t memory_import; + memory_import.module_name = module_name_bytes; + memory_import.import_name = import_memory_name_bytes; + memory_import.tag = WASM_MEMORY; + wasmer_memory_t *memory = NULL; + wasmer_limits_t descriptor; + descriptor.min = 256; + wasmer_limit_option_t max; + max.has_some = true; + max.some = 256; + descriptor.max = max; + wasmer_result_t memory_result = wasmer_memory_new(&memory, descriptor); + if (memory_result != WASMER_OK) + { + print_wasmer_error(); + } + memory_import.value.memory = memory; + + // Define a global import + const char *import_global_name = "__memory_base"; + wasmer_byte_array import_global_name_bytes; + import_global_name_bytes.bytes = (const uint8_t *) import_global_name; + import_global_name_bytes.bytes_len = strlen(import_global_name); + wasmer_import_t global_import; + global_import.module_name = module_name_bytes; + global_import.import_name = import_global_name_bytes; + global_import.tag = WASM_GLOBAL; + wasmer_value_t val; + val.tag = WASM_I32; + val.value.I32 = 1024; + wasmer_global_t *global = wasmer_global_new(val, false); + global_import.value.global = global; + + // Define a table import + const char *import_table_name = "table"; + wasmer_byte_array import_table_name_bytes; + import_table_name_bytes.bytes = (const uint8_t *) import_table_name; + import_table_name_bytes.bytes_len = strlen(import_table_name); + wasmer_import_t table_import; + table_import.module_name = module_name_bytes; + table_import.import_name = import_table_name_bytes; + table_import.tag = WASM_TABLE; + wasmer_table_t *table = NULL; + wasmer_limits_t table_descriptor; + table_descriptor.min = 256; + wasmer_limit_option_t table_max; + table_max.has_some = true; + table_max.some = 256; + table_descriptor.max = table_max; + wasmer_result_t table_result = wasmer_table_new(&table, table_descriptor); + if (table_result != WASMER_OK) + { + print_wasmer_error(); + } + table_import.value.table = table; + + // Define an empty import object + wasmer_import_object_t *import_object = wasmer_import_object_new(); + // Create our imports + wasmer_import_t imports[] = {func_import, global_import, memory_import, table_import}; + int imports_len = sizeof(imports) / sizeof(imports[0]); + // Add our imports to the import object + wasmer_import_object_extend(import_object, imports, imports_len); + + // Read the wasm file bytes + FILE *file = fopen("assets/hello_wasm.wasm", "r"); + fseek(file, 0, SEEK_END); + long len = ftell(file); + uint8_t *bytes = malloc(len); + fseek(file, 0, SEEK_SET); + fread(bytes, 1, len, file); + fclose(file); + + wasmer_module_t *module = NULL; + // Compile the WebAssembly module + wasmer_result_t compile_result = wasmer_compile(&module, bytes, len); + printf("Compile result: %d\n", compile_result); + if (compile_result != WASMER_OK) + { + print_wasmer_error(); + } + assert(compile_result == WASMER_OK); + + // Instantiatoe the module with our import_object + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiate_result = wasmer_module_import_instantiate(&instance, module, import_object); + printf("Instantiate result: %d\n", instantiate_result); + if (instantiate_result != WASMER_OK) + { + print_wasmer_error(); + } + assert(instantiate_result == WASMER_OK); + + // Call the exported "hello_wasm" function of our instance + wasmer_value_t params[] = {}; + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + wasmer_result_t call_result = wasmer_instance_call(instance, "_hello_wasm", params, 0, results, 1); + printf("Call result: %d\n", call_result); + assert(call_result == WASMER_OK); + assert(print_str_called); + + // Use *_destroy methods to cleanup as specified in the header documentation + wasmer_import_func_destroy(func); + wasmer_global_destroy(global); + wasmer_memory_destroy(memory); + wasmer_table_destroy(table); + wasmer_instance_destroy(instance); + wasmer_import_object_destroy(import_object); + wasmer_module_destroy(module); + + return 0; +} diff --git a/lib/runtime-c-api/tests/test-wasi-import-object.c b/lib/runtime-c-api/tests/test-wasi-import-object.c new file mode 100644 index 000000000..bd1478dc1 --- /dev/null +++ b/lib/runtime-c-api/tests/test-wasi-import-object.c @@ -0,0 +1,250 @@ +#include +#include "../wasmer.h" +#include +#include +#include + +static bool host_print_called = false; + +// Host function that will be imported into the Web Assembly Instance +void host_print(const wasmer_instance_context_t *ctx, int32_t ptr, int32_t len) +{ + host_print_called = true; + const wasmer_memory_t *memory = wasmer_instance_context_memory(ctx, 0); + uint32_t mem_len = wasmer_memory_length(memory); + uint8_t *mem_bytes = wasmer_memory_data(memory); + printf("%.*s", len, mem_bytes + ptr); +} + +// Use the last_error API to retrieve error messages +void print_wasmer_error() +{ + int error_len = wasmer_last_error_length(); + printf("Error len: `%d`\n", error_len); + char *error_str = malloc(error_len); + wasmer_last_error_message(error_str, error_len); + printf("Error str: `%s`\n", error_str); +} + +// helper function to print byte array to stdout +void print_byte_array(wasmer_byte_array *arr) { + for (int i = 0; i < arr->bytes_len; ++i) { + putchar(arr->bytes[i]); + } +} + +int main() +{ + // Create a new func to hold the parameter and signature + // of our `host_print` host function + wasmer_value_tag params_sig[] = {WASM_I32, WASM_I32}; + wasmer_value_tag returns_sig[] = {}; + wasmer_import_func_t *func = wasmer_import_func_new((void (*)(void *)) host_print, params_sig, 2, returns_sig, 0); + + // Create module name for our imports + // represented in bytes for UTF-8 compatability + const char *module_name = "env"; + wasmer_byte_array module_name_bytes; + module_name_bytes.bytes = (const uint8_t *) module_name; + module_name_bytes.bytes_len = strlen(module_name); + + // Define a function import + const char *import_name = "host_print"; + wasmer_byte_array import_name_bytes; + import_name_bytes.bytes = (const uint8_t *) import_name; + import_name_bytes.bytes_len = strlen(import_name); + wasmer_import_t func_import; + func_import.module_name = module_name_bytes; + func_import.import_name = import_name_bytes; + func_import.tag = WASM_FUNCTION; + func_import.value.func = func; + + // Define a memory import + const char *import_memory_name = "memory"; + wasmer_byte_array import_memory_name_bytes; + import_memory_name_bytes.bytes = (const uint8_t *) import_memory_name; + import_memory_name_bytes.bytes_len = strlen(import_memory_name); + wasmer_import_t memory_import; + memory_import.module_name = module_name_bytes; + memory_import.import_name = import_memory_name_bytes; + memory_import.tag = WASM_MEMORY; + wasmer_memory_t *memory = NULL; + wasmer_limits_t descriptor; + descriptor.min = 256; + wasmer_limit_option_t max; + max.has_some = true; + max.some = 256; + descriptor.max = max; + wasmer_result_t memory_result = wasmer_memory_new(&memory, descriptor); + if (memory_result != WASMER_OK) + { + print_wasmer_error(); + } + memory_import.value.memory = memory; + + // Define a global import + const char *import_global_name = "__memory_base"; + wasmer_byte_array import_global_name_bytes; + import_global_name_bytes.bytes = (const uint8_t *) import_global_name; + import_global_name_bytes.bytes_len = strlen(import_global_name); + wasmer_import_t global_import; + global_import.module_name = module_name_bytes; + global_import.import_name = import_global_name_bytes; + global_import.tag = WASM_GLOBAL; + wasmer_value_t val; + val.tag = WASM_I32; + val.value.I32 = 1024; + wasmer_global_t *global = wasmer_global_new(val, false); + global_import.value.global = global; + + // Define a table import + const char *import_table_name = "table"; + wasmer_byte_array import_table_name_bytes; + import_table_name_bytes.bytes = (const uint8_t *) import_table_name; + import_table_name_bytes.bytes_len = strlen(import_table_name); + wasmer_import_t table_import; + table_import.module_name = module_name_bytes; + table_import.import_name = import_table_name_bytes; + table_import.tag = WASM_TABLE; + wasmer_table_t *table = NULL; + wasmer_limits_t table_descriptor; + table_descriptor.min = 256; + wasmer_limit_option_t table_max; + table_max.has_some = true; + table_max.some = 256; + table_descriptor.max = table_max; + wasmer_result_t table_result = wasmer_table_new(&table, table_descriptor); + if (table_result != WASMER_OK) + { + print_wasmer_error(); + } + table_import.value.table = table; + + + // Create arbitrary arguments for our program + + // Set up data for our WASI import object + // + // Environment variables and program arguments are processed by the WASI + // program. They will not have any effects unless the program includes + // logic to process them. + const char *wasi_prog_name = "wasi_test_program"; + const char *wasi_first_arg = "--help"; + wasmer_byte_array args[] = { + { .bytes = (const uint8_t *) wasi_prog_name, + .bytes_len = strlen(wasi_prog_name) }, + { .bytes = (const uint8_t *) wasi_first_arg, + .bytes_len = strlen(wasi_first_arg) } + }; + int wasi_argc = sizeof(args) / sizeof(args[0]); + + // Create arbitrary environment variables for our program; + const char *wasi_color_env = "COLOR=TRUE"; + const char *wasi_app_should_log = "APP_SHOULD_LOG=FALSE"; + wasmer_byte_array envs[] = { + { .bytes = (const uint8_t *) wasi_color_env, + .bytes_len = strlen(wasi_color_env) }, + { .bytes = (const uint8_t *) wasi_app_should_log, + .bytes_len = strlen(wasi_app_should_log) } + }; + int wasi_env_len = sizeof(args) / sizeof(args[0]); + + // Open the host's current directory under a different name. + // WARNING: this gives the WASI module limited access to your host's file system, + // use caution when granting these permissions to untrusted Wasm modules. + const char *wasi_map_dir_alias = "the_host_current_dir"; + const char *wasi_map_dir_host_path = "."; + wasmer_wasi_map_dir_entry_t mapped_dirs[] = { + { .alias = + { .bytes = (const uint8_t *) wasi_map_dir_alias, + .bytes_len = strlen(wasi_map_dir_alias) }, + .host_file_path = + { .bytes = (const uint8_t *) wasi_map_dir_host_path, + .bytes_len = strlen(wasi_map_dir_host_path) } } + }; + int mapped_dir_len = sizeof(mapped_dirs) / sizeof(mapped_dirs[0]); + + // Create the WASI import object + wasmer_import_object_t *import_object = + wasmer_wasi_generate_import_object(args, wasi_argc, + envs, wasi_env_len, + NULL, 0, + mapped_dirs, mapped_dir_len); + + // Create our imports + wasmer_import_t imports[] = {func_import, global_import, memory_import, table_import}; + int imports_len = sizeof(imports) / sizeof(imports[0]); + // Add our imports to the import object + wasmer_import_object_extend(import_object, imports, imports_len); + + // Read the wasm file bytes + FILE *file = fopen("assets/extended_wasi.wasm", "r"); + assert(file); + fseek(file, 0, SEEK_END); + long len = ftell(file); + uint8_t *bytes = malloc(len); + fseek(file, 0, SEEK_SET); + fread(bytes, 1, len, file); + fclose(file); + + wasmer_module_t *module = NULL; + // Compile the WebAssembly module + wasmer_result_t compile_result = wasmer_compile(&module, bytes, len); + printf("Compile result: %d\n", compile_result); + if (compile_result != WASMER_OK) + { + print_wasmer_error(); + } + assert(compile_result == WASMER_OK); + + // Instantiatoe the module with our import_object + wasmer_instance_t *instance = NULL; + wasmer_result_t instantiate_result = wasmer_module_import_instantiate(&instance, module, import_object); + printf("Instantiate result: %d\n", instantiate_result); + if (instantiate_result != WASMER_OK) + { + print_wasmer_error(); + } + assert(instantiate_result == WASMER_OK); + + // Call the exported "hello_wasm" function of our instance + wasmer_value_t params[] = {}; + wasmer_value_t result_one; + wasmer_value_t results[] = {result_one}; + // _start runs before main for WASI programs + wasmer_result_t call_result = wasmer_instance_call(instance, "_start", params, 0, results, 1); + printf("Call result: %d\n", call_result); + assert(call_result == WASMER_OK); + assert(host_print_called); + + wasmer_import_object_iter_t *func_iter = wasmer_import_object_iterate_functions(import_object); + + puts("Functions in import object:"); + while ( !wasmer_import_object_iter_at_end(func_iter) ) { + wasmer_import_t import; + wasmer_result_t result = wasmer_import_object_iter_next(func_iter, &import); + assert(result == WASMER_OK); + + print_byte_array(&import.module_name); + putchar(' '); + print_byte_array(&import.import_name); + putchar('\n'); + + assert(import.tag == WASM_FUNCTION); + assert(import.value.func); + wasmer_import_object_imports_destroy(&import, 1); + } + wasmer_import_object_iter_destroy(func_iter); + + // Use *_destroy methods to cleanup as specified in the header documentation + wasmer_import_func_destroy(func); + wasmer_global_destroy(global); + wasmer_memory_destroy(memory); + wasmer_table_destroy(table); + wasmer_instance_destroy(instance); + wasmer_import_object_destroy(import_object); + wasmer_module_destroy(module); + + return 0; +} + diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 297428ee2..40a265cff 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -1,3 +1,22 @@ + +#if !defined(WASMER_H_MACROS) +#define WASMER_H_MACROS + +#if defined(MSVC) +#if defined(_M_AMD64) +#define ARCH_X86_64 +#endif +#endif + +#if defined(GCC) || defined(__GNUC__) || defined(__clang__) +#if defined(__x86_64__) +#define ARCH_X86_64 +#endif +#endif + +#endif // WASMER_H_MACROS + + #ifndef WASMER_H #define WASMER_H @@ -6,11 +25,14 @@ #include #include +/** + * List of export/import kinds. + */ enum wasmer_import_export_kind { - WASM_FUNCTION, - WASM_GLOBAL, - WASM_MEMORY, - WASM_TABLE, + WASM_FUNCTION = 0, + WASM_GLOBAL = 1, + WASM_MEMORY = 2, + WASM_TABLE = 3, }; typedef uint32_t wasmer_import_export_kind; @@ -31,6 +53,9 @@ typedef struct { } wasmer_module_t; +/** + * Opaque pointer to `NamedExportDescriptor`. + */ typedef struct { } wasmer_export_descriptor_t; @@ -40,10 +65,16 @@ typedef struct { uint32_t bytes_len; } wasmer_byte_array; +/** + * Opaque pointer to `NamedExportDescriptors`. + */ typedef struct { } wasmer_export_descriptors_t; +/** + * Opaque pointer to `wasmer_export_t`. + */ typedef struct { } wasmer_export_func_t; @@ -60,6 +91,9 @@ typedef struct { wasmer_value value; } wasmer_value_t; +/** + * Opaque pointer to `NamedExport`. + */ typedef struct { } wasmer_export_t; @@ -68,6 +102,9 @@ typedef struct { } wasmer_memory_t; +/** + * Opaque pointer to `NamedExports`. + */ typedef struct { } wasmer_exports_t; @@ -101,6 +138,9 @@ typedef struct { } wasmer_table_t; +/** + * Union of import/export value. + */ typedef union { const wasmer_import_func_t *func; const wasmer_table_t *table; @@ -117,6 +157,10 @@ typedef struct { typedef struct { +} wasmer_import_object_iter_t; + +typedef struct { + } wasmer_instance_t; typedef struct { @@ -137,21 +181,44 @@ typedef struct { } wasmer_serialized_module_t; +#if (!defined(_WIN32) && defined(ARCH_X86_64)) typedef struct { } wasmer_trampoline_buffer_builder_t; +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) typedef struct { } wasmer_trampoline_callable_t; +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) typedef struct { } wasmer_trampoline_buffer_t; +#endif + +/** + * Opens a directory that's visible to the WASI module as `alias` but + * is backed by the host file at `host_file_path` + */ +typedef struct { + /** + * What the WASI module will see in its virtual root + */ + wasmer_byte_array alias; + /** + * The backing file that the WASI module will interact with via the alias + */ + wasmer_byte_array host_file_path; +} wasmer_wasi_map_dir_entry_t; /** * Creates a new Module from the given wasm bytes. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -171,6 +238,7 @@ wasmer_byte_array wasmer_export_descriptor_name(wasmer_export_descriptor_t *expo /** * Gets export descriptors for the given module + * * The caller owns the object and should call `wasmer_export_descriptors_destroy` to free it. */ void wasmer_export_descriptors(const wasmer_module_t *module, @@ -195,19 +263,23 @@ int wasmer_export_descriptors_len(wasmer_export_descriptors_t *exports); /** * Calls a `func` with the provided parameters. * Results are set using the provided `results` pointer. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ wasmer_result_t wasmer_export_func_call(const wasmer_export_func_t *func, const wasmer_value_t *params, - int params_len, + unsigned int params_len, wasmer_value_t *results, - int results_len); + unsigned int results_len); /** * Sets the params buffer to the parameter types of the given wasmer_export_func_t + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -217,7 +289,9 @@ wasmer_result_t wasmer_export_func_params(const wasmer_export_func_t *func, /** * Sets the result parameter to the arity of the params of the wasmer_export_func_t + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -225,7 +299,9 @@ wasmer_result_t wasmer_export_func_params_arity(const wasmer_export_func_t *func /** * Sets the returns buffer to the parameter types of the given wasmer_export_func_t + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -235,7 +311,9 @@ wasmer_result_t wasmer_export_func_returns(const wasmer_export_func_t *func, /** * Sets the result parameter to the arity of the returns of the wasmer_export_func_t + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -259,7 +337,9 @@ const wasmer_export_func_t *wasmer_export_to_func(const wasmer_export_t *export_ /** * Gets a memory pointer from an export pointer. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -323,6 +403,7 @@ wasmer_byte_array wasmer_import_descriptor_name(wasmer_import_descriptor_t *impo /** * Gets import descriptors for the given module + * * The caller owns the object and should call `wasmer_import_descriptors_destroy` to free it. */ void wasmer_import_descriptors(const wasmer_module_t *module, @@ -351,6 +432,7 @@ void wasmer_import_func_destroy(wasmer_import_func_t *func); /** * Creates new func + * * The caller owns the object and should call `wasmer_import_func_destroy` to free it. */ wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data), @@ -361,7 +443,9 @@ wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data), /** * Sets the params buffer to the parameter types of the given wasmer_import_func_t + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -371,7 +455,9 @@ wasmer_result_t wasmer_import_func_params(const wasmer_import_func_t *func, /** * Sets the result parameter to the arity of the params of the wasmer_import_func_t + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -379,7 +465,9 @@ wasmer_result_t wasmer_import_func_params_arity(const wasmer_import_func_t *func /** * Sets the returns buffer to the parameter types of the given wasmer_import_func_t + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -389,7 +477,9 @@ wasmer_result_t wasmer_import_func_returns(const wasmer_import_func_t *func, /** * Sets the result parameter to the arity of the returns of the wasmer_import_func_t + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -405,9 +495,60 @@ void wasmer_import_object_destroy(wasmer_import_object_t *import_object); * Extends an existing import object with new imports */ wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_object, - wasmer_import_t *imports, + const wasmer_import_t *imports, unsigned int imports_len); +/** + * Gets an entry from an ImportObject at the name and namespace. + * Stores `name`, `namespace`, and `import_export_value` in `import`. + * Thus these must remain valid for the lifetime of `import`. + * + * The caller owns all data involved. + * `import_export_value` will be written to based on `tag`. + */ +wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *import_object, + wasmer_byte_array namespace_, + wasmer_byte_array name, + wasmer_import_t *import, + wasmer_import_export_value *import_export_value, + uint32_t tag); + +/** + * Frees the memory allocated in `wasmer_import_object_iter_next` + * + * This function does not free the memory in `wasmer_import_object_t`; + * it only frees memory allocated while querying a `wasmer_import_object_t`. + */ +void wasmer_import_object_imports_destroy(wasmer_import_t *imports, uint32_t imports_len); + +/** + * Returns true if further calls to `wasmer_import_object_iter_next` will + * not return any new data + */ +bool wasmer_import_object_iter_at_end(wasmer_import_object_iter_t *import_object_iter); + +/** + * Frees the memory allocated by `wasmer_import_object_iterate_functions` + */ +void wasmer_import_object_iter_destroy(wasmer_import_object_iter_t *import_object_iter); + +/** + * Writes the next value to `import`. `WASMER_ERROR` is returned if there + * was an error or there's nothing left to return. + * + * To free the memory allocated here, pass the import to `wasmer_import_object_imports_destroy`. + * To check if the iterator is done, use `wasmer_import_object_iter_at_end`. + */ +wasmer_result_t wasmer_import_object_iter_next(wasmer_import_object_iter_t *import_object_iter, + wasmer_import_t *import); + +/** + * Create an iterator over the functions in the import object. + * Get the next import with `wasmer_import_object_iter_next` + * Free the iterator with `wasmer_import_object_iter_destroy` + */ +wasmer_import_object_iter_t *wasmer_import_object_iterate_functions(const wasmer_import_object_t *import_object); + /** * Creates a new empty import object. * See also `wasmer_import_object_append` @@ -417,7 +558,9 @@ wasmer_import_object_t *wasmer_import_object_new(void); /** * Calls an instances exported function by `name` with the provided parameters. * Results are set using the provided `results` pointer. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -458,13 +601,16 @@ void wasmer_instance_destroy(wasmer_instance_t *instance); /** * Gets Exports for the given instance + * * The caller owns the object and should call `wasmer_exports_destroy` to free it. */ void wasmer_instance_exports(wasmer_instance_t *instance, wasmer_exports_t **exports); /** * Creates a new Instance from the given wasm bytes and imports. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -478,7 +624,9 @@ wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance, * Gets the length in bytes of the last error. * This can be used to dynamically allocate a buffer with the correct number of * bytes needed to store a message. + * * # Example + * * ```c * int error_len = wasmer_last_error_length(); * char *error_str = malloc(error_len); @@ -489,9 +637,12 @@ int wasmer_last_error_length(void); /** * Stores the last error message into the provided buffer up to the given `length`. * The `length` parameter must be large enough to store the last error message. + * * Returns the length of the string in bytes. * Returns `-1` if an error occurs. + * * # Example + * * ```c * int error_len = wasmer_last_error_length(); * char *error_str = malloc(error_len); @@ -518,7 +669,9 @@ void wasmer_memory_destroy(wasmer_memory_t *memory); /** * Grows a Memory by the given number of pages. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -532,8 +685,11 @@ uint32_t wasmer_memory_length(const wasmer_memory_t *memory); /** * Creates a new Memory for the given descriptor and initializes the given * pointer to pointer to a pointer to the new memory. + * * The caller owns the object and should call `wasmer_memory_destroy` to free it. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -541,7 +697,9 @@ wasmer_result_t wasmer_memory_new(wasmer_memory_t **memory, wasmer_limits_t limi /** * Deserialize the given serialized module. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -555,8 +713,9 @@ void wasmer_module_destroy(wasmer_module_t *module); /** * Given: - * A prepared `wasmer` import-object - * A compiled wasmer module + * * A prepared `wasmer` import-object + * * A compiled wasmer module + * * Instantiates a wasmer instance */ wasmer_result_t wasmer_module_import_instantiate(wasmer_instance_t **instance, @@ -565,7 +724,9 @@ wasmer_result_t wasmer_module_import_instantiate(wasmer_instance_t **instance, /** * Creates a new Instance from the given module and imports. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -576,8 +737,11 @@ wasmer_result_t wasmer_module_instantiate(const wasmer_module_t *module, /** * Serialize the given Module. + * * The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -596,8 +760,11 @@ void wasmer_serialized_module_destroy(wasmer_serialized_module_t *serialized_mod /** * Transform a sequence of bytes into a serialized module. + * * The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -612,7 +779,9 @@ void wasmer_table_destroy(wasmer_table_t *table); /** * Grows a Table by the given number of elements. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ @@ -626,13 +795,17 @@ uint32_t wasmer_table_length(wasmer_table_t *table); /** * Creates a new Table for the given descriptor and initializes the given * pointer to pointer to a pointer to the new Table. + * * The caller owns the object and should call `wasmer_table_destroy` to free it. + * * Returns `wasmer_result_t::WASMER_OK` upon success. + * * Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` * and `wasmer_last_error_message` to get an error message. */ wasmer_result_t wasmer_table_new(wasmer_table_t **table, wasmer_limits_t limits); +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /** * Adds a callinfo trampoline to the builder. */ @@ -640,43 +813,81 @@ uintptr_t wasmer_trampoline_buffer_builder_add_callinfo_trampoline(wasmer_trampo const wasmer_trampoline_callable_t *func, const void *ctx, uint32_t num_params); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /** * Adds a context trampoline to the builder. */ uintptr_t wasmer_trampoline_buffer_builder_add_context_trampoline(wasmer_trampoline_buffer_builder_t *builder, const wasmer_trampoline_callable_t *func, const void *ctx); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /** * Finalizes the trampoline builder into an executable buffer. */ wasmer_trampoline_buffer_t *wasmer_trampoline_buffer_builder_build(wasmer_trampoline_buffer_builder_t *builder); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /** * Creates a new trampoline builder. */ wasmer_trampoline_buffer_builder_t *wasmer_trampoline_buffer_builder_new(void); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /** * Destroys the trampoline buffer if not null. */ void wasmer_trampoline_buffer_destroy(wasmer_trampoline_buffer_t *buffer); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /** * Returns the callable pointer for the trampoline with index `idx`. */ const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(const wasmer_trampoline_buffer_t *buffer, uintptr_t idx); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /** * Returns the context added by `add_context_trampoline`, from within the callee function. */ void *wasmer_trampoline_get_context(void); +#endif /** * Returns true for valid wasm bytes and false for invalid bytes */ bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); +/** + * Convenience function that creates a WASI import object with no arguments, + * environment variables, preopened files, or mapped directories. + * + * This function is the same as calling [`wasmer_wasi_generate_import_object`] with all + * empty values. + */ +wasmer_import_object_t *wasmer_wasi_generate_default_import_object(void); + +/** + * Creates a WASI import object. + * + * This function treats null pointers as empty collections. + * For example, passing null for a string in `args`, will lead to a zero + * length argument in that position. + */ +wasmer_import_object_t *wasmer_wasi_generate_import_object(const wasmer_byte_array *args, + unsigned int args_len, + const wasmer_byte_array *envs, + unsigned int envs_len, + const wasmer_byte_array *preopened_files, + unsigned int preopened_files_len, + const wasmer_wasi_map_dir_entry_t *mapped_dirs, + unsigned int mapped_dirs_len); + #endif /* WASMER_H */ diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index cf7a1c7b3..b437edd4b 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -1,3 +1,22 @@ + +#if !defined(WASMER_H_MACROS) +#define WASMER_H_MACROS + +#if defined(MSVC) +#if defined(_M_AMD64) +#define ARCH_X86_64 +#endif +#endif + +#if defined(GCC) || defined(__GNUC__) || defined(__clang__) +#if defined(__x86_64__) +#define ARCH_X86_64 +#endif +#endif + +#endif // WASMER_H_MACROS + + #ifndef WASMER_H #define WASMER_H @@ -6,11 +25,12 @@ #include #include +/// List of export/import kinds. enum class wasmer_import_export_kind : uint32_t { - WASM_FUNCTION, - WASM_GLOBAL, - WASM_MEMORY, - WASM_TABLE, + WASM_FUNCTION = 0, + WASM_GLOBAL = 1, + WASM_MEMORY = 2, + WASM_TABLE = 3, }; enum class wasmer_result_t { @@ -29,6 +49,7 @@ struct wasmer_module_t { }; +/// Opaque pointer to `NamedExportDescriptor`. struct wasmer_export_descriptor_t { }; @@ -38,10 +59,12 @@ struct wasmer_byte_array { uint32_t bytes_len; }; +/// Opaque pointer to `NamedExportDescriptors`. struct wasmer_export_descriptors_t { }; +/// Opaque pointer to `wasmer_export_t`. struct wasmer_export_func_t { }; @@ -58,6 +81,7 @@ struct wasmer_value_t { wasmer_value value; }; +/// Opaque pointer to `NamedExport`. struct wasmer_export_t { }; @@ -66,6 +90,7 @@ struct wasmer_memory_t { }; +/// Opaque pointer to `NamedExports`. struct wasmer_exports_t { }; @@ -99,6 +124,7 @@ struct wasmer_table_t { }; +/// Union of import/export value. union wasmer_import_export_value { const wasmer_import_func_t *func; const wasmer_table_t *table; @@ -113,6 +139,10 @@ struct wasmer_import_t { wasmer_import_export_value value; }; +struct wasmer_import_object_iter_t { + +}; + struct wasmer_instance_t { }; @@ -135,22 +165,39 @@ struct wasmer_serialized_module_t { }; +#if (!defined(_WIN32) && defined(ARCH_X86_64)) struct wasmer_trampoline_buffer_builder_t { }; +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) struct wasmer_trampoline_callable_t { }; +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) struct wasmer_trampoline_buffer_t { }; +#endif + +/// Opens a directory that's visible to the WASI module as `alias` but +/// is backed by the host file at `host_file_path` +struct wasmer_wasi_map_dir_entry_t { + /// What the WASI module will see in its virtual root + wasmer_byte_array alias; + /// The backing file that the WASI module will interact with via the alias + wasmer_byte_array host_file_path; +}; extern "C" { /// Creates a new Module from the given wasm bytes. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_compile(wasmer_module_t **module, @@ -164,6 +211,7 @@ wasmer_import_export_kind wasmer_export_descriptor_kind(wasmer_export_descriptor wasmer_byte_array wasmer_export_descriptor_name(wasmer_export_descriptor_t *export_descriptor); /// Gets export descriptors for the given module +/// /// The caller owns the object and should call `wasmer_export_descriptors_destroy` to free it. void wasmer_export_descriptors(const wasmer_module_t *module, wasmer_export_descriptors_t **export_descriptors); @@ -180,17 +228,21 @@ int wasmer_export_descriptors_len(wasmer_export_descriptors_t *exports); /// Calls a `func` with the provided parameters. /// Results are set using the provided `results` pointer. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_export_func_call(const wasmer_export_func_t *func, const wasmer_value_t *params, - int params_len, + unsigned int params_len, wasmer_value_t *results, - int results_len); + unsigned int results_len); /// Sets the params buffer to the parameter types of the given wasmer_export_func_t +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_export_func_params(const wasmer_export_func_t *func, @@ -198,13 +250,17 @@ wasmer_result_t wasmer_export_func_params(const wasmer_export_func_t *func, uint32_t params_len); /// Sets the result parameter to the arity of the params of the wasmer_export_func_t +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_export_func_params_arity(const wasmer_export_func_t *func, uint32_t *result); /// Sets the returns buffer to the parameter types of the given wasmer_export_func_t +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_export_func_returns(const wasmer_export_func_t *func, @@ -212,7 +268,9 @@ wasmer_result_t wasmer_export_func_returns(const wasmer_export_func_t *func, uint32_t returns_len); /// Sets the result parameter to the arity of the returns of the wasmer_export_func_t +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_export_func_returns_arity(const wasmer_export_func_t *func, @@ -228,7 +286,9 @@ wasmer_byte_array wasmer_export_name(wasmer_export_t *export_); const wasmer_export_func_t *wasmer_export_to_func(const wasmer_export_t *export_); /// Gets a memory pointer from an export pointer. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_export_to_memory(const wasmer_export_t *export_, wasmer_memory_t **memory); @@ -268,6 +328,7 @@ wasmer_byte_array wasmer_import_descriptor_module_name(wasmer_import_descriptor_ wasmer_byte_array wasmer_import_descriptor_name(wasmer_import_descriptor_t *import_descriptor); /// Gets import descriptors for the given module +/// /// The caller owns the object and should call `wasmer_import_descriptors_destroy` to free it. void wasmer_import_descriptors(const wasmer_module_t *module, wasmer_import_descriptors_t **import_descriptors); @@ -286,6 +347,7 @@ unsigned int wasmer_import_descriptors_len(wasmer_import_descriptors_t *exports) void wasmer_import_func_destroy(wasmer_import_func_t *func); /// Creates new func +/// /// The caller owns the object and should call `wasmer_import_func_destroy` to free it. wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data), const wasmer_value_tag *params, @@ -294,7 +356,9 @@ wasmer_import_func_t *wasmer_import_func_new(void (*func)(void *data), unsigned int returns_len); /// Sets the params buffer to the parameter types of the given wasmer_import_func_t +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_import_func_params(const wasmer_import_func_t *func, @@ -302,13 +366,17 @@ wasmer_result_t wasmer_import_func_params(const wasmer_import_func_t *func, unsigned int params_len); /// Sets the result parameter to the arity of the params of the wasmer_import_func_t +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_import_func_params_arity(const wasmer_import_func_t *func, uint32_t *result); /// Sets the returns buffer to the parameter types of the given wasmer_import_func_t +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_import_func_returns(const wasmer_import_func_t *func, @@ -316,7 +384,9 @@ wasmer_result_t wasmer_import_func_returns(const wasmer_import_func_t *func, unsigned int returns_len); /// Sets the result parameter to the arity of the returns of the wasmer_import_func_t +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_import_func_returns_arity(const wasmer_import_func_t *func, @@ -327,16 +397,57 @@ void wasmer_import_object_destroy(wasmer_import_object_t *import_object); /// Extends an existing import object with new imports wasmer_result_t wasmer_import_object_extend(wasmer_import_object_t *import_object, - wasmer_import_t *imports, + const wasmer_import_t *imports, unsigned int imports_len); +/// Gets an entry from an ImportObject at the name and namespace. +/// Stores `name`, `namespace`, and `import_export_value` in `import`. +/// Thus these must remain valid for the lifetime of `import`. +/// +/// The caller owns all data involved. +/// `import_export_value` will be written to based on `tag`. +wasmer_result_t wasmer_import_object_get_import(const wasmer_import_object_t *import_object, + wasmer_byte_array namespace_, + wasmer_byte_array name, + wasmer_import_t *import, + wasmer_import_export_value *import_export_value, + uint32_t tag); + +/// Frees the memory allocated in `wasmer_import_object_iter_next` +/// +/// This function does not free the memory in `wasmer_import_object_t`; +/// it only frees memory allocated while querying a `wasmer_import_object_t`. +void wasmer_import_object_imports_destroy(wasmer_import_t *imports, uint32_t imports_len); + +/// Returns true if further calls to `wasmer_import_object_iter_next` will +/// not return any new data +bool wasmer_import_object_iter_at_end(wasmer_import_object_iter_t *import_object_iter); + +/// Frees the memory allocated by `wasmer_import_object_iterate_functions` +void wasmer_import_object_iter_destroy(wasmer_import_object_iter_t *import_object_iter); + +/// Writes the next value to `import`. `WASMER_ERROR` is returned if there +/// was an error or there's nothing left to return. +/// +/// To free the memory allocated here, pass the import to `wasmer_import_object_imports_destroy`. +/// To check if the iterator is done, use `wasmer_import_object_iter_at_end`. +wasmer_result_t wasmer_import_object_iter_next(wasmer_import_object_iter_t *import_object_iter, + wasmer_import_t *import); + +/// Create an iterator over the functions in the import object. +/// Get the next import with `wasmer_import_object_iter_next` +/// Free the iterator with `wasmer_import_object_iter_destroy` +wasmer_import_object_iter_t *wasmer_import_object_iterate_functions(const wasmer_import_object_t *import_object); + /// Creates a new empty import object. /// See also `wasmer_import_object_append` wasmer_import_object_t *wasmer_import_object_new(); /// Calls an instances exported function by `name` with the provided parameters. /// Results are set using the provided `results` pointer. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_instance_call(wasmer_instance_t *instance, @@ -365,11 +476,14 @@ const wasmer_memory_t *wasmer_instance_context_memory(const wasmer_instance_cont void wasmer_instance_destroy(wasmer_instance_t *instance); /// Gets Exports for the given instance +/// /// The caller owns the object and should call `wasmer_exports_destroy` to free it. void wasmer_instance_exports(wasmer_instance_t *instance, wasmer_exports_t **exports); /// Creates a new Instance from the given wasm bytes and imports. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance, @@ -381,7 +495,9 @@ wasmer_result_t wasmer_instantiate(wasmer_instance_t **instance, /// Gets the length in bytes of the last error. /// This can be used to dynamically allocate a buffer with the correct number of /// bytes needed to store a message. +/// /// # Example +/// /// ```c /// int error_len = wasmer_last_error_length(); /// char *error_str = malloc(error_len); @@ -390,9 +506,12 @@ int wasmer_last_error_length(); /// Stores the last error message into the provided buffer up to the given `length`. /// The `length` parameter must be large enough to store the last error message. +/// /// Returns the length of the string in bytes. /// Returns `-1` if an error occurs. +/// /// # Example +/// /// ```c /// int error_len = wasmer_last_error_length(); /// char *error_str = malloc(error_len); @@ -411,7 +530,9 @@ uint32_t wasmer_memory_data_length(wasmer_memory_t *mem); void wasmer_memory_destroy(wasmer_memory_t *memory); /// Grows a Memory by the given number of pages. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_memory_grow(wasmer_memory_t *memory, uint32_t delta); @@ -421,14 +542,19 @@ uint32_t wasmer_memory_length(const wasmer_memory_t *memory); /// Creates a new Memory for the given descriptor and initializes the given /// pointer to pointer to a pointer to the new memory. +/// /// The caller owns the object and should call `wasmer_memory_destroy` to free it. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_memory_new(wasmer_memory_t **memory, wasmer_limits_t limits); /// Deserialize the given serialized module. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_module_deserialize(wasmer_module_t **module, @@ -438,15 +564,18 @@ wasmer_result_t wasmer_module_deserialize(wasmer_module_t **module, void wasmer_module_destroy(wasmer_module_t *module); /// Given: -/// A prepared `wasmer` import-object -/// A compiled wasmer module +/// * A prepared `wasmer` import-object +/// * A compiled wasmer module +/// /// Instantiates a wasmer instance wasmer_result_t wasmer_module_import_instantiate(wasmer_instance_t **instance, const wasmer_module_t *module, const wasmer_import_object_t *import_object); /// Creates a new Instance from the given module and imports. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_module_instantiate(const wasmer_module_t *module, @@ -455,8 +584,11 @@ wasmer_result_t wasmer_module_instantiate(const wasmer_module_t *module, int imports_len); /// Serialize the given Module. +/// /// The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_module_serialize(wasmer_serialized_module_t **serialized_module, @@ -469,8 +601,11 @@ wasmer_byte_array wasmer_serialized_module_bytes(const wasmer_serialized_module_ void wasmer_serialized_module_destroy(wasmer_serialized_module_t *serialized_module); /// Transform a sequence of bytes into a serialized module. +/// /// The caller owns the object and should call `wasmer_serialized_module_destroy` to free it. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_serialized_module_from_bytes(wasmer_serialized_module_t **serialized_module, @@ -481,7 +616,9 @@ wasmer_result_t wasmer_serialized_module_from_bytes(wasmer_serialized_module_t * void wasmer_table_destroy(wasmer_table_t *table); /// Grows a Table by the given number of elements. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_table_grow(wasmer_table_t *table, uint32_t delta); @@ -491,42 +628,80 @@ uint32_t wasmer_table_length(wasmer_table_t *table); /// Creates a new Table for the given descriptor and initializes the given /// pointer to pointer to a pointer to the new Table. +/// /// The caller owns the object and should call `wasmer_table_destroy` to free it. +/// /// Returns `wasmer_result_t::WASMER_OK` upon success. +/// /// Returns `wasmer_result_t::WASMER_ERROR` upon failure. Use `wasmer_last_error_length` /// and `wasmer_last_error_message` to get an error message. wasmer_result_t wasmer_table_new(wasmer_table_t **table, wasmer_limits_t limits); +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /// Adds a callinfo trampoline to the builder. uintptr_t wasmer_trampoline_buffer_builder_add_callinfo_trampoline(wasmer_trampoline_buffer_builder_t *builder, const wasmer_trampoline_callable_t *func, const void *ctx, uint32_t num_params); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /// Adds a context trampoline to the builder. uintptr_t wasmer_trampoline_buffer_builder_add_context_trampoline(wasmer_trampoline_buffer_builder_t *builder, const wasmer_trampoline_callable_t *func, const void *ctx); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /// Finalizes the trampoline builder into an executable buffer. wasmer_trampoline_buffer_t *wasmer_trampoline_buffer_builder_build(wasmer_trampoline_buffer_builder_t *builder); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /// Creates a new trampoline builder. wasmer_trampoline_buffer_builder_t *wasmer_trampoline_buffer_builder_new(); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /// Destroys the trampoline buffer if not null. void wasmer_trampoline_buffer_destroy(wasmer_trampoline_buffer_t *buffer); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /// Returns the callable pointer for the trampoline with index `idx`. const wasmer_trampoline_callable_t *wasmer_trampoline_buffer_get_trampoline(const wasmer_trampoline_buffer_t *buffer, uintptr_t idx); +#endif +#if (!defined(_WIN32) && defined(ARCH_X86_64)) /// Returns the context added by `add_context_trampoline`, from within the callee function. void *wasmer_trampoline_get_context(); +#endif /// Returns true for valid wasm bytes and false for invalid bytes bool wasmer_validate(const uint8_t *wasm_bytes, uint32_t wasm_bytes_len); +/// Convenience function that creates a WASI import object with no arguments, +/// environment variables, preopened files, or mapped directories. +/// +/// This function is the same as calling [`wasmer_wasi_generate_import_object`] with all +/// empty values. +wasmer_import_object_t *wasmer_wasi_generate_default_import_object(); + +/// Creates a WASI import object. +/// +/// This function treats null pointers as empty collections. +/// For example, passing null for a string in `args`, will lead to a zero +/// length argument in that position. +wasmer_import_object_t *wasmer_wasi_generate_import_object(const wasmer_byte_array *args, + unsigned int args_len, + const wasmer_byte_array *envs, + unsigned int envs_len, + const wasmer_byte_array *preopened_files, + unsigned int preopened_files_len, + const wasmer_wasi_map_dir_entry_t *mapped_dirs, + unsigned int mapped_dirs_len); + } // extern "C" #endif // WASMER_H diff --git a/lib/runtime-core-tests/Cargo.toml b/lib/runtime-core-tests/Cargo.toml new file mode 100644 index 000000000..57e9e19f9 --- /dev/null +++ b/lib/runtime-core-tests/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "wasmer-runtime-core-tests" +version = "0.11.0" +description = "Tests for the Wasmer runtime core crate" +license = "MIT" +authors = ["The Wasmer Engineering Team "] +edition = "2018" +publish = false + +[dependencies] +wabt = "0.9.1" +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } +wasmer-clif-backend = { path = "../clif-backend", version = "0.11.0", optional = true } +wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.11.0", optional = true } +wasmer-llvm-backend = { path = "../llvm-backend", version = "0.11.0", features = ["test"], optional = true } + +[features] +default = ["backend-cranelift"] +backend-cranelift = ["wasmer-clif-backend"] +backend-singlepass = ["wasmer-singlepass-backend"] +backend-llvm = ["wasmer-llvm-backend"] diff --git a/lib/runtime-core-tests/src/lib.rs b/lib/runtime-core-tests/src/lib.rs new file mode 100644 index 000000000..96f7e6265 --- /dev/null +++ b/lib/runtime-core-tests/src/lib.rs @@ -0,0 +1,21 @@ +pub use wabt::wat2wasm; +use wasmer_runtime_core::backend::Compiler; + +#[cfg(feature = "backend-cranelift")] +pub fn get_compiler() -> impl Compiler { + use wasmer_clif_backend::CraneliftCompiler; + + CraneliftCompiler::new() +} + +#[cfg(feature = "backend-singlepass")] +pub fn get_compiler() -> impl Compiler { + use wasmer_singlepass_backend::SinglePassCompiler; + SinglePassCompiler::new() +} + +#[cfg(feature = "backend-llvm")] +pub fn get_compiler() -> impl Compiler { + use wasmer_llvm_backend::LLVMCompiler; + LLVMCompiler::new() +} diff --git a/lib/runtime-core-tests/tests/imports.rs b/lib/runtime-core-tests/tests/imports.rs new file mode 100644 index 000000000..6b7223c67 --- /dev/null +++ b/lib/runtime-core-tests/tests/imports.rs @@ -0,0 +1,294 @@ +use wasmer_runtime_core::{ + compile_with, error::RuntimeError, imports, memory::Memory, typed_func::Func, + types::MemoryDescriptor, units::Pages, vm, Instance, +}; +use wasmer_runtime_core_tests::{get_compiler, wat2wasm}; + +macro_rules! call_and_assert { + ($instance:ident, $function:ident, $expected_value:expr) => { + let $function: Func = $instance.func(stringify!($function)).unwrap(); + + let result = $function.call(1); + + match (result, $expected_value) { + (Ok(value), expected_value) => assert_eq!( + Ok(value), + expected_value, + concat!("Expected right when calling `", stringify!($function), "`.") + ), + ( + Err(RuntimeError::Error { data }), + Err(RuntimeError::Error { + data: expected_data, + }), + ) => { + if let (Some(data), Some(expected_data)) = ( + data.downcast_ref::<&str>(), + expected_data.downcast_ref::<&str>(), + ) { + assert_eq!( + data, expected_data, + concat!("Expected right when calling `", stringify!($function), "`.") + ) + } else if let (Some(data), Some(expected_data)) = ( + data.downcast_ref::(), + expected_data.downcast_ref::(), + ) { + assert_eq!( + data, expected_data, + concat!("Expected right when calling `", stringify!($function), "`.") + ) + } else { + assert!(false, "Unexpected error, cannot compare it.") + } + } + (result, expected_value) => assert!( + false, + format!( + "Unexpected assertion for `{}`: left = `{:?}`, right = `{:?}`.", + stringify!($function), + result, + expected_value + ) + ), + } + }; +} + +/// The shift that is set in the instance memory. The value is part of +/// the result returned by the imported functions if the memory is +/// read properly. +const SHIFT: i32 = 10; + +/// The shift that is captured in the environment of a closure. The +/// value is part of the result returned by the imported function if +/// the closure captures its environment properly. +#[allow(non_upper_case_globals)] +const shift: i32 = 100; + +fn imported_functions_forms(test: &dyn Fn(&Instance)) { + const MODULE: &str = r#" +(module + (type $type (func (param i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (import "env" "callback_fn" (func $callback_fn (type $type))) + (import "env" "callback_closure" (func $callback_closure (type $type))) + (import "env" "callback_closure_with_env" (func $callback_closure_with_env (type $type))) + (import "env" "callback_fn_with_vmctx" (func $callback_fn_with_vmctx (type $type))) + (import "env" "callback_closure_with_vmctx" (func $callback_closure_with_vmctx (type $type))) + (import "env" "callback_closure_with_vmctx_and_env" (func $callback_closure_with_vmctx_and_env (type $type))) + (import "env" "callback_fn_trap" (func $callback_fn_trap (type $type))) + (import "env" "callback_closure_trap" (func $callback_closure_trap (type $type))) + (import "env" "callback_fn_trap_with_vmctx" (func $callback_fn_trap_with_vmctx (type $type))) + (import "env" "callback_closure_trap_with_vmctx" (func $callback_closure_trap_with_vmctx (type $type))) + (import "env" "callback_closure_trap_with_vmctx_and_env" (func $callback_closure_trap_with_vmctx_and_env (type $type))) + + (func (export "function_fn") (type $type) + get_local 0 + call $callback_fn) + + (func (export "function_closure") (type $type) + get_local 0 + call $callback_closure) + + (func (export "function_closure_with_env") (type $type) + get_local 0 + call $callback_closure_with_env) + + (func (export "function_fn_with_vmctx") (type $type) + get_local 0 + call $callback_fn_with_vmctx) + + (func (export "function_closure_with_vmctx") (type $type) + get_local 0 + call $callback_closure_with_vmctx) + + (func (export "function_closure_with_vmctx_and_env") (type $type) + get_local 0 + call $callback_closure_with_vmctx_and_env) + + (func (export "function_fn_trap") (type $type) + get_local 0 + call $callback_fn_trap) + + (func (export "function_closure_trap") (type $type) + get_local 0 + call $callback_closure_trap) + + (func (export "function_fn_trap_with_vmctx") (type $type) + get_local 0 + call $callback_fn_trap_with_vmctx) + + (func (export "function_closure_trap_with_vmctx") (type $type) + get_local 0 + call $callback_closure_trap_with_vmctx) + + (func (export "function_closure_trap_with_vmctx_and_env") (type $type) + get_local 0 + call $callback_closure_trap_with_vmctx_and_env)) +"#; + + let wasm_binary = wat2wasm(MODULE.as_bytes()).expect("WAST not valid or malformed"); + let module = compile_with(&wasm_binary, &get_compiler()).unwrap(); + let memory_descriptor = MemoryDescriptor::new(Pages(1), Some(Pages(1)), false).unwrap(); + let memory = Memory::new(memory_descriptor).unwrap(); + + memory.view()[0].set(SHIFT); + + let import_object = imports! { + "env" => { + "memory" => memory.clone(), + + // Regular function. + "callback_fn" => Func::new(callback_fn), + + // Closure without a captured environment. + "callback_closure" => Func::new(|n: i32| -> Result { + Ok(n + 1) + }), + + // Closure with a captured environment (a single variable + an instance of `Memory`). + "callback_closure_with_env" => Func::new(move |n: i32| -> Result { + let shift_ = shift + memory.view::()[0].get(); + + Ok(shift_ + n + 1) + }), + + // Regular function with an explicit `vmctx`. + "callback_fn_with_vmctx" => Func::new(callback_fn_with_vmctx), + + // Closure without a captured environment but with an explicit `vmctx`. + "callback_closure_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { + let memory = vmctx.memory(0); + let shift_: i32 = memory.view()[0].get(); + + Ok(shift_ + n + 1) + }), + + // Closure with a captured environment (a single variable) and with an explicit `vmctx`. + "callback_closure_with_vmctx_and_env" => Func::new(move |vmctx: &mut vm::Ctx, n: i32| -> Result { + let memory = vmctx.memory(0); + let shift_ = shift + memory.view::()[0].get(); + + Ok(shift_ + n + 1) + }), + + // Trap a regular function. + "callback_fn_trap" => Func::new(callback_fn_trap), + + // Trap a closure without a captured environment. + "callback_closure_trap" => Func::new(|n: i32| -> Result { + Err(format!("bar {}", n + 1)) + }), + + // Trap a regular function with an explicit `vmctx`. + "callback_fn_trap_with_vmctx" => Func::new(callback_fn_trap_with_vmctx), + + // Trap a closure without a captured environment but with an explicit `vmctx`. + "callback_closure_trap_with_vmctx" => Func::new(|vmctx: &mut vm::Ctx, n: i32| -> Result { + let memory = vmctx.memory(0); + let shift_: i32 = memory.view()[0].get(); + + Err(format!("qux {}", shift_ + n + 1)) + }), + + // Trap a closure with a captured environment (a single variable) and with an explicit `vmctx`. + "callback_closure_trap_with_vmctx_and_env" => Func::new(move |vmctx: &mut vm::Ctx, n: i32| -> Result { + let memory = vmctx.memory(0); + let shift_ = shift + memory.view::()[0].get(); + + Err(format!("! {}", shift_ + n + 1)) + }), + }, + }; + let instance = module.instantiate(&import_object).unwrap(); + + test(&instance); +} + +fn callback_fn(n: i32) -> Result { + Ok(n + 1) +} + +fn callback_fn_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result { + let memory = vmctx.memory(0); + let shift_: i32 = memory.view()[0].get(); + + Ok(shift_ + n + 1) +} + +fn callback_fn_trap(n: i32) -> Result { + Err(format!("foo {}", n + 1)) +} + +fn callback_fn_trap_with_vmctx(vmctx: &mut vm::Ctx, n: i32) -> Result { + let memory = vmctx.memory(0); + let shift_: i32 = memory.view()[0].get(); + + Err(format!("baz {}", shift_ + n + 1)) +} + +macro_rules! test { + ($test_name:ident, $function:ident, $expected_value:expr) => { + #[test] + fn $test_name() { + imported_functions_forms(&|instance| { + call_and_assert!(instance, $function, $expected_value); + }); + } + }; +} + +test!(test_fn, function_fn, Ok(2)); +test!(test_closure, function_closure, Ok(2)); +test!( + test_closure_with_env, + function_closure_with_env, + Ok(2 + shift + SHIFT) +); +test!(test_fn_with_vmctx, function_fn_with_vmctx, Ok(2 + SHIFT)); +test!( + test_closure_with_vmctx, + function_closure_with_vmctx, + Ok(2 + SHIFT) +); +test!( + test_closure_with_vmctx_and_env, + function_closure_with_vmctx_and_env, + Ok(2 + shift + SHIFT) +); +test!( + test_fn_trap, + function_fn_trap, + Err(RuntimeError::Error { + data: Box::new(format!("foo {}", 2)) + }) +); +test!( + test_closure_trap, + function_closure_trap, + Err(RuntimeError::Error { + data: Box::new(format!("bar {}", 2)) + }) +); +test!( + test_fn_trap_with_vmctx, + function_fn_trap_with_vmctx, + Err(RuntimeError::Error { + data: Box::new(format!("baz {}", 2 + SHIFT)) + }) +); +test!( + test_closure_trap_with_vmctx, + function_closure_trap_with_vmctx, + Err(RuntimeError::Error { + data: Box::new(format!("qux {}", 2 + SHIFT)) + }) +); +test!( + test_closure_trap_with_vmctx_and_env, + function_closure_trap_with_vmctx_and_env, + Err(RuntimeError::Error { + data: Box::new(format!("! {}", 2 + shift + SHIFT)) + }) +); diff --git a/lib/runtime-core/Cargo.toml b/lib/runtime-core/Cargo.toml index 5f1370623..7c76d74ac 100644 --- a/lib/runtime-core/Cargo.toml +++ b/lib/runtime-core/Cargo.toml @@ -1,54 +1,52 @@ [package] name = "wasmer-runtime-core" -version = "0.6.0" +version = "0.11.0" description = "Wasmer runtime core library" license = "MIT" authors = ["The Wasmer Engineering Team "] repository = "https://github.com/wasmerio/wasmer" +keywords = ["wasm", "webassembly", "runtime"] +categories = ["wasm"] edition = "2018" [dependencies] -nix = "0.15.0" -page_size = "0.4.1" -wasmparser = "0.35.1" -parking_lot = "0.9.0" -lazy_static = "1.3.0" -errno = "0.2.4" +nix = "0.15" +page_size = "0.4" +wasmparser = "0.39.1" +parking_lot = "0.9" +lazy_static = "1.4" +errno = "0.2" libc = "0.2.60" -hex = "0.3.2" -smallvec = "0.6.10" +hex = "0.3" +smallvec = "0.6" bincode = "1.1" -colored = "1.8" [dependencies.indexmap] -version = "1.0.2" +version = "1.2" features = ["serde-1"] # Dependencies for caching. [dependencies.serde] -version = "1.0.99" +version = "1.0" # This feature is required for serde to support serializing/deserializing reference counted pointers (e.g. Rc and Arc). features = ["rc"] [dependencies.serde_derive] -version = "1.0.98" +version = "1.0" [dependencies.serde_bytes] -version = "0.11.2" +version = "0.11" [dependencies.serde-bench] version = "0.0.7" [dependencies.blake2b_simd] -version = "0.5.6" +version = "0.5" [dependencies.digest] -version = "0.8.1" +version = "0.8" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.7", features = ["memoryapi"] } - -[dev-dependencies] -field-offset = "0.1.1" +winapi = { version = "0.3", features = ["memoryapi"] } [build-dependencies] -blake2b_simd = "0.5.6" -rustc_version = "0.2.3" +blake2b_simd = "0.5" +rustc_version = "0.2" cc = "1.0" [features] @@ -58,3 +56,4 @@ trace = ["debug"] "backend-cranelift" = [] "backend-singlepass" = [] "backend-llvm" = [] +managed = [] diff --git a/lib/runtime-core/README.md b/lib/runtime-core/README.md index 0bbb74379..fafbcd85b 100644 --- a/lib/runtime-core/README.md +++ b/lib/runtime-core/README.md @@ -1,21 +1,21 @@

- Wasmer logo + Wasmer logo

- - Build Status + + Build Status - License + License Join the Wasmer Community - Number of downloads from crates.io + Number of downloads from crates.io Read our API documentation @@ -25,7 +25,8 @@ # Wasmer Runtime Core Wasmer is a standalone JIT WebAssembly runtime, aiming to be fully -compatible with Emscripten, Rust and Go. [Learn +compatible with WASI, Emscripten, Rust and Go. [Learn more](https://github.com/wasmerio/wasmer). -This crate represents the core of the runtime. +This crate represents the core of the runtime. Consider +[`wasmer-runtime`] for higher level APIs. diff --git a/lib/runtime-core/build.rs b/lib/runtime-core/build.rs index 35aafc835..3a549e45f 100644 --- a/lib/runtime-core/build.rs +++ b/lib/runtime-core/build.rs @@ -29,15 +29,20 @@ fn main() { println!("cargo:rustc-cfg=nightly"); } - if cfg!(all(target_os = "linux", target_arch = "x86_64")) { - cc::Build::new() - .file("image-loading-linux-x86-64.s") - .compile("image-loading"); - } else if cfg!(all(target_os = "macos", target_arch = "x86_64")) { - cc::Build::new() - .file("image-loading-macos-x86-64.s") - .compile("image-loading"); - } else { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap(); + let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); + match (target_os.as_str(), target_arch.as_str()) { + ("linux", "x86_64") => { + cc::Build::new() + .file("image-loading-linux-x86-64.s") + .compile("image-loading"); + } + ("macos", "x86_64") => { + cc::Build::new() + .file("image-loading-macos-x86-64.s") + .compile("image-loading"); + } + _ => {} } } diff --git a/lib/runtime-core/image-loading-linux-x86-64.s b/lib/runtime-core/image-loading-linux-x86-64.s index 37ed0f986..c9d6edff2 100644 --- a/lib/runtime-core/image-loading-linux-x86-64.s +++ b/lib/runtime-core/image-loading-linux-x86-64.s @@ -41,6 +41,30 @@ add $8, %rsp movq (%rsp), %xmm7 add $8, %rsp +movq (%rsp), %xmm8 +add $8, %rsp + +movq (%rsp), %xmm9 +add $8, %rsp + +movq (%rsp), %xmm10 +add $8, %rsp + +movq (%rsp), %xmm11 +add $8, %rsp + +movq (%rsp), %xmm12 +add $8, %rsp + +movq (%rsp), %xmm13 +add $8, %rsp + +movq (%rsp), %xmm14 +add $8, %rsp + +movq (%rsp), %xmm15 +add $8, %rsp + popq %rbp popq %rax popq %rbx @@ -67,3 +91,37 @@ popq %r13 popq %r14 popq %r15 retq + +# For switching into a backend without information about where registers are preserved. +.globl register_preservation_trampoline +register_preservation_trampoline: +subq $8, %rsp +pushq %rax +pushq %rcx +pushq %rdx +pushq %rdi +pushq %rsi +pushq %r8 +pushq %r9 +pushq %r10 + +callq get_boundary_register_preservation@PLT + +# Keep this consistent with BoundaryRegisterPreservation +movq %r15, 0(%rax) +movq %r14, 8(%rax) +movq %r13, 16(%rax) +movq %r12, 24(%rax) +movq %rbx, 32(%rax) + +popq %r10 +popq %r9 +popq %r8 +popq %rsi +popq %rdi +popq %rdx +popq %rcx +popq %rax +addq $8, %rsp + +jmpq *%rax diff --git a/lib/runtime-core/image-loading-macos-x86-64.s b/lib/runtime-core/image-loading-macos-x86-64.s index a6a307f1f..5e19f7b4a 100644 --- a/lib/runtime-core/image-loading-macos-x86-64.s +++ b/lib/runtime-core/image-loading-macos-x86-64.s @@ -41,6 +41,30 @@ add $8, %rsp movq (%rsp), %xmm7 add $8, %rsp +movq (%rsp), %xmm8 +add $8, %rsp + +movq (%rsp), %xmm9 +add $8, %rsp + +movq (%rsp), %xmm10 +add $8, %rsp + +movq (%rsp), %xmm11 +add $8, %rsp + +movq (%rsp), %xmm12 +add $8, %rsp + +movq (%rsp), %xmm13 +add $8, %rsp + +movq (%rsp), %xmm14 +add $8, %rsp + +movq (%rsp), %xmm15 +add $8, %rsp + popq %rbp popq %rax popq %rbx @@ -67,3 +91,37 @@ popq %r13 popq %r14 popq %r15 retq + +# For switching into a backend without information about where registers are preserved. +.globl _register_preservation_trampoline +_register_preservation_trampoline: +subq $8, %rsp +pushq %rax +pushq %rcx +pushq %rdx +pushq %rdi +pushq %rsi +pushq %r8 +pushq %r9 +pushq %r10 + +callq _get_boundary_register_preservation + +# Keep this consistent with BoundaryRegisterPreservation +movq %r15, 0(%rax) +movq %r14, 8(%rax) +movq %r13, 16(%rax) +movq %r12, 24(%rax) +movq %rbx, 32(%rax) + +popq %r10 +popq %r9 +popq %r8 +popq %rsi +popq %rdi +popq %rdx +popq %rcx +popq %rax +addq $8, %rsp + +jmpq *%rax diff --git a/lib/runtime-core/src/backend.rs b/lib/runtime-core/src/backend.rs index 2bb230318..febfdda0e 100644 --- a/lib/runtime-core/src/backend.rs +++ b/lib/runtime-core/src/backend.rs @@ -22,14 +22,17 @@ pub mod sys { } pub use crate::sig_registry::SigRegistry; +/// Enum used to select which compiler should be used to generate code. #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] pub enum Backend { Cranelift, Singlepass, LLVM, + Auto, } impl Backend { + /// Get a list of the currently enabled (via feature flag) backends. pub fn variants() -> &'static [&'static str] { &[ #[cfg(feature = "backend-cranelift")] @@ -38,16 +41,18 @@ impl Backend { "singlepass", #[cfg(feature = "backend-llvm")] "llvm", + "auto", ] } - /// stable string representation of the backend - /// can be used as part of a cache key, for example + /// Stable string representation of the backend. + /// It can be used as part of a cache key, for example. pub fn to_string(&self) -> &'static str { match self { Backend::Cranelift => "cranelift", Backend::Singlepass => "singlepass", Backend::LLVM => "llvm", + Backend::Auto => "auto", } } } @@ -65,11 +70,88 @@ impl std::str::FromStr for Backend { "singlepass" => Ok(Backend::Singlepass), "cranelift" => Ok(Backend::Cranelift), "llvm" => Ok(Backend::LLVM), + "auto" => Ok(Backend::Auto), _ => Err(format!("The backend {} doesn't exist", s)), } } } +#[derive(Copy, Clone, Debug)] +pub enum Architecture { + X64, + Aarch64, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug)] +pub enum InlineBreakpointType { + Trace, + Middleware, + Unknown, +} + +#[derive(Clone, Debug)] +pub struct InlineBreakpoint { + pub size: usize, + pub ty: InlineBreakpointType, +} + +pub fn get_inline_breakpoint_size(arch: Architecture, backend: Backend) -> Option { + match (arch, backend) { + (Architecture::X64, Backend::Singlepass) => Some(7), + (Architecture::Aarch64, Backend::Singlepass) => Some(12), + _ => None, + } +} + +pub fn read_inline_breakpoint( + arch: Architecture, + backend: Backend, + code: &[u8], +) -> Option { + match arch { + Architecture::X64 => match backend { + Backend::Singlepass => { + if code.len() < 7 { + None + } else if &code[..6] == &[0x0f, 0x0b, 0x0f, 0xb9, 0xcd, 0xff] { + // ud2 ud (int 0xff) code + Some(InlineBreakpoint { + size: 7, + ty: match code[6] { + 0 => InlineBreakpointType::Trace, + 1 => InlineBreakpointType::Middleware, + _ => InlineBreakpointType::Unknown, + }, + }) + } else { + None + } + } + _ => None, + }, + Architecture::Aarch64 => match backend { + Backend::Singlepass => { + if code.len() < 12 { + None + } else if &code[..8] == &[0, 0, 0, 0, 0xff, 0xff, 0xff, 0xff] { + Some(InlineBreakpoint { + size: 12, + ty: match code[8] { + 0 => InlineBreakpointType::Trace, + 1 => InlineBreakpointType::Middleware, + _ => InlineBreakpointType::Unknown, + }, + }) + } else { + None + } + } + _ => None, + }, + } +} + #[cfg(test)] mod backend_test { use super::*; @@ -111,6 +193,7 @@ impl Default for MemoryBoundCheckMode { } } +/// Controls which experimental features will be enabled. #[derive(Debug, Default)] pub struct Features { pub simd: bool, @@ -126,6 +209,11 @@ pub struct CompilerConfig { pub enforce_stack_check: bool, pub track_state: bool, pub features: Features, + + // target info used by LLVM + pub triple: Option, + pub cpu_name: Option, + pub cpu_features: Option, } pub trait Compiler { @@ -159,6 +247,10 @@ pub trait RunnableModule: Send + Sync { None } + unsafe fn patch_local_function(&self, _idx: usize, _target_address: usize) -> bool { + false + } + /// A wasm trampoline contains the necessary data to dynamically call an exported wasm function. /// Given a particular signature index, we are returned a trampoline that is matched with that /// signature and an invoke function that can call the trampoline. @@ -175,6 +267,11 @@ pub trait RunnableModule: Send + Sync { fn get_offsets(&self) -> Option> { None } + + /// Returns the beginning offsets of all local functions. + fn get_local_function_offsets(&self) -> Option> { + None + } } pub trait CacheGen: Send + Sync { diff --git a/lib/runtime-core/src/backing.rs b/lib/runtime-core/src/backing.rs index 8f9c66c73..1b50abaf6 100644 --- a/lib/runtime-core/src/backing.rs +++ b/lib/runtime-core/src/backing.rs @@ -1,5 +1,5 @@ use crate::{ - error::{LinkError, LinkResult}, + error::{CreationError, LinkError, LinkResult}, export::{Context, Export}, global::Global, import::ImportObject, @@ -15,8 +15,13 @@ use crate::{ }, vm, }; -use std::{fmt::Debug, slice}; +use std::{ + fmt::Debug, + ptr::{self, NonNull}, + slice, +}; +/// Size of the array for internal instance usage pub const INTERNALS_SIZE: usize = 256; pub(crate) struct Internals(pub(crate) [u64; INTERNALS_SIZE]); @@ -54,22 +59,32 @@ pub struct LocalBacking { pub(crate) internals: Internals, } +// Manually implemented because LocalBacking contains raw pointers directly +unsafe impl Send for LocalBacking {} + impl LocalBacking { pub(crate) fn new( module: &ModuleInner, imports: &ImportBacking, vmctx: *mut vm::Ctx, ) -> LinkResult { - let mut memories = Self::generate_memories(module); + let mut memories = match Self::generate_memories(module) { + Ok(m) => m, + Err(e) => { + return Err(vec![LinkError::Generic { + message: format!("unable to create memory: {:?}", e), + }]); + } + }; let mut tables = Self::generate_tables(module); - let mut globals = Self::generate_globals(module, imports); + let mut globals = Self::generate_globals(module, imports)?; // Ensure all initializers are valid before running finalizers Self::validate_memories(module, imports)?; Self::validate_tables(module, imports, &mut tables)?; - let vm_memories = Self::finalize_memories(module, imports, &mut memories); - let vm_tables = Self::finalize_tables(module, imports, &mut tables, vmctx); + let vm_memories = Self::finalize_memories(module, imports, &mut memories)?; + let vm_tables = Self::finalize_tables(module, imports, &mut tables, vmctx)?; let vm_globals = Self::finalize_globals(&mut globals); let dynamic_sigindices = Self::generate_sigindices(&module.info); @@ -116,13 +131,16 @@ impl LocalBacking { .into_boxed_map() } - fn generate_memories(module: &ModuleInner) -> BoxedMap { + fn generate_memories( + module: &ModuleInner, + ) -> Result, CreationError> { let mut memories = Map::with_capacity(module.info.memories.len()); for (_, &desc) in &module.info.memories { - memories.push(Memory::new(desc).expect("unable to create memory")); + let memory = Memory::new(desc)?; + memories.push(memory); } - memories.into_boxed_map() + Ok(memories.into_boxed_map()) } /// Validate each locally-defined memory in the Module. @@ -133,12 +151,23 @@ impl LocalBacking { for init in module.info.data_initializers.iter() { let init_base = match init.base { Initializer::Const(Value::I32(offset)) => offset as u32, - Initializer::Const(_) => panic!("a const initializer must be the i32 type"), + Initializer::Const(_) => { + return Err(vec![LinkError::Generic { + message: "a const initializer must be an i32".to_string(), + }]); + } Initializer::GetGlobal(import_global_index) => { + if import_global_index.index() >= imports.globals.len() { + return Err(vec![LinkError::Generic { + message: "incorrect global index for initializer".to_string(), + }]); + } if let Value::I32(x) = imports.globals[import_global_index].get() { x as u32 } else { - panic!("unsupported global type for initializer") + return Err(vec![LinkError::Generic { + message: "unsupported global type for initializer".to_string(), + }]); } } } as usize; @@ -177,18 +206,29 @@ impl LocalBacking { module: &ModuleInner, imports: &ImportBacking, memories: &mut SliceMap, - ) -> BoxedMap { + ) -> LinkResult> { // For each init that has some data... // Initialize data for init in module.info.data_initializers.iter() { let init_base = match init.base { Initializer::Const(Value::I32(offset)) => offset as u32, - Initializer::Const(_) => panic!("a const initializer must be the i32 type"), + Initializer::Const(_) => { + return Err(vec![LinkError::Generic { + message: "a const initializer must be an i32".to_string(), + }]); + } Initializer::GetGlobal(import_global_index) => { + if import_global_index.index() >= imports.globals.len() { + return Err(vec![LinkError::Generic { + message: "incorrect global index for initializer".to_string(), + }]); + } if let Value::I32(x) = imports.globals[import_global_index].get() { x as u32 } else { - panic!("unsupported global type for initializer") + return Err(vec![LinkError::Generic { + message: "unsupported global type for initializer".to_string(), + }]); } } } as usize; @@ -217,11 +257,11 @@ impl LocalBacking { } } - memories + Ok(memories .iter_mut() .map(|(_, mem)| mem.vm_local_memory()) .collect::>() - .into_boxed_map() + .into_boxed_map()) } fn generate_tables(module: &ModuleInner) -> BoxedMap { @@ -245,12 +285,23 @@ impl LocalBacking { for init in &module.info.elem_initializers { let init_base = match init.base { Initializer::Const(Value::I32(offset)) => offset as u32, - Initializer::Const(_) => panic!("a const initializer must be the i32 type"), + Initializer::Const(_) => { + return Err(vec![LinkError::Generic { + message: "a const initializer must be an i32".to_string(), + }]); + } Initializer::GetGlobal(import_global_index) => { + if import_global_index.index() >= imports.globals.len() { + return Err(vec![LinkError::Generic { + message: "incorrect global index for initializer".to_string(), + }]); + } if let Value::I32(x) = imports.globals[import_global_index].get() { x as u32 } else { - panic!("unsupported global type for initializer") + return Err(vec![LinkError::Generic { + message: "unsupported global type for initializer".to_string(), + }]); } } } as usize; @@ -288,16 +339,27 @@ impl LocalBacking { imports: &ImportBacking, tables: &mut SliceMap, vmctx: *mut vm::Ctx, - ) -> BoxedMap { + ) -> LinkResult> { for init in &module.info.elem_initializers { let init_base = match init.base { Initializer::Const(Value::I32(offset)) => offset as u32, - Initializer::Const(_) => panic!("a const initializer must be the i32 type"), + Initializer::Const(_) => { + return Err(vec![LinkError::Generic { + message: "a const initializer be an i32".to_string(), + }]); + } Initializer::GetGlobal(import_global_index) => { + if import_global_index.index() >= imports.globals.len() { + return Err(vec![LinkError::Generic { + message: "incorrect global index for initializer".to_string(), + }]); + } if let Value::I32(x) = imports.globals[import_global_index].get() { x as u32 } else { - panic!("unsupported global type for initializer") + return Err(vec![LinkError::Generic { + message: "unsupported global type for initializer".to_string(), + }]); } } } as usize; @@ -325,9 +387,9 @@ impl LocalBacking { vmctx, ), LocalOrImport::Import(imported_func_index) => { - let vm::ImportedFunc { func, vmctx } = + let vm::ImportedFunc { func, func_ctx } = imports.vm_functions[imported_func_index]; - (func, vmctx) + (func, unsafe { func_ctx.as_ref() }.vmctx.as_ptr()) } }; @@ -358,9 +420,9 @@ impl LocalBacking { vmctx, ), LocalOrImport::Import(imported_func_index) => { - let vm::ImportedFunc { func, vmctx } = + let vm::ImportedFunc { func, func_ctx } = imports.vm_functions[imported_func_index]; - (func, vmctx) + (func, unsafe { func_ctx.as_ref() }.vmctx.as_ptr()) } }; @@ -371,23 +433,32 @@ impl LocalBacking { } } - tables + Ok(tables .iter_mut() .map(|(_, table)| table.vm_local_table()) .collect::>() - .into_boxed_map() + .into_boxed_map()) } fn generate_globals( module: &ModuleInner, imports: &ImportBacking, - ) -> BoxedMap { + ) -> LinkResult> { let mut globals = Map::with_capacity(module.info.globals.len()); for (_, global_init) in module.info.globals.iter() { let value = match &global_init.init { Initializer::Const(value) => value.clone(), Initializer::GetGlobal(import_global_index) => { + if imports.globals.len() <= import_global_index.index() { + return Err(vec![LinkError::Generic { + message: format!( + "Trying to read the `{:?}` global that is not properly initialized.", + import_global_index.index() + ), + }]); + } + imports.globals[*import_global_index].get() } }; @@ -401,7 +472,7 @@ impl LocalBacking { globals.push(global); } - globals.into_boxed_map() + Ok(globals.into_boxed_map()) } fn finalize_globals( @@ -415,6 +486,8 @@ impl LocalBacking { } } +/// The `ImportBacking` stores references to the imported resources of an Instance. This includes +/// imported memories, tables, globals and functions. #[derive(Debug)] pub struct ImportBacking { pub(crate) memories: BoxedMap, @@ -427,7 +500,11 @@ pub struct ImportBacking { pub(crate) vm_globals: BoxedMap, } +// manually implemented because ImportBacking contains raw pointers directly +unsafe impl Send for ImportBacking {} + impl ImportBacking { + /// Creates a new `ImportBacking` from the given `ModuleInner`, `ImportObject`, and `Ctx`. pub fn new( module: &ModuleInner, imports: &ImportObject, @@ -476,11 +553,21 @@ impl ImportBacking { } } + /// Gets a `ImportedFunc` from the given `ImportedFuncIndex`. pub fn imported_func(&self, index: ImportedFuncIndex) -> vm::ImportedFunc { self.vm_functions[index].clone() } } +impl Drop for ImportBacking { + fn drop(&mut self) { + // Properly drop the `vm::FuncCtx` in `vm::ImportedFunc`. + for (_imported_func_index, imported_func) in (*self.vm_functions).iter_mut() { + let _: Box = unsafe { Box::from_raw(imported_func.func_ctx.as_ptr()) }; + } + } +} + fn import_functions( module: &ModuleInner, imports: &ImportObject, @@ -502,9 +589,9 @@ fn import_functions( let namespace = module.info.namespace_table.get(*namespace_index); let name = module.info.name_table.get(*name_index); - let import = imports - .get_namespace(namespace) - .and_then(|namespace| namespace.get_export(name)); + let import = + imports.maybe_with_namespace(namespace, |namespace| namespace.get_export(name)); + match import { Some(Export::Function { func, @@ -514,10 +601,28 @@ fn import_functions( if *expected_sig == *signature { functions.push(vm::ImportedFunc { func: func.inner(), - vmctx: match ctx { - Context::External(ctx) => ctx, - Context::Internal => vmctx, - }, + func_ctx: NonNull::new(Box::into_raw(Box::new(vm::FuncCtx { + // ^^^^^^^^ `vm::FuncCtx` is purposely leaked. + // It is dropped by the specific `Drop` + // implementation of `ImportBacking`. + vmctx: NonNull::new(match ctx { + Context::External(vmctx) => vmctx, + Context::ExternalWithEnv(vmctx_, _) => { + if vmctx_.is_null() { + vmctx + } else { + vmctx_ + } + } + Context::Internal => vmctx, + }) + .expect("`vmctx` must not be null."), + func_env: match ctx { + Context::ExternalWithEnv(_, func_env) => func_env, + _ => None, + }, + }))) + .unwrap(), }); } else { link_errors.push(LinkError::IncorrectImportSignature { @@ -546,8 +651,8 @@ fn import_functions( None => { if imports.allow_missing_functions { functions.push(vm::ImportedFunc { - func: ::std::ptr::null(), - vmctx: ::std::ptr::null_mut(), + func: ptr::null(), + func_ctx: unsafe { NonNull::new_unchecked(ptr::null_mut()) }, // TODO: Non-sense… }); } else { link_errors.push(LinkError::ImportNotFound { @@ -559,7 +664,7 @@ fn import_functions( } } - if link_errors.len() > 0 { + if !link_errors.is_empty() { Err(link_errors) } else { Ok(functions.into_boxed_map()) @@ -590,9 +695,8 @@ fn import_memories( let namespace = module.info.namespace_table.get(*namespace_index); let name = module.info.name_table.get(*name_index); - let memory_import = imports - .get_namespace(&namespace) - .and_then(|namespace| namespace.get_export(&name)); + let memory_import = + imports.maybe_with_namespace(namespace, |namespace| namespace.get_export(name)); match memory_import { Some(Export::Memory(memory)) => { if expected_memory_desc.fits_in_imported(memory.descriptor()) { @@ -662,9 +766,8 @@ fn import_tables( let namespace = module.info.namespace_table.get(*namespace_index); let name = module.info.name_table.get(*name_index); - let table_import = imports - .get_namespace(&namespace) - .and_then(|namespace| namespace.get_export(&name)); + let table_import = + imports.maybe_with_namespace(namespace, |namespace| namespace.get_export(name)); match table_import { Some(Export::Table(mut table)) => { if expected_table_desc.fits_in_imported(table.descriptor()) { @@ -733,9 +836,8 @@ fn import_globals( { let namespace = module.info.namespace_table.get(*namespace_index); let name = module.info.name_table.get(*name_index); - let import = imports - .get_namespace(namespace) - .and_then(|namespace| namespace.get_export(name)); + let import = + imports.maybe_with_namespace(namespace, |namespace| namespace.get_export(name)); match import { Some(Export::Global(mut global)) => { if global.descriptor() == *imported_global_desc { diff --git a/lib/runtime-core/src/cache.rs b/lib/runtime-core/src/cache.rs index 89bbaf796..e924cd9f5 100644 --- a/lib/runtime-core/src/cache.rs +++ b/lib/runtime-core/src/cache.rs @@ -1,3 +1,7 @@ +//! The cache module provides the common data structures used by compiler backends to allow +//! serializing compiled wasm code to a binary format. The binary format can be persisted, +//! and loaded to allow skipping compilation and fast startup. + use crate::{ backend::Backend, module::{Module, ModuleInfo}, @@ -6,20 +10,31 @@ use crate::{ use blake2b_simd::blake2bp; use std::{fmt, io, mem, slice}; +/// Indicates the invalid type of invalid cache file #[derive(Debug)] pub enum InvalidFileType { + /// Given cache header slice does not match the expected size of an `ArtifactHeader` InvalidSize, + /// Given cache header slice does not contain the expected magic bytes InvalidMagic, } +/// Kinds of caching errors #[derive(Debug)] pub enum Error { + /// An IO error while reading/writing a cache binary. IoError(io::Error), + /// An error deserializing bytes into a cache data structure. DeserializeError(String), + /// An error serializing bytes from a cache data structure. SerializeError(String), + /// An undefined caching error with a message. Unknown(String), + /// An invalid cache binary given. InvalidFile(InvalidFileType), + /// The cached binary has been invalidated. InvalidatedCache, + /// The current backend does not support caching. UnsupportedBackend(Backend), } @@ -164,6 +179,8 @@ struct ArtifactInner { compiled_code: Memory, } +/// Artifact are produced by caching, are serialized/deserialized to binaries, and contain +/// module info, backend metadata, and compiled code. pub struct Artifact { inner: ArtifactInner, } @@ -183,6 +200,7 @@ impl Artifact { } } + /// Deserializes an `Artifact` from the given byte slice. pub fn deserialize(bytes: &[u8]) -> Result { let (_, body_slice) = ArtifactHeader::read_from_slice(bytes)?; @@ -192,6 +210,7 @@ impl Artifact { Ok(Artifact { inner }) } + /// A reference to the `Artifact`'s stored `ModuleInfo` pub fn info(&self) -> &ModuleInfo { &self.inner.info } @@ -205,6 +224,7 @@ impl Artifact { ) } + /// Serializes the `Artifact` into a vector of bytes pub fn serialize(&self) -> Result, Error> { let cache_header = ArtifactHeader { magic: WASMER_CACHE_MAGIC, @@ -230,7 +250,9 @@ impl Artifact { /// /// The `wasmer-runtime` supplies a naive `FileSystemCache` api. pub trait Cache { + /// Error type to return when load error occurs type LoadError: fmt::Debug; + /// Error type to return when store error occurs type StoreError: fmt::Debug; /// loads a module using the default `Backend` @@ -238,6 +260,7 @@ pub trait Cache { /// loads a cached module using a specific `Backend` fn load_with_backend(&self, key: WasmHash, backend: Backend) -> Result; + /// Store a module into the cache with the given key fn store(&mut self, key: WasmHash, module: Module) -> Result<(), Self::StoreError>; } diff --git a/lib/runtime-core/src/codegen.rs b/lib/runtime-core/src/codegen.rs index e69b9637c..991e05ed8 100644 --- a/lib/runtime-core/src/codegen.rs +++ b/lib/runtime-core/src/codegen.rs @@ -1,3 +1,7 @@ +//! The codegen module provides common functions and data structures used by multiple backends +//! during the code generation process. +#[cfg(unix)] +use crate::fault::FaultInfo; use crate::{ backend::RunnableModule, backend::{Backend, CacheGen, Compiler, CompilerConfig, Features, Token}, @@ -17,22 +21,35 @@ use std::sync::{Arc, RwLock}; use wasmparser::{self, WasmDecoder}; use wasmparser::{Operator, Type as WpType}; +/// A type that defines a function pointer, which is called when breakpoints occur. pub type BreakpointHandler = Box Result<(), Box> + Send + Sync + 'static>; + +/// Maps instruction pointers to their breakpoint handlers. pub type BreakpointMap = Arc>; +/// An event generated during parsing of a wasm binary #[derive(Debug)] pub enum Event<'a, 'b> { + /// An internal event created by the parser used to provide hooks during code generation. Internal(InternalEvent), + /// An event generated by parsing a wasm operator Wasm(&'b Operator<'a>), + /// An event generated by parsing a wasm operator that contains an owned `Operator` WasmOwned(Operator<'a>), } +/// Kinds of `InternalEvent`s created during parsing. pub enum InternalEvent { + /// A function parse is about to begin. FunctionBegin(u32), + /// A function parsing has just completed. FunctionEnd, + /// A breakpoint emitted during parsing. Breakpoint(BreakpointHandler), + /// Indicates setting an internal field. SetInternal(u32), + /// Indicates getting an internal field. GetInternal(u32), } @@ -48,14 +65,32 @@ impl fmt::Debug for InternalEvent { } } +/// Information for a breakpoint +#[cfg(unix)] pub struct BreakpointInfo<'a> { - pub fault: Option<&'a dyn Any>, + /// Fault. + pub fault: Option<&'a FaultInfo>, } +/// Information for a breakpoint +#[cfg(not(unix))] +pub struct BreakpointInfo { + /// Fault placeholder. + pub fault: Option<()>, +} + +/// A trait that represents the functions needed to be implemented to generate code for a module. pub trait ModuleCodeGenerator, RM: RunnableModule, E: Debug> { /// Creates a new module code generator. fn new() -> Self; + /// Creates a new module code generator for specified target. + fn new_with_target( + triple: Option, + cpu_name: Option, + cpu_features: Option, + ) -> Self; + /// Returns the backend id associated with this MCG. fn backend_id() -> Backend; @@ -65,7 +100,7 @@ pub trait ModuleCodeGenerator, RM: RunnableModule, } /// Adds an import function. fn feed_import_function(&mut self) -> Result<(), E>; - + /// Sets the signatures. fn feed_signatures(&mut self, signatures: Map) -> Result<(), E>; /// Sets function signatures. fn feed_function_signatures(&mut self, assoc: Map) -> Result<(), E>; @@ -80,6 +115,8 @@ pub trait ModuleCodeGenerator, RM: RunnableModule, unsafe fn from_cache(cache: Artifact, _: Token) -> Result; } +/// A streaming compiler which is designed to generated code for a module based on a stream +/// of wasm parser events. pub struct StreamingCompiler< MCG: ModuleCodeGenerator, FCG: FunctionCodeGenerator, @@ -94,6 +131,7 @@ pub struct StreamingCompiler< _phantom_e: PhantomData, } +/// A simple generator for a `StreamingCompiler`. pub struct SimpleStreamingCompilerGen< MCG: ModuleCodeGenerator, FCG: FunctionCodeGenerator, @@ -113,6 +151,7 @@ impl< E: Debug, > SimpleStreamingCompilerGen { + /// Create a new `StreamingCompiler`. pub fn new() -> StreamingCompiler MiddlewareChain> { StreamingCompiler::new(|| MiddlewareChain::new()) } @@ -126,6 +165,7 @@ impl< CGEN: Fn() -> MiddlewareChain, > StreamingCompiler { + /// Create a new `StreamingCompiler` with the given `MiddlewareChain`. pub fn new(chain_gen: CGEN) -> Self { Self { middleware_chain_generator: chain_gen, @@ -137,6 +177,7 @@ impl< } } +/// Create a new `ValidatingParserConfig` with the given features. pub fn validating_parser_config(features: &Features) -> wasmparser::ValidatingParserConfig { wasmparser::ValidatingParserConfig { operator_config: wasmparser::OperatorValidatorConfig { @@ -146,7 +187,6 @@ pub fn validating_parser_config(features: &Features) -> wasmparser::ValidatingPa enable_bulk_memory: false, enable_multi_value: false, }, - mutable_global_imports: true, } } @@ -183,7 +223,14 @@ impl< validate_with_features(wasm, &compiler_config.features)?; } - let mut mcg = MCG::new(); + let mut mcg = match MCG::backend_id() { + Backend::LLVM => MCG::new_with_target( + compiler_config.triple.clone(), + compiler_config.cpu_name.clone(), + compiler_config.cpu_features.clone(), + ), + _ => MCG::new(), + }; let mut chain = (self.middleware_chain_generator)(); let info = crate::parse::read_module( wasm, @@ -216,34 +263,41 @@ impl< fn requires_pre_validation(backend: Backend) -> bool { match backend { Backend::Cranelift => true, - Backend::LLVM => false, + Backend::LLVM => true, Backend::Singlepass => false, + Backend::Auto => false, } } +/// A sink for parse events. pub struct EventSink<'a, 'b> { buffer: SmallVec<[Event<'a, 'b>; 2]>, } impl<'a, 'b> EventSink<'a, 'b> { + /// Push a new `Event` to this sink. pub fn push(&mut self, ev: Event<'a, 'b>) { self.buffer.push(ev); } } +/// A container for a chain of middlewares. pub struct MiddlewareChain { chain: Vec>, } impl MiddlewareChain { + /// Create a new empty `MiddlewareChain`. pub fn new() -> MiddlewareChain { MiddlewareChain { chain: vec![] } } + /// Push a new `FunctionMiddleware` to this `MiddlewareChain`. pub fn push(&mut self, m: M) { self.chain.push(Box::new(m)); } + /// Run this chain with the provided function code generator, event and module info. pub(crate) fn run>( &mut self, fcg: Option<&mut FCG>, @@ -271,8 +325,11 @@ impl MiddlewareChain { } } +/// A trait that represents the signature required to implement middleware for a function. pub trait FunctionMiddleware { + /// The error type for this middleware's functions. type Error: Debug; + /// Processes the given event, module info and sink. fn feed_event<'a, 'b: 'a>( &mut self, op: Event<'a, 'b>, diff --git a/lib/runtime-core/src/error.rs b/lib/runtime-core/src/error.rs index 9aac82fab..f2fcf7ebd 100644 --- a/lib/runtime-core/src/error.rs +++ b/lib/runtime-core/src/error.rs @@ -1,13 +1,28 @@ +//! The error module contains the data structures and helper functions used to implement errors that +//! are produced and returned from the wasmer runtime core. use crate::types::{FuncSig, GlobalDescriptor, MemoryDescriptor, TableDescriptor, Type}; use core::borrow::Borrow; use std::any::Any; +/// Aliases the standard `Result` type as `Result` within this module. pub type Result = std::result::Result; +/// Result of an attempt to compile the provided WebAssembly module into a `Module`. +/// Aliases the standard `Result` with `CompileError` as the default error type. pub type CompileResult = std::result::Result; +/// Result of an attempt to link the provided WebAssembly instance. +/// Aliases the standard `Result` with `Vec` as the default error type. pub type LinkResult = std::result::Result>; +/// Result of an attempt to run the provided WebAssembly instance. +/// Aliases the standard `Result` with `RuntimeError` as the default error type. pub type RuntimeResult = std::result::Result; +/// Result of an attempt to call the provided WebAssembly instance. +/// Aliases the standard `Result` with `CallError` as the default error type. pub type CallResult = std::result::Result; +/// Result of an attempt to resolve a WebAssembly function by name. +/// Aliases the standard `Result` with `ResolveError` as the default error type. pub type ResolveResult = std::result::Result; +/// Result of an attempt to parse bytes into a WebAssembly module. +/// Aliases the standard `Result` with `ParseError` as the default error type. pub type ParseResult = std::result::Result; /// This is returned when the chosen compiler is unable to @@ -17,8 +32,16 @@ pub type ParseResult = std::result::Result; /// Comparing two `CompileError`s always evaluates to false. #[derive(Debug, Clone)] pub enum CompileError { - ValidationError { msg: String }, - InternalError { msg: String }, + /// A validation error containing an error message. + ValidationError { + /// An error message. + msg: String, + }, + /// A internal error containing an error message. + InternalError { + /// An error message. + msg: String, + }, } impl PartialEq for CompileError { @@ -46,41 +69,71 @@ impl std::error::Error for CompileError {} /// Comparing two `LinkError`s always evaluates to false. #[derive(Debug, Clone)] pub enum LinkError { + /// The type of the provided import does not match the expected type. IncorrectImportType { + /// Namespace. namespace: String, + /// Name. name: String, + /// Expected. expected: String, + /// Found. found: String, }, + /// The signature of the provided import does not match the expected signature. IncorrectImportSignature { + /// Namespace. namespace: String, + /// Name. name: String, + /// Expected. expected: FuncSig, + /// Found. found: FuncSig, }, + /// An expected import was not provided. ImportNotFound { + /// Namespace. namespace: String, + /// Name. name: String, }, + /// The memory descriptor provided does not match the expected descriptor. IncorrectMemoryDescriptor { + /// Namespace. namespace: String, + /// Name. name: String, + /// Expected. expected: MemoryDescriptor, + /// Found. found: MemoryDescriptor, }, + /// The table descriptor provided does not match the expected descriptor. IncorrectTableDescriptor { + /// Namespace. namespace: String, + /// Name. name: String, + /// Expected. expected: TableDescriptor, + /// Found. found: TableDescriptor, }, + /// The global descriptor provided does not match the expected descriptor. IncorrectGlobalDescriptor { + /// Namespace. namespace: String, + /// Name. name: String, + /// Expected. expected: GlobalDescriptor, + /// Found. found: GlobalDescriptor, }, + /// A generic error with a message. Generic { + /// Error message. message: String, }, } @@ -126,8 +179,16 @@ impl std::error::Error for LinkError {} /// /// Comparing two `RuntimeError`s always evaluates to false. pub enum RuntimeError { - Trap { msg: Box }, - Error { data: Box }, + /// Trap. + Trap { + /// Trap message. + msg: Box, + }, + /// Error. + Error { + /// Error data. + data: Box, + }, } impl PartialEq for RuntimeError { @@ -169,9 +230,23 @@ impl std::error::Error for RuntimeError {} /// Comparing two `ResolveError`s always evaluates to false. #[derive(Debug, Clone)] pub enum ResolveError { - Signature { expected: FuncSig, found: Vec }, - ExportNotFound { name: String }, - ExportWrongType { name: String }, + /// Found signature did not match expected signature. + Signature { + /// Expected `FuncSig`. + expected: FuncSig, + /// Found type. + found: Vec, + }, + /// Export not found. + ExportNotFound { + /// Name. + name: String, + }, + /// Export found with the wrong type. + ExportWrongType { + /// Name. + name: String, + }, } impl PartialEq for ResolveError { @@ -213,7 +288,9 @@ impl std::error::Error for ResolveError {} /// /// Comparing two `CallError`s always evaluates to false. pub enum CallError { + /// An error occured resolving the functions name or types. Resolve(ResolveError), + /// A runtime error occurred during the function call. Runtime(RuntimeError), } @@ -247,8 +324,11 @@ impl std::error::Error for CallError {} /// like a `Memory` or a `Table`. #[derive(Debug, Clone)] pub enum CreationError { + /// Unable to create memory error. UnableToCreateMemory, + /// Unable to create table error. UnableToCreateTable, + /// Invalid descriptor error with message. InvalidDescriptor(String), } @@ -281,11 +361,17 @@ impl std::error::Error for CreationError {} /// Comparing two `Error`s always evaluates to false. #[derive(Debug)] pub enum Error { + /// Compile error. CompileError(CompileError), + /// Link errors. LinkError(Vec), + /// Runtime error. RuntimeError(RuntimeError), + /// Resolve error. ResolveError(ResolveError), + /// Call error. CallError(CallError), + /// Creation error. CreationError(CreationError), } @@ -368,13 +454,20 @@ impl std::fmt::Display for Error { impl std::error::Error for Error {} +/// An error occurred while growing a memory or table. #[derive(Debug)] pub enum GrowError { + /// Error growing memory. MemoryGrowError, + /// Error growing table. TableGrowError, + /// Max pages were exceeded. ExceededMaxPages(PageError), + /// Max pages for memory were exceeded. ExceededMaxPagesForMemory(usize, usize), + /// Error protecting memory. CouldNotProtectMemory(MemoryProtectionError), + /// Error creating memory. CouldNotCreateMemory(MemoryCreationError), } @@ -393,9 +486,11 @@ impl std::fmt::Display for GrowError { impl std::error::Error for GrowError {} +/// A kind of page error. #[derive(Debug)] pub enum PageError { // left, right, added + /// Max pages were exceeded error. ExceededMaxPages(usize, usize, usize), } @@ -414,9 +509,12 @@ impl Into for PageError { } } +/// Error occured while creating memory. #[derive(Debug)] pub enum MemoryCreationError { + /// Allocation of virtual memory failed error. VirtualMemoryAllocationFailed(usize, String), + /// Error creating memory from file. CouldNotCreateMemoryFromFile(std::io::Error), } @@ -446,8 +544,10 @@ impl From for MemoryCreationError { } } +/// Error protecting memory. #[derive(Debug)] pub enum MemoryProtectionError { + /// Protection failed error. ProtectionFailed(usize, usize, String), } @@ -470,8 +570,10 @@ impl Into for MemoryProtectionError { } } +/// Parse Error. #[derive(Debug)] pub enum ParseError { + /// Error reading binary. BinaryReadError, } diff --git a/lib/runtime-core/src/export.rs b/lib/runtime-core/src/export.rs index 52ea9cf61..8729d9797 100644 --- a/lib/runtime-core/src/export.rs +++ b/lib/runtime-core/src/export.rs @@ -1,31 +1,57 @@ +//! The export module contains the implementation data structures and helper functions used to +//! manipulate and access a wasm module's exports including memories, tables, globals, and +//! functions. use crate::{ global::Global, instance::InstanceInner, memory::Memory, module::ExportIndex, module::ModuleInner, table::Table, types::FuncSig, vm, }; use indexmap::map::Iter as IndexMapIter; -use std::sync::Arc; +use std::{ptr::NonNull, sync::Arc}; +/// A kind of Context. #[derive(Debug, Copy, Clone)] pub enum Context { + /// External context include a mutable pointer to `Ctx`. External(*mut vm::Ctx), + + /// External context with an environment include a mutable pointer + /// to `Ctx` and an optional non-null pointer to `FuncEnv`. + ExternalWithEnv(*mut vm::Ctx, Option>), + + /// Internal context. Internal, } +// Manually implemented because context contains a raw pointer to Ctx +unsafe impl Send for Context {} + +/// Kind of WebAssembly export. #[derive(Debug, Clone)] pub enum Export { + /// Function export. Function { + /// A pointer to a function. func: FuncPointer, + /// A kind of context. ctx: Context, + /// The signature of the function. signature: Arc, }, + /// Memory export. Memory(Memory), + /// Table export. Table(Table), + /// Global export. Global(Global), } +/// Const pointer to a `Func`. #[derive(Debug, Clone)] pub struct FuncPointer(*const vm::Func); +// Manually implemented because FuncPointer contains a raw pointer to Ctx +unsafe impl Send for FuncPointer {} + impl FuncPointer { /// This needs to be unsafe because there is /// no way to check whether the passed function @@ -39,6 +65,7 @@ impl FuncPointer { } } +/// An iterator to an instance's exports. pub struct ExportIter<'a> { inner: &'a InstanceInner, iter: IndexMapIter<'a, String, ExportIndex>, diff --git a/lib/runtime-core/src/fault.rs b/lib/runtime-core/src/fault.rs index b44978b1b..bc64c15fa 100644 --- a/lib/runtime-core/src/fault.rs +++ b/lib/runtime-core/src/fault.rs @@ -1,15 +1,35 @@ -mod raw { +//! The fault module contains the implementation for handling breakpoints, traps, and signals +//! for wasm code. + +pub mod raw { + //! The raw module contains required externed function interfaces for the fault module. use std::ffi::c_void; + #[cfg(target_arch = "x86_64")] extern "C" { + /// Load registers and return on the stack [stack_end..stack_begin]. pub fn run_on_alternative_stack(stack_end: *mut u64, stack_begin: *mut u64) -> u64; + /// Internal routine for switching into a backend without information about where registers are preserved. + pub fn register_preservation_trampoline(); // NOT safe to call directly + } + + /// Internal routine for switching into a backend without information about where registers are preserved. + #[cfg(not(target_arch = "x86_64"))] + pub extern "C" fn register_preservation_trampoline() { + unimplemented!("register_preservation_trampoline"); + } + + extern "C" { + /// libc setjmp pub fn setjmp(env: *mut c_void) -> i32; + /// libc longjmp pub fn longjmp(env: *mut c_void, val: i32) -> !; } } use crate::codegen::{BreakpointInfo, BreakpointMap}; -use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR, XMM}; +use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR}; +use crate::state::{CodeVersion, ExecutionStateImage}; use crate::vm; use libc::{mmap, mprotect, siginfo_t, MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE}; use nix::sys::signal::{ @@ -17,19 +37,25 @@ use nix::sys::signal::{ SIGSEGV, SIGTRAP, }; use std::any::Any; -use std::cell::UnsafeCell; +use std::cell::{Cell, RefCell, UnsafeCell}; use std::ffi::c_void; use std::process; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Once; +#[cfg(target_arch = "x86_64")] pub(crate) unsafe fn run_on_alternative_stack(stack_end: *mut u64, stack_begin: *mut u64) -> u64 { raw::run_on_alternative_stack(stack_end, stack_begin) } +#[cfg(not(target_arch = "x86_64"))] +pub(crate) unsafe fn run_on_alternative_stack(_stack_end: *mut u64, _stack_begin: *mut u64) -> u64 { + unimplemented!("run_on_alternative_stack"); +} + const TRAP_STACK_SIZE: usize = 1048576; // 1MB -const SETJMP_BUFFER_LEN: usize = 27; +const SETJMP_BUFFER_LEN: usize = 128; type SetJmpBuffer = [i32; SETJMP_BUFFER_LEN]; struct UnwindInfo { @@ -38,8 +64,34 @@ struct UnwindInfo { payload: Option>, // out } +/// A store for boundary register preservation. +#[repr(packed)] +#[derive(Default, Copy, Clone)] +pub struct BoundaryRegisterPreservation { + /// R15. + pub r15: u64, + /// R14. + pub r14: u64, + /// R13. + pub r13: u64, + /// R12. + pub r12: u64, + /// RBX. + pub rbx: u64, +} + thread_local! { static UNWIND: UnsafeCell> = UnsafeCell::new(None); + static CURRENT_CTX: UnsafeCell<*mut vm::Ctx> = UnsafeCell::new(::std::ptr::null_mut()); + static CURRENT_CODE_VERSIONS: RefCell> = RefCell::new(vec![]); + static WAS_SIGINT_TRIGGERED: Cell = Cell::new(false); + static BOUNDARY_REGISTER_PRESERVATION: UnsafeCell = UnsafeCell::new(BoundaryRegisterPreservation::default()); +} + +/// Gets a mutable pointer to the `BoundaryRegisterPreservation`. +#[no_mangle] +pub unsafe extern "C" fn get_boundary_register_preservation() -> *mut BoundaryRegisterPreservation { + BOUNDARY_REGISTER_PRESERVATION.with(|x| x.get()) } struct InterruptSignalMem(*mut u8); @@ -68,10 +120,49 @@ lazy_static! { } static INTERRUPT_SIGNAL_DELIVERED: AtomicBool = AtomicBool::new(false); +/// Returns a boolean indicating if SIGINT triggered the fault. +pub fn was_sigint_triggered_fault() -> bool { + WAS_SIGINT_TRIGGERED.with(|x| x.get()) +} + +/// Runs a callback function with the given `Ctx`. +pub unsafe fn with_ctx R>(ctx: *mut vm::Ctx, cb: F) -> R { + let addr = CURRENT_CTX.with(|x| x.get()); + let old = *addr; + *addr = ctx; + let ret = cb(); + *addr = old; + ret +} + +/// Pushes a new `CodeVersion` to the current code versions. +pub fn push_code_version(version: CodeVersion) { + CURRENT_CODE_VERSIONS.with(|x| x.borrow_mut().push(version)); +} + +/// Pops a `CodeVersion` from the current code versions. +pub fn pop_code_version() -> Option { + CURRENT_CODE_VERSIONS.with(|x| x.borrow_mut().pop()) +} + +/// Gets the wasm interrupt signal mem. pub unsafe fn get_wasm_interrupt_signal_mem() -> *mut u8 { INTERRUPT_SIGNAL_MEM.0 } +/// Sets the wasm interrupt on the given `Ctx`. +pub unsafe fn set_wasm_interrupt_on_ctx(ctx: *mut vm::Ctx) { + if mprotect( + (&*ctx).internal.interrupt_signal_mem as _, + INTERRUPT_SIGNAL_MEM_SIZE, + PROT_NONE, + ) < 0 + { + panic!("cannot set PROT_NONE on signal mem"); + } +} + +/// Sets a wasm interrupt. pub unsafe fn set_wasm_interrupt() { let mem: *mut u8 = INTERRUPT_SIGNAL_MEM.0; if mprotect(mem as _, INTERRUPT_SIGNAL_MEM_SIZE, PROT_NONE) < 0 { @@ -79,6 +170,7 @@ pub unsafe fn set_wasm_interrupt() { } } +/// Clears the wasm interrupt. pub unsafe fn clear_wasm_interrupt() { let mem: *mut u8 = INTERRUPT_SIGNAL_MEM.0; if mprotect(mem as _, INTERRUPT_SIGNAL_MEM_SIZE, PROT_READ | PROT_WRITE) < 0 { @@ -86,6 +178,7 @@ pub unsafe fn clear_wasm_interrupt() { } } +/// Catches an unsafe unwind with the given functions and breakpoints. pub unsafe fn catch_unsafe_unwind R>( f: F, breakpoints: Option, @@ -111,6 +204,7 @@ pub unsafe fn catch_unsafe_unwind R>( } } +/// Begins an unsafe unwind. pub unsafe fn begin_unsafe_unwind(e: Box) -> ! { let unwind = UNWIND.with(|x| x.get()); let inner = (*unwind) @@ -128,6 +222,14 @@ unsafe fn with_breakpoint_map) -> R>(f: F) - f(inner.breakpoints.as_ref()) } +#[cfg(not(target_arch = "x86_64"))] +/// Allocates and runs with the given stack size and closure. +pub fn allocate_and_run R>(_size: usize, f: F) -> R { + f() +} + +#[cfg(target_arch = "x86_64")] +/// Allocates and runs with the given stack size and closure. pub fn allocate_and_run R>(size: usize, f: F) -> R { struct Context R, R> { f: Option, @@ -154,7 +256,7 @@ pub fn allocate_and_run R>(size: usize, f: F) -> R { // NOTE: Keep this consistent with `image-loading-*.s`. stack[end_offset - 4 - 10] = &mut ctx as *mut Context as usize as u64; // rdi - const NUM_SAVED_REGISTERS: usize = 23; + const NUM_SAVED_REGISTERS: usize = 31; let stack_begin = stack .as_mut_ptr() .offset((end_offset - 4 - NUM_SAVED_REGISTERS) as isize); @@ -170,19 +272,84 @@ extern "C" fn signal_trap_handler( siginfo: *mut siginfo_t, ucontext: *mut c_void, ) { + use crate::backend::{ + get_inline_breakpoint_size, read_inline_breakpoint, Architecture, InlineBreakpointType, + }; + + #[cfg(target_arch = "x86_64")] + static ARCH: Architecture = Architecture::X64; + + #[cfg(target_arch = "aarch64")] + static ARCH: Architecture = Architecture::Aarch64; + + let mut should_unwind = false; + let mut unwind_result: Box = Box::new(()); + unsafe { let fault = get_fault_info(siginfo as _, ucontext); + let early_return = allocate_and_run(TRAP_STACK_SIZE, || { + CURRENT_CODE_VERSIONS.with(|versions| { + let versions = versions.borrow(); + for v in versions.iter() { + let magic_size = if let Some(x) = get_inline_breakpoint_size(ARCH, v.backend) { + x + } else { + continue; + }; + let ip = fault.ip.get(); + let end = v.base + v.msm.total_size; + if ip >= v.base && ip < end && ip + magic_size <= end { + if let Some(ib) = read_inline_breakpoint( + ARCH, + v.backend, + std::slice::from_raw_parts(ip as *const u8, magic_size), + ) { + match ib.ty { + InlineBreakpointType::Trace => {} + InlineBreakpointType::Middleware => { + let out: Option>> = + with_breakpoint_map(|bkpt_map| { + bkpt_map.and_then(|x| x.get(&ip)).map(|x| { + x(BreakpointInfo { + fault: Some(&fault), + }) + }) + }); + if let Some(Ok(())) = out { + } else if let Some(Err(e)) = out { + should_unwind = true; + unwind_result = e; + } + } + _ => println!("Unknown breakpoint type: {:?}", ib.ty), + } - let mut unwind_result: Box = Box::new(()); + fault.ip.set(ip + magic_size); + return true; + } + break; + } + } + false + }) + }); + if should_unwind { + begin_unsafe_unwind(unwind_result); + } + if early_return { + return; + } - let should_unwind = allocate_and_run(TRAP_STACK_SIZE, || { + should_unwind = allocate_and_run(TRAP_STACK_SIZE, || { let mut is_suspend_signal = false; + WAS_SIGINT_TRIGGERED.with(|x| x.set(false)); + match Signal::from_c_int(signum) { Ok(SIGTRAP) => { // breakpoint let out: Option>> = with_breakpoint_map(|bkpt_map| { - bkpt_map.and_then(|x| x.get(&(fault.ip as usize))).map(|x| { + bkpt_map.and_then(|x| x.get(&(fault.ip.get()))).map(|x| { x(BreakpointInfo { fault: Some(&fault), }) @@ -203,45 +370,31 @@ extern "C" fn signal_trap_handler( if fault.faulting_addr as usize == get_wasm_interrupt_signal_mem() as usize { is_suspend_signal = true; clear_wasm_interrupt(); - INTERRUPT_SIGNAL_DELIVERED.store(false, Ordering::SeqCst); + if INTERRUPT_SIGNAL_DELIVERED.swap(false, Ordering::SeqCst) { + WAS_SIGINT_TRIGGERED.with(|x| x.set(true)); + } } } _ => {} } - // TODO: make this safer - let ctx = &mut *(fault.known_registers[X64Register::GPR(GPR::R15).to_index().0].unwrap() - as *mut vm::Ctx); - let rsp = fault.known_registers[X64Register::GPR(GPR::RSP).to_index().0].unwrap(); - - let msm = (*ctx.module) - .runnable_module - .get_module_state_map() - .unwrap(); - let code_base = (*ctx.module).runnable_module.get_code().unwrap().as_ptr() as usize; - let es_image = read_stack( - &msm, - code_base, - rsp as usize as *const u64, - fault.known_registers, - Some(fault.ip as usize as u64), - ); + let ctx: &mut vm::Ctx = &mut **CURRENT_CTX.with(|x| x.get()); + let es_image = fault + .read_stack(None) + .expect("fault.read_stack() failed. Broken invariants?"); if is_suspend_signal { let image = build_instance_image(ctx, es_image); unwind_result = Box::new(image); } else { - use colored::*; if es_image.frames.len() > 0 { eprintln!( "\n{}", "Wasmer encountered an error while running your WebAssembly program." - .bold() - .red() ); es_image.print_backtrace_if_needed(); } - // Just let the error propagate otherrwise + // Just let the error propagate otherwise } true @@ -267,6 +420,7 @@ extern "C" fn sigint_handler( } } +/// Ensure the signal handler is installed. pub fn ensure_sighandler() { INSTALL_SIGHANDLER.call_once(|| unsafe { install_sighandler(); @@ -295,14 +449,109 @@ unsafe fn install_sighandler() { sigaction(SIGINT, &sa_interrupt).unwrap(); } +#[derive(Debug, Clone)] +/// Info about the fault pub struct FaultInfo { + /// Faulting address. pub faulting_addr: *const c_void, - pub ip: *const c_void, - pub known_registers: [Option; 24], + /// Instruction pointer. + pub ip: &'static Cell, + /// Values of known registers. + pub known_registers: [Option; 32], +} + +impl FaultInfo { + /// Parses the stack and builds an execution state image. + pub unsafe fn read_stack(&self, max_depth: Option) -> Option { + let rsp = match self.known_registers[X64Register::GPR(GPR::RSP).to_index().0] { + Some(x) => x, + None => return None, + }; + + Some(CURRENT_CODE_VERSIONS.with(|versions| { + let versions = versions.borrow(); + read_stack( + || versions.iter(), + rsp as usize as *const u64, + self.known_registers, + Some(self.ip.get() as u64), + max_depth, + ) + })) + } +} + +#[cfg(all(target_os = "linux", target_arch = "aarch64"))] +/// Get fault info from siginfo and ucontext. +pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *mut c_void) -> FaultInfo { + #[allow(dead_code)] + #[allow(non_camel_case_types)] + #[repr(packed)] + struct sigcontext { + fault_address: u64, + regs: [u64; 31], + sp: u64, + pc: u64, + pstate: u64, + reserved: [u8; 4096], + } + + #[allow(dead_code)] + #[allow(non_camel_case_types)] + #[repr(packed)] + struct ucontext { + unknown: [u8; 176], + uc_mcontext: sigcontext, + } + + #[allow(dead_code)] + #[allow(non_camel_case_types)] + #[repr(C)] + struct siginfo_t { + si_signo: i32, + si_errno: i32, + si_code: i32, + si_addr: u64, + // ... + } + + let siginfo = siginfo as *const siginfo_t; + let si_addr = (*siginfo).si_addr; + + let ucontext = ucontext as *mut ucontext; + let gregs = &(*ucontext).uc_mcontext.regs; + + let mut known_registers: [Option; 32] = [None; 32]; + + known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(gregs[15] as _); + known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(gregs[14] as _); + known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(gregs[13] as _); + known_registers[X64Register::GPR(GPR::R12).to_index().0] = Some(gregs[12] as _); + known_registers[X64Register::GPR(GPR::R11).to_index().0] = Some(gregs[11] as _); + known_registers[X64Register::GPR(GPR::R10).to_index().0] = Some(gregs[10] as _); + known_registers[X64Register::GPR(GPR::R9).to_index().0] = Some(gregs[9] as _); + known_registers[X64Register::GPR(GPR::R8).to_index().0] = Some(gregs[8] as _); + known_registers[X64Register::GPR(GPR::RSI).to_index().0] = Some(gregs[6] as _); + known_registers[X64Register::GPR(GPR::RDI).to_index().0] = Some(gregs[7] as _); + known_registers[X64Register::GPR(GPR::RDX).to_index().0] = Some(gregs[2] as _); + known_registers[X64Register::GPR(GPR::RCX).to_index().0] = Some(gregs[1] as _); + known_registers[X64Register::GPR(GPR::RBX).to_index().0] = Some(gregs[3] as _); + known_registers[X64Register::GPR(GPR::RAX).to_index().0] = Some(gregs[0] as _); + + known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(gregs[5] as _); + known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(gregs[28] as _); + + FaultInfo { + faulting_addr: si_addr as usize as _, + ip: std::mem::transmute::<&mut u64, &'static Cell>(&mut (*ucontext).uc_mcontext.pc), + known_registers, + } } #[cfg(all(target_os = "linux", target_arch = "x86_64"))] -pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> FaultInfo { +/// Get fault info from siginfo and ucontext. +pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *mut c_void) -> FaultInfo { + use crate::state::x64::XMM; use libc::{ _libc_xmmreg, ucontext_t, REG_R10, REG_R11, REG_R12, REG_R13, REG_R14, REG_R15, REG_R8, REG_R9, REG_RAX, REG_RBP, REG_RBX, REG_RCX, REG_RDI, REG_RDX, REG_RIP, REG_RSI, REG_RSP, @@ -325,11 +574,10 @@ pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> let siginfo = siginfo as *const siginfo_t; let si_addr = (*siginfo).si_addr; - let ucontext = ucontext as *const ucontext_t; - let gregs = &(*ucontext).uc_mcontext.gregs; - let fpregs = &*(*ucontext).uc_mcontext.fpregs; + let ucontext = ucontext as *mut ucontext_t; + let gregs = &mut (*ucontext).uc_mcontext.gregs; - let mut known_registers: [Option; 24] = [None; 24]; + let mut known_registers: [Option; 32] = [None; 32]; known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(gregs[REG_R15 as usize] as _); known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(gregs[REG_R14 as usize] as _); known_registers[X64Register::GPR(GPR::R13).to_index().0] = Some(gregs[REG_R13 as usize] as _); @@ -348,24 +596,43 @@ pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> known_registers[X64Register::GPR(GPR::RBP).to_index().0] = Some(gregs[REG_RBP as usize] as _); known_registers[X64Register::GPR(GPR::RSP).to_index().0] = Some(gregs[REG_RSP as usize] as _); - known_registers[X64Register::XMM(XMM::XMM0).to_index().0] = Some(read_xmm(&fpregs._xmm[0])); - known_registers[X64Register::XMM(XMM::XMM1).to_index().0] = Some(read_xmm(&fpregs._xmm[1])); - known_registers[X64Register::XMM(XMM::XMM2).to_index().0] = Some(read_xmm(&fpregs._xmm[2])); - known_registers[X64Register::XMM(XMM::XMM3).to_index().0] = Some(read_xmm(&fpregs._xmm[3])); - known_registers[X64Register::XMM(XMM::XMM4).to_index().0] = Some(read_xmm(&fpregs._xmm[4])); - known_registers[X64Register::XMM(XMM::XMM5).to_index().0] = Some(read_xmm(&fpregs._xmm[5])); - known_registers[X64Register::XMM(XMM::XMM6).to_index().0] = Some(read_xmm(&fpregs._xmm[6])); - known_registers[X64Register::XMM(XMM::XMM7).to_index().0] = Some(read_xmm(&fpregs._xmm[7])); + if !(*ucontext).uc_mcontext.fpregs.is_null() { + let fpregs = &*(*ucontext).uc_mcontext.fpregs; + known_registers[X64Register::XMM(XMM::XMM0).to_index().0] = Some(read_xmm(&fpregs._xmm[0])); + known_registers[X64Register::XMM(XMM::XMM1).to_index().0] = Some(read_xmm(&fpregs._xmm[1])); + known_registers[X64Register::XMM(XMM::XMM2).to_index().0] = Some(read_xmm(&fpregs._xmm[2])); + known_registers[X64Register::XMM(XMM::XMM3).to_index().0] = Some(read_xmm(&fpregs._xmm[3])); + known_registers[X64Register::XMM(XMM::XMM4).to_index().0] = Some(read_xmm(&fpregs._xmm[4])); + known_registers[X64Register::XMM(XMM::XMM5).to_index().0] = Some(read_xmm(&fpregs._xmm[5])); + known_registers[X64Register::XMM(XMM::XMM6).to_index().0] = Some(read_xmm(&fpregs._xmm[6])); + known_registers[X64Register::XMM(XMM::XMM7).to_index().0] = Some(read_xmm(&fpregs._xmm[7])); + known_registers[X64Register::XMM(XMM::XMM8).to_index().0] = Some(read_xmm(&fpregs._xmm[8])); + known_registers[X64Register::XMM(XMM::XMM9).to_index().0] = Some(read_xmm(&fpregs._xmm[9])); + known_registers[X64Register::XMM(XMM::XMM10).to_index().0] = + Some(read_xmm(&fpregs._xmm[10])); + known_registers[X64Register::XMM(XMM::XMM11).to_index().0] = + Some(read_xmm(&fpregs._xmm[11])); + known_registers[X64Register::XMM(XMM::XMM12).to_index().0] = + Some(read_xmm(&fpregs._xmm[12])); + known_registers[X64Register::XMM(XMM::XMM13).to_index().0] = + Some(read_xmm(&fpregs._xmm[13])); + known_registers[X64Register::XMM(XMM::XMM14).to_index().0] = + Some(read_xmm(&fpregs._xmm[14])); + known_registers[X64Register::XMM(XMM::XMM15).to_index().0] = + Some(read_xmm(&fpregs._xmm[15])); + } FaultInfo { faulting_addr: si_addr as usize as _, - ip: gregs[REG_RIP as usize] as _, + ip: std::mem::transmute::<&mut i64, &'static Cell>(&mut gregs[REG_RIP as usize]), known_registers, } } +/// Get fault info from siginfo and ucontext. #[cfg(all(target_os = "macos", target_arch = "x86_64"))] -pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> FaultInfo { +pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *mut c_void) -> FaultInfo { + use crate::state::x64::XMM; #[allow(dead_code)] #[repr(C)] struct ucontext_t { @@ -374,7 +641,7 @@ pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> uc_stack: libc::stack_t, uc_link: *const ucontext_t, uc_mcsize: u64, - uc_mcontext: *const mcontext_t, + uc_mcontext: *mut mcontext_t, } #[repr(C)] struct exception_state { @@ -409,8 +676,17 @@ pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> } #[repr(C)] struct fpstate { - _unused: [u8; 168], - xmm: [[u64; 2]; 8], + _cwd: u16, + _swd: u16, + _ftw: u16, + _fop: u16, + _rip: u64, + _rdp: u64, + _mxcsr: u32, + _mxcr_mask: u32, + _st: [[u16; 8]; 8], + xmm: [[u64; 2]; 16], + _padding: [u32; 24], } #[allow(dead_code)] #[repr(C)] @@ -423,11 +699,11 @@ pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> let siginfo = siginfo as *const siginfo_t; let si_addr = (*siginfo).si_addr; - let ucontext = ucontext as *const ucontext_t; - let ss = &(*(*ucontext).uc_mcontext).ss; + let ucontext = ucontext as *mut ucontext_t; + let ss = &mut (*(*ucontext).uc_mcontext).ss; let fs = &(*(*ucontext).uc_mcontext).fs; - let mut known_registers: [Option; 24] = [None; 24]; + let mut known_registers: [Option; 32] = [None; 32]; known_registers[X64Register::GPR(GPR::R15).to_index().0] = Some(ss.r15); known_registers[X64Register::GPR(GPR::R14).to_index().0] = Some(ss.r14); @@ -455,10 +731,18 @@ pub unsafe fn get_fault_info(siginfo: *const c_void, ucontext: *const c_void) -> known_registers[X64Register::XMM(XMM::XMM5).to_index().0] = Some(fs.xmm[5][0]); known_registers[X64Register::XMM(XMM::XMM6).to_index().0] = Some(fs.xmm[6][0]); known_registers[X64Register::XMM(XMM::XMM7).to_index().0] = Some(fs.xmm[7][0]); + known_registers[X64Register::XMM(XMM::XMM8).to_index().0] = Some(fs.xmm[8][0]); + known_registers[X64Register::XMM(XMM::XMM9).to_index().0] = Some(fs.xmm[9][0]); + known_registers[X64Register::XMM(XMM::XMM10).to_index().0] = Some(fs.xmm[10][0]); + known_registers[X64Register::XMM(XMM::XMM11).to_index().0] = Some(fs.xmm[11][0]); + known_registers[X64Register::XMM(XMM::XMM12).to_index().0] = Some(fs.xmm[12][0]); + known_registers[X64Register::XMM(XMM::XMM13).to_index().0] = Some(fs.xmm[13][0]); + known_registers[X64Register::XMM(XMM::XMM14).to_index().0] = Some(fs.xmm[14][0]); + known_registers[X64Register::XMM(XMM::XMM15).to_index().0] = Some(fs.xmm[15][0]); FaultInfo { faulting_addr: si_addr, - ip: ss.rip as _, + ip: std::mem::transmute::<&mut u64, &'static Cell>(&mut ss.rip), known_registers, } } diff --git a/lib/runtime-core/src/global.rs b/lib/runtime-core/src/global.rs index 8d4212531..be9eaf6ae 100644 --- a/lib/runtime-core/src/global.rs +++ b/lib/runtime-core/src/global.rs @@ -1,14 +1,20 @@ +//! The global module contains the implementation data structures and helper functions used to +//! manipulate and access a wasm globals. use crate::{ export::Export, import::IsExport, types::{GlobalDescriptor, Type, Value}, vm, }; -use std::{cell::RefCell, fmt, rc::Rc}; +use std::{ + fmt, + sync::{Arc, Mutex}, +}; +/// Container with a descriptor and a reference to a global value. pub struct Global { desc: GlobalDescriptor, - storage: Rc>, + storage: Arc>, } impl Global { @@ -56,7 +62,7 @@ impl Global { Self { desc, - storage: Rc::new(RefCell::new(local_global)), + storage: Arc::new(Mutex::new(local_global)), } } @@ -83,7 +89,8 @@ impl Global { Value::V128(x) => x, }, }; - *self.storage.borrow_mut() = local_global; + let mut storage = self.storage.lock().unwrap(); + *storage = local_global; } else { panic!("Wrong type for setting this global") } @@ -94,7 +101,8 @@ impl Global { /// Get the value held by this global. pub fn get(&self) -> Value { - let data = self.storage.borrow().data; + let storage = self.storage.lock().unwrap(); + let data = storage.data; match self.desc.ty { Type::I32 => Value::I32(data as i32), @@ -105,8 +113,10 @@ impl Global { } } + // TODO: think about this and if this should now be unsafe pub(crate) fn vm_local_global(&mut self) -> *mut vm::LocalGlobal { - &mut *self.storage.borrow_mut() + let mut storage = self.storage.lock().unwrap(); + &mut *storage } } @@ -120,7 +130,7 @@ impl Clone for Global { fn clone(&self) -> Self { Self { desc: self.desc, - storage: Rc::clone(&self.storage), + storage: Arc::clone(&self.storage), } } } diff --git a/lib/runtime-core/src/import.rs b/lib/runtime-core/src/import.rs index fc1da43b7..284ff1725 100644 --- a/lib/runtime-core/src/import.rs +++ b/lib/runtime-core/src/import.rs @@ -1,19 +1,29 @@ +//! The import module contains the implementation data structures and helper functions used to +//! manipulate and access a wasm module's imports including memories, tables, globals, and +//! functions. use crate::export::Export; use std::collections::VecDeque; use std::collections::{hash_map::Entry, HashMap}; use std::{ - cell::{Ref, RefCell}, + borrow::{Borrow, BorrowMut}, ffi::c_void, - rc::Rc, + sync::{Arc, Mutex}, }; +/// This trait represents objects that act as a namespace for imports. For example, an `Instance` +/// or `ImportObject` could be considered namespaces that could provide imports to an instance. pub trait LikeNamespace { + /// Gets an export by name. fn get_export(&self, name: &str) -> Option; + /// Gets all exports in the namespace. fn get_exports(&self) -> Vec<(String, Export)>; + /// Maybe insert an `Export` by name into the namespace. fn maybe_insert(&mut self, name: &str, export: Export) -> Option<()>; } +/// A trait that represents `Export` values. pub trait IsExport { + /// Gets self as `Export`. fn to_export(&self) -> Export; } @@ -45,8 +55,11 @@ impl IsExport for Export { /// } /// ``` pub struct ImportObject { - map: Rc>>>, - pub(crate) state_creator: Option (*mut c_void, fn(*mut c_void))>>, + map: Arc>>>, + pub(crate) state_creator: + Option (*mut c_void, fn(*mut c_void)) + Send + Sync + 'static>>, + /// Allow missing functions to be generated and instantiation to continue when required + /// functions are not provided. pub allow_missing_functions: bool, } @@ -54,19 +67,20 @@ impl ImportObject { /// Create a new `ImportObject`. pub fn new() -> Self { Self { - map: Rc::new(RefCell::new(HashMap::new())), + map: Arc::new(Mutex::new(HashMap::new())), state_creator: None, allow_missing_functions: false, } } + /// Create a new `ImportObject` which generates data from the provided state creator. pub fn new_with_data(state_creator: F) -> Self where - F: Fn() -> (*mut c_void, fn(*mut c_void)) + 'static, + F: Fn() -> (*mut c_void, fn(*mut c_void)) + 'static + Send + Sync, { Self { - map: Rc::new(RefCell::new(HashMap::new())), - state_creator: Some(Rc::new(state_creator)), + map: Arc::new(Mutex::new(HashMap::new())), + state_creator: Some(Arc::new(state_creator)), allow_missing_functions: false, } } @@ -92,9 +106,10 @@ impl ImportObject { pub fn register(&mut self, name: S, namespace: N) -> Option> where S: Into, - N: LikeNamespace + 'static, + N: LikeNamespace + Send + 'static, { - let mut map = self.map.borrow_mut(); + let mut guard = self.map.lock().unwrap(); + let map = guard.borrow_mut(); match map.entry(name.into()) { Entry::Vacant(empty) => { @@ -105,19 +120,48 @@ impl ImportObject { } } - pub fn get_namespace(&self, namespace: &str) -> Option> { - let map_ref = self.map.borrow(); - + /// Apply a function on the namespace if it exists + /// If your function can fail, consider using `maybe_with_namespace` + pub fn with_namespace(&self, namespace: &str, f: Func) -> Option + where + Func: FnOnce(&(dyn LikeNamespace + Send)) -> InnerRet, + InnerRet: Sized, + { + let guard = self.map.lock().unwrap(); + let map_ref = guard.borrow(); if map_ref.contains_key(namespace) { - Some(Ref::map(map_ref, |map| &*map[namespace])) + Some(f(map_ref[namespace].as_ref())) } else { None } } + /// The same as `with_namespace` but takes a function that may fail + /// # Usage: + /// ``` + /// # use wasmer_runtime_core::import::{ImportObject, LikeNamespace}; + /// # use wasmer_runtime_core::export::Export; + /// fn get_export(imports: &ImportObject, namespace: &str, name: &str) -> Option { + /// imports.maybe_with_namespace(namespace, |ns| ns.get_export(name)) + /// } + /// ``` + pub fn maybe_with_namespace(&self, namespace: &str, f: Func) -> Option + where + Func: FnOnce(&(dyn LikeNamespace + Send)) -> Option, + InnerRet: Sized, + { + let guard = self.map.lock().unwrap(); + let map_ref = guard.borrow(); + map_ref + .get(namespace) + .map(|ns| ns.as_ref()) + .and_then(|ns| f(ns)) + } + + /// Create a clone ref of this namespace. pub fn clone_ref(&self) -> Self { Self { - map: Rc::clone(&self.map), + map: Arc::clone(&self.map), state_creator: self.state_creator.clone(), allow_missing_functions: false, } @@ -125,7 +169,9 @@ impl ImportObject { fn get_objects(&self) -> VecDeque<(String, String, Export)> { let mut out = VecDeque::new(); - for (name, ns) in self.map.borrow().iter() { + let guard = self.map.lock().unwrap(); + let map = guard.borrow(); + for (name, ns) in map.iter() { for (id, exp) in ns.get_exports() { out.push_back((name.clone(), id, exp)); } @@ -134,6 +180,7 @@ impl ImportObject { } } +/// Iterator for an `ImportObject`'s exports. pub struct ImportObjectIterator { elements: VecDeque<(String, String, Export)>, } @@ -158,7 +205,8 @@ impl IntoIterator for ImportObject { impl Extend<(String, String, Export)> for ImportObject { fn extend>(&mut self, iter: T) { - let mut map = self.map.borrow_mut(); + let mut guard = self.map.lock().unwrap(); + let map = guard.borrow_mut(); for (ns, id, exp) in iter.into_iter() { if let Some(like_ns) = map.get_mut(&ns) { like_ns.maybe_insert(&id, exp); @@ -171,25 +219,29 @@ impl Extend<(String, String, Export)> for ImportObject { } } +/// The top-level container for the two-level wasm imports pub struct Namespace { - map: HashMap>, + map: HashMap>, } impl Namespace { + /// Create a new empty `Namespace`. pub fn new() -> Self { Self { map: HashMap::new(), } } - pub fn insert(&mut self, name: S, export: E) -> Option> + /// Insert a new `Export` into the namespace with the given name. + pub fn insert(&mut self, name: S, export: E) -> Option> where S: Into, - E: IsExport + 'static, + E: IsExport + Send + 'static, { self.map.insert(name.into(), Box::new(export)) } + /// Returns true if the `Namespace` contains the given name. pub fn contains_key(&mut self, key: S) -> bool where S: Into, @@ -241,12 +293,14 @@ mod test { imports1.extend(imports2); - let cat_ns = imports1.get_namespace("cat").unwrap(); - assert!(cat_ns.get_export("small").is_some()); + let small_cat_export = + imports1.maybe_with_namespace("cat", |cat_ns| cat_ns.get_export("small")); + assert!(small_cat_export.is_some()); - let dog_ns = imports1.get_namespace("dog").unwrap(); - assert!(dog_ns.get_export("happy").is_some()); - assert!(dog_ns.get_export("small").is_some()); + let entries = imports1.maybe_with_namespace("dog", |dog_ns| { + Some((dog_ns.get_export("happy")?, dog_ns.get_export("small")?)) + }); + assert!(entries.is_some()); } #[test] @@ -264,14 +318,14 @@ mod test { }; imports1.extend(imports2); - let dog_ns = imports1.get_namespace("dog").unwrap(); + let happy_dog_entry = imports1 + .maybe_with_namespace("dog", |dog_ns| dog_ns.get_export("happy")) + .unwrap(); - assert!( - if let Export::Global(happy_dog_global) = dog_ns.get_export("happy").unwrap() { - happy_dog_global.get() == Value::I32(4) - } else { - false - } - ); + assert!(if let Export::Global(happy_dog_global) = happy_dog_entry { + happy_dog_global.get() == Value::I32(4) + } else { + false + }); } } diff --git a/lib/runtime-core/src/instance.rs b/lib/runtime-core/src/instance.rs index e6ba365f6..eb47c98e1 100644 --- a/lib/runtime-core/src/instance.rs +++ b/lib/runtime-core/src/instance.rs @@ -1,3 +1,5 @@ +//! The instance module contains the implementation data structures and helper functions used to +//! manipulate and access wasm instances. use crate::{ backend::RunnableModule, backing::{ImportBacking, LocalBacking}, @@ -16,7 +18,12 @@ use crate::{ vm::{self, InternalField}, }; use smallvec::{smallvec, SmallVec}; -use std::{mem, pin::Pin, ptr::NonNull, sync::Arc}; +use std::{ + mem, + pin::Pin, + ptr::NonNull, + sync::{Arc, Mutex}, +}; pub(crate) struct InstanceInner { #[allow(dead_code)] @@ -25,6 +32,9 @@ pub(crate) struct InstanceInner { pub(crate) vmctx: *mut vm::Ctx, } +// manually implemented because InstanceInner contains a raw pointer to Ctx +unsafe impl Send for InstanceInner {} + impl Drop for InstanceInner { fn drop(&mut self) { // Drop the vmctx. @@ -40,6 +50,7 @@ impl Drop for InstanceInner { /// /// [`ImportObject`]: struct.ImportObject.html pub struct Instance { + /// Reference to the module used to instantiate this instance. pub module: Arc, inner: Pin>, #[allow(dead_code)] @@ -102,9 +113,13 @@ impl Instance { let ctx_ptr = match start_index.local_or_import(&instance.module.info) { LocalOrImport::Local(_) => instance.inner.vmctx, - LocalOrImport::Import(imported_func_index) => { - instance.inner.import_backing.vm_functions[imported_func_index].vmctx + LocalOrImport::Import(imported_func_index) => unsafe { + instance.inner.import_backing.vm_functions[imported_func_index] + .func_ctx + .as_ref() } + .vmctx + .as_ptr(), }; let sig_index = *instance @@ -121,7 +136,7 @@ impl Instance { .expect("wasm trampoline"); let start_func: Func<(), (), Wasm> = - unsafe { Func::from_raw_parts(wasm_trampoline, func_ptr, ctx_ptr) }; + unsafe { Func::from_raw_parts(wasm_trampoline, func_ptr, None, ctx_ptr) }; start_func.call()?; } @@ -129,6 +144,7 @@ impl Instance { Ok(instance) } + /// Load an `Instance` using the given loader. pub fn load(&self, loader: T) -> ::std::result::Result { loader.load(&*self.module.runnable_module, &self.module.info, unsafe { &*self.inner.vmctx @@ -187,9 +203,13 @@ impl Instance { let ctx = match func_index.local_or_import(&self.module.info) { LocalOrImport::Local(_) => self.inner.vmctx, - LocalOrImport::Import(imported_func_index) => { - self.inner.import_backing.vm_functions[imported_func_index].vmctx + LocalOrImport::Import(imported_func_index) => unsafe { + self.inner.import_backing.vm_functions[imported_func_index] + .func_ctx + .as_ref() } + .vmctx + .as_ptr(), }; let func_wasm_inner = self @@ -198,20 +218,26 @@ impl Instance { .get_trampoline(&self.module.info, sig_index) .unwrap(); - let func_ptr = match func_index.local_or_import(&self.module.info) { - LocalOrImport::Local(local_func_index) => self - .module - .runnable_module - .get_func(&self.module.info, local_func_index) - .unwrap(), - LocalOrImport::Import(import_func_index) => NonNull::new( - self.inner.import_backing.vm_functions[import_func_index].func as *mut _, - ) - .unwrap(), + let (func_ptr, func_env) = match func_index.local_or_import(&self.module.info) { + LocalOrImport::Local(local_func_index) => ( + self.module + .runnable_module + .get_func(&self.module.info, local_func_index) + .unwrap(), + None, + ), + LocalOrImport::Import(import_func_index) => { + let imported_func = &self.inner.import_backing.vm_functions[import_func_index]; + + ( + NonNull::new(imported_func.func as *mut _).unwrap(), + unsafe { imported_func.func_ctx.as_ref() }.func_env, + ) + } }; let typed_func: Func = - unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, ctx) }; + unsafe { Func::from_raw_parts(func_wasm_inner, func_ptr, func_env, ctx) }; Ok(typed_func) } else { @@ -222,6 +248,7 @@ impl Instance { } } + /// Resolve a function by name. pub fn resolve_func(&self, name: &str) -> ResolveResult { let export_index = self.module @@ -373,10 +400,12 @@ impl Instance { Module::new(Arc::clone(&self.module)) } + /// Get the value of an internal field pub fn get_internal(&self, field: &InternalField) -> u64 { self.inner.backing.internals.0[field.index()] } + /// Set the value of an internal field. pub fn set_internal(&mut self, field: &InternalField, value: u64) { self.inner.backing.internals.0[field.index()] = value; } @@ -397,6 +426,7 @@ impl InstanceInner { ctx: match ctx { Context::Internal => Context::External(self.vmctx), ctx @ Context::External(_) => ctx, + ctx @ Context::ExternalWithEnv(_, _) => ctx, }, signature, } @@ -439,15 +469,16 @@ impl InstanceInner { ), LocalOrImport::Import(imported_func_index) => { let imported_func = &self.import_backing.vm_functions[imported_func_index]; + let func_ctx = unsafe { imported_func.func_ctx.as_ref() }; + ( imported_func.func as *const _, - Context::External(imported_func.vmctx), + Context::ExternalWithEnv(func_ctx.vmctx.as_ptr(), func_ctx.func_env), ) } }; let signature = SigRegistry.lookup_signature_ref(&module.info.signatures[sig_index]); - // let signature = &module.info.signatures[sig_index]; (unsafe { FuncPointer::new(func_ptr) }, ctx, signature) } @@ -517,6 +548,27 @@ impl LikeNamespace for Rc { } } +impl LikeNamespace for Arc> { + fn get_export(&self, name: &str) -> Option { + let instance = self.lock().unwrap(); + let export_index = instance.module.info.exports.get(name)?; + + Some( + instance + .inner + .get_export_from_index(&instance.module, export_index), + ) + } + + fn get_exports(&self) -> Vec<(String, Export)> { + unimplemented!("Use the exports method instead"); + } + + fn maybe_insert(&mut self, _name: &str, _export: Export) -> Option<()> { + None + } +} + #[must_use] fn call_func_with_index( info: &ModuleInfo, @@ -527,14 +579,50 @@ fn call_func_with_index( args: &[Value], rets: &mut Vec, ) -> CallResult<()> { - rets.clear(); - let sig_index = *info .func_assoc .get(func_index) .expect("broken invariant, incorrect func index"); let signature = &info.signatures[sig_index]; + + let func_ptr = match func_index.local_or_import(info) { + LocalOrImport::Local(local_func_index) => { + runnable.get_func(info, local_func_index).unwrap() + } + LocalOrImport::Import(import_func_index) => { + NonNull::new(import_backing.vm_functions[import_func_index].func as *mut _).unwrap() + } + }; + + let ctx_ptr = match func_index.local_or_import(info) { + LocalOrImport::Local(_) => local_ctx, + LocalOrImport::Import(imported_func_index) => unsafe { + import_backing.vm_functions[imported_func_index] + .func_ctx + .as_ref() + } + .vmctx + .as_ptr(), + }; + + let wasm = runnable + .get_trampoline(info, sig_index) + .expect("wasm trampoline"); + + call_func_with_index_inner(ctx_ptr, func_ptr, signature, wasm, args, rets) +} + +pub(crate) fn call_func_with_index_inner( + ctx_ptr: *mut vm::Ctx, + func_ptr: NonNull, + signature: &FuncSig, + wasm: Wasm, + args: &[Value], + rets: &mut Vec, +) -> CallResult<()> { + rets.clear(); + let num_results = signature.returns().len(); let num_results = num_results + signature @@ -551,22 +639,6 @@ fn call_func_with_index( })? } - let func_ptr = match func_index.local_or_import(info) { - LocalOrImport::Local(local_func_index) => { - runnable.get_func(info, local_func_index).unwrap() - } - LocalOrImport::Import(import_func_index) => { - NonNull::new(import_backing.vm_functions[import_func_index].func as *mut _).unwrap() - } - }; - - let ctx_ptr = match func_index.local_or_import(info) { - LocalOrImport::Local(_) => local_ctx, - LocalOrImport::Import(imported_func_index) => { - import_backing.vm_functions[imported_func_index].vmctx - } - }; - let mut raw_args: SmallVec<[u64; 8]> = SmallVec::new(); for v in args { match v { @@ -598,9 +670,7 @@ fn call_func_with_index( trampoline, invoke, invoke_env, - } = runnable - .get_trampoline(info, sig_index) - .expect("wasm trampoline"); + } = wasm; let run_wasm = |result_space: *mut u64| unsafe { let mut trap_info = WasmTrapInfo::Unknown; @@ -731,10 +801,12 @@ impl<'a> DynFunc<'a> { Ok(results) } + /// Gets the signature of this `Dynfunc`. pub fn signature(&self) -> &FuncSig { &*self.signature } + /// Gets a const pointer to the function represent by this `DynFunc`. pub fn raw(&self) -> *const vm::Func { match self.func_index.local_or_import(&self.module.info) { LocalOrImport::Local(local_func_index) => self @@ -749,3 +821,15 @@ impl<'a> DynFunc<'a> { } } } + +#[cfg(test)] +mod test { + use super::*; + + fn is_send() {} + + #[test] + fn test_instance_is_send() { + is_send::(); + } +} diff --git a/lib/runtime-core/src/lib.rs b/lib/runtime-core/src/lib.rs index c286cd67e..56f98bc71 100644 --- a/lib/runtime-core/src/lib.rs +++ b/lib/runtime-core/src/lib.rs @@ -1,5 +1,20 @@ +//! Wasmer Runtime Core Library +//! +//! The runtime core library provides common data structures which are shared by compiler backends +//! to implement a Web Assembly runtime. +//! +//! The runtime core also provides an API for users who use wasmer as an embedded wasm runtime which +//! allows operations like compiling, instantiating, providing imports, access exports, memories, +//! and tables for example. +//! +//! The runtime core library is recommended to be used by only power users who wish to customize the +//! wasmer runtime. Most wasmer users should prefer the API which is re-exported by the wasmer +//! runtime library which provides common defaults and a friendly API. +//! + #![deny( dead_code, + missing_docs, nonstandard_style, unused_imports, unused_mut, @@ -8,10 +23,8 @@ unreachable_patterns )] #![cfg_attr(nightly, feature(unwind_attributes))] - -#[cfg(test)] -#[macro_use] -extern crate field_offset; +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] #[macro_use] extern crate serde_derive; @@ -51,9 +64,11 @@ pub mod vm; pub mod vmcalls; #[cfg(all(unix, target_arch = "x86_64"))] pub use trampoline_x64 as trampoline; -#[cfg(all(unix, target_arch = "x86_64"))] +#[cfg(unix)] pub mod fault; pub mod state; +#[cfg(feature = "managed")] +pub mod tiering; use self::error::CompileResult; #[doc(inline)] @@ -73,6 +88,9 @@ pub use wasmparser; use self::cache::{Artifact, Error as CacheError}; pub mod prelude { + //! The prelude module is a helper module used to bring commonly used runtime core imports into + //! scope. + pub use crate::import::{ImportObject, Namespace}; pub use crate::types::{ FuncIndex, GlobalIndex, ImportedFuncIndex, ImportedGlobalIndex, ImportedMemoryIndex, @@ -142,7 +160,6 @@ pub fn validate_and_report_errors_with_features( enable_reference_types: false, enable_threads: features.threads, }, - mutable_global_imports: true, }; let mut parser = wasmparser::ValidatingParser::new(wasm, Some(config)); loop { @@ -155,6 +172,7 @@ pub fn validate_and_report_errors_with_features( } } +/// Creates a new module from the given cache `Artifact` for the specified compiler backend pub unsafe fn load_cache_with( cache: Artifact, compiler: &dyn backend::Compiler, diff --git a/lib/runtime-core/src/loader.rs b/lib/runtime-core/src/loader.rs index 9a790c6a4..84d2bbbfb 100644 --- a/lib/runtime-core/src/loader.rs +++ b/lib/runtime-core/src/loader.rs @@ -1,3 +1,4 @@ +//! The loader module functions are used to load an instance. use crate::{backend::RunnableModule, module::ModuleInfo, types::Type, types::Value, vm::Ctx}; #[cfg(unix)] use libc::{mmap, mprotect, munmap, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_READ, PROT_WRITE}; @@ -6,10 +7,14 @@ use std::{ ops::{Deref, DerefMut}, }; +/// The loader trait represents the functions used to load an instance. pub trait Loader { + /// The type of `Instance` for the loader. type Instance: Instance; + /// The error type returned by the loader. type Error: Debug; + /// Loads the given module and context into an instance. fn load( &self, rm: &dyn RunnableModule, @@ -18,18 +23,23 @@ pub trait Loader { ) -> Result; } +/// This trait represents an instance used by the loader. pub trait Instance { + /// The error type returned by this instance. type Error: Debug; + /// Call a function by id with the given args. fn call(&mut self, id: usize, args: &[Value]) -> Result; + /// Read memory at the given offset and length. fn read_memory(&mut self, _offset: u32, _len: u32) -> Result, Self::Error> { - unimplemented!() + unimplemented!("Instance::read_memory") } - + /// Write memory at the given offset and length. fn write_memory(&mut self, _offset: u32, _len: u32, _buf: &[u8]) -> Result<(), Self::Error> { - unimplemented!() + unimplemented!("Instance::write_memory") } } +/// A local implementation for `Loader`. pub struct LocalLoader; impl Loader for LocalLoader { @@ -54,6 +64,7 @@ impl Loader for LocalLoader { } } +/// A local instance. pub struct LocalInstance { code: CodeMemory, offsets: Vec, @@ -111,32 +122,51 @@ impl Instance for LocalInstance { } } +/// A pointer to code in memory. pub struct CodeMemory { ptr: *mut u8, size: usize, } +unsafe impl Send for CodeMemory {} +unsafe impl Sync for CodeMemory {} + #[cfg(not(unix))] impl CodeMemory { + /// Creates a new code memory with the given size. pub fn new(_size: usize) -> CodeMemory { - unimplemented!(); + unimplemented!("CodeMemory::new"); } - pub fn make_executable(&mut self) { - unimplemented!(); + /// Makes this code memory executable. + pub fn make_executable(&self) { + unimplemented!("CodeMemory::make_executable"); + } + + /// Makes this code memory writable. + pub fn make_writable(&self) { + unimplemented!("CodeMemory::make_writable"); } } #[cfg(unix)] impl CodeMemory { + /// Creates a new code memory with the given size. pub fn new(size: usize) -> CodeMemory { + if size == 0 { + return CodeMemory { + ptr: std::ptr::null_mut(), + size: 0, + }; + } + fn round_up_to_page_size(size: usize) -> usize { (size + (4096 - 1)) & !(4096 - 1) } let size = round_up_to_page_size(size); let ptr = unsafe { mmap( - ::std::ptr::null_mut(), + std::ptr::null_mut(), size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, @@ -153,11 +183,19 @@ impl CodeMemory { } } - pub fn make_executable(&mut self) { + /// Makes this code memory executable. + pub fn make_executable(&self) { if unsafe { mprotect(self.ptr as _, self.size, PROT_READ | PROT_EXEC) } != 0 { panic!("cannot set code memory to executable"); } } + + /// Makes this code memory writable. + pub fn make_writable(&self) { + if unsafe { mprotect(self.ptr as _, self.size, PROT_READ | PROT_WRITE) } != 0 { + panic!("cannot set code memory to writable"); + } + } } #[cfg(unix)] @@ -172,12 +210,12 @@ impl Drop for CodeMemory { impl Deref for CodeMemory { type Target = [u8]; fn deref(&self) -> &[u8] { - unsafe { ::std::slice::from_raw_parts(self.ptr, self.size) } + unsafe { std::slice::from_raw_parts(self.ptr, self.size) } } } impl DerefMut for CodeMemory { fn deref_mut(&mut self) -> &mut [u8] { - unsafe { ::std::slice::from_raw_parts_mut(self.ptr, self.size) } + unsafe { std::slice::from_raw_parts_mut(self.ptr, self.size) } } } diff --git a/lib/runtime-core/src/macros.rs b/lib/runtime-core/src/macros.rs index 6dbb93ca0..71e8c2e2f 100644 --- a/lib/runtime-core/src/macros.rs +++ b/lib/runtime-core/src/macros.rs @@ -1,3 +1,5 @@ +/// Prints a log message with args, similar to println, when the debug feature is enabled. +/// If the debug feature is disabled, arguments are not evaluated or printed. #[macro_export] #[cfg(feature = "debug")] macro_rules! debug { @@ -11,6 +13,8 @@ macro_rules! debug { }, line!(), $($arg)*)); } +/// Prints a log message with args, similar to println, when the debug feature is enabled. +/// If the debug feature is disabled, arguments are not evaluated or printed. #[macro_export] #[cfg(not(feature = "debug"))] macro_rules! debug { @@ -18,6 +22,8 @@ macro_rules! debug { ($fmt:expr, $($arg:tt)*) => {}; } +/// Prints a log message with args, similar to println, when the trace feature is enabled. +/// If the trace feature is disabled, arguments are not evaluated or printed. #[macro_export] #[cfg(feature = "trace")] macro_rules! trace { @@ -29,6 +35,8 @@ macro_rules! trace { } } +/// Prints a log message with args, similar to println, when the trace feature is enabled. +/// If the trace feature is disabled, arguments are not evaluated or printed. #[macro_export] #[cfg(not(feature = "trace"))] macro_rules! trace { @@ -36,9 +44,48 @@ macro_rules! trace { ($fmt:expr, $($arg:tt)*) => {}; } +/// Helper macro to create a new `Func` object using the provided function pointer. +/// +/// # Usage +/// +/// Function pointers or closures are supported. Closures can capture +/// their environment (with `move). The first parameter is likely to +/// be of kind `vm::Ctx`, though it can be optional. +/// +/// ``` +/// # use wasmer_runtime_core::{imports, func}; +/// # use wasmer_runtime_core::vm; +/// +/// // A function that has access to `vm::Ctx`. +/// fn func_with_vmctx(_: &mut vm::Ctx, n: i32) -> i32 { +/// n +/// } +/// +/// // A function that cannot access `vm::Ctx`. +/// fn func(n: i32) -> i32 { +/// n +/// } +/// +/// let i = 7; +/// +/// let import_object = imports! { +/// "env" => { +/// "foo" => func!(func_with_vmctx), +/// "bar" => func!(func), +/// // A closure with a captured environment, and an access to `vm::Ctx`. +/// "baz" => func!(move |_: &mut vm::Ctx, n: i32| -> i32 { +/// n + i +/// }), +/// // A closure without a captured environment, and no access to `vm::Ctx`. +/// "qux" => func!(|n: i32| -> i32 { +/// n +/// }), +/// }, +/// }; +/// ``` #[macro_export] macro_rules! func { - ($func:path) => {{ + ($func:expr) => {{ $crate::Func::new($func) }}; } @@ -47,12 +94,12 @@ macro_rules! func { /// /// [`ImportObject`]: struct.ImportObject.html /// -/// # Note: +/// # Note /// The `import` macro currently only supports /// importing functions. /// /// -/// # Usage: +/// # Usage /// ``` /// # use wasmer_runtime_core::{imports, func}; /// # use wasmer_runtime_core::vm::Ctx; diff --git a/lib/runtime-core/src/memory/dynamic.rs b/lib/runtime-core/src/memory/dynamic.rs index 332a37acb..885c47dbe 100644 --- a/lib/runtime-core/src/memory/dynamic.rs +++ b/lib/runtime-core/src/memory/dynamic.rs @@ -62,10 +62,12 @@ impl DynamicMemory { Ok(storage) } + /// The size of this memory in `Pages`. pub fn size(&self) -> Pages { self.current } + /// Try to grow self by the given number of delta pages. pub fn grow(&mut self, delta: Pages, local: &mut vm::LocalMemory) -> Result { if delta == Pages(0) { return Ok(self.current); @@ -104,10 +106,12 @@ impl DynamicMemory { Ok(old_pages) } + /// Get this memory represented as a slice of bytes. pub fn as_slice(&self) -> &[u8] { unsafe { &self.memory.as_slice()[0..self.current.bytes().0] } } + /// Get this memory represented as a mutable slice of bytes pub fn as_slice_mut(&mut self) -> &mut [u8] { unsafe { &mut self.memory.as_slice_mut()[0..self.current.bytes().0] } } diff --git a/lib/runtime-core/src/memory/mod.rs b/lib/runtime-core/src/memory/mod.rs index a7510b818..75a02e227 100644 --- a/lib/runtime-core/src/memory/mod.rs +++ b/lib/runtime-core/src/memory/mod.rs @@ -1,3 +1,5 @@ +//! The memory module contains the implementation data structures and helper functions used to +//! manipulate and access wasm memory. use crate::{ error::{CreationError, GrowError}, export::Export, @@ -8,12 +10,9 @@ use crate::{ units::Pages, vm, }; -use std::{ - cell::{Cell, RefCell}, - fmt, mem, - rc::Rc, - sync::Arc, -}; +use std::{cell::Cell, fmt, mem, sync::Arc}; + +use std::sync::Mutex as StdMutex; pub use self::dynamic::DynamicMemory; pub use self::static_::StaticMemory; @@ -53,16 +52,12 @@ impl Memory { /// # use wasmer_runtime_core::memory::Memory; /// # use wasmer_runtime_core::error::Result; /// # use wasmer_runtime_core::units::Pages; - /// # fn create_memory() -> Result<()> { - /// let descriptor = MemoryDescriptor { - /// minimum: Pages(10), - /// maximum: None, - /// shared: false, - /// }; + /// fn create_memory() -> Result<()> { + /// let descriptor = MemoryDescriptor::new(Pages(10), None, false).unwrap(); /// - /// let memory = Memory::new(descriptor)?; - /// # Ok(()) - /// # } + /// let memory = Memory::new(descriptor)?; + /// Ok(()) + /// } /// ``` pub fn new(desc: MemoryDescriptor) -> Result { if let Some(max) = desc.maximum { @@ -131,11 +126,11 @@ impl Memory { /// /// ``` /// # use wasmer_runtime_core::memory::{Memory, MemoryView}; - /// # use std::sync::atomic::Ordering; + /// # use std::{cell::Cell, sync::atomic::Ordering}; /// # fn view_memory(memory: Memory) { /// // Without synchronization. /// let view: MemoryView = memory.view(); - /// for byte in view[0x1000 .. 0x1010].iter().map(|cell| cell.get()) { + /// for byte in view[0x1000 .. 0x1010].iter().map(Cell::get) { /// println!("byte: {}", byte); /// } /// @@ -177,10 +172,14 @@ impl fmt::Debug for Memory { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +/// A kind a memory. +#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum MemoryType { + /// A dynamic memory. Dynamic, + /// A static memory. Static, + /// A shared static memory. SharedStatic, } @@ -207,16 +206,22 @@ enum UnsharedMemoryStorage { Static(Box), } +/// A reference to an unshared memory. pub struct UnsharedMemory { - internal: Rc, + internal: Arc, } struct UnsharedMemoryInternal { - storage: RefCell, + storage: StdMutex, local: Cell, } +// Manually implemented because UnsharedMemoryInternal uses `Cell` and is used in an Arc; +// this is safe because the lock for storage can be used to protect (seems like a weak reason: PLEASE REVIEW!) +unsafe impl Sync for UnsharedMemoryInternal {} + impl UnsharedMemory { + /// Create a new `UnsharedMemory` from the given memory descriptor. pub fn new(desc: MemoryDescriptor) -> Result { let mut local = vm::LocalMemory { base: std::ptr::null_mut(), @@ -231,19 +236,24 @@ impl UnsharedMemory { MemoryType::Static => { UnsharedMemoryStorage::Static(StaticMemory::new(desc, &mut local)?) } - MemoryType::SharedStatic => panic!("attempting to create shared unshared memory"), + MemoryType::SharedStatic => { + return Err(CreationError::InvalidDescriptor( + "attempting to create shared unshared memory".to_string(), + )); + } }; Ok(Self { - internal: Rc::new(UnsharedMemoryInternal { - storage: RefCell::new(storage), + internal: Arc::new(UnsharedMemoryInternal { + storage: StdMutex::new(storage), local: Cell::new(local), }), }) } + /// Try to grow this memory by the given number of delta pages. pub fn grow(&self, delta: Pages) -> Result { - let mut storage = self.internal.storage.borrow_mut(); + let mut storage = self.internal.storage.lock().unwrap(); let mut local = self.internal.local.get(); @@ -259,8 +269,9 @@ impl UnsharedMemory { pages } + /// Size of this memory in pages. pub fn size(&self) -> Pages { - let storage = self.internal.storage.borrow(); + let storage = self.internal.storage.lock().unwrap(); match &*storage { UnsharedMemoryStorage::Dynamic(ref dynamic_memory) => dynamic_memory.size(), @@ -276,21 +287,27 @@ impl UnsharedMemory { impl Clone for UnsharedMemory { fn clone(&self) -> Self { UnsharedMemory { - internal: Rc::clone(&self.internal), + internal: Arc::clone(&self.internal), } } } +/// A reference to a shared memory. pub struct SharedMemory { internal: Arc, } +/// Data structure for a shared internal memory. pub struct SharedMemoryInternal { - memory: RefCell>, + memory: StdMutex>, local: Cell, lock: Mutex<()>, } +// Manually implemented because SharedMemoryInternal uses `Cell` and is used in Arc; +// this is safe because of `lock`; accesing `local` without locking `lock` is not safe (Maybe we could put the lock on Local then?) +unsafe impl Sync for SharedMemoryInternal {} + impl SharedMemory { fn new(desc: MemoryDescriptor) -> Result { let mut local = vm::LocalMemory { @@ -303,25 +320,31 @@ impl SharedMemory { Ok(Self { internal: Arc::new(SharedMemoryInternal { - memory: RefCell::new(memory), + memory: StdMutex::new(memory), local: Cell::new(local), lock: Mutex::new(()), }), }) } + /// Try to grow this memory by the given number of delta pages. pub fn grow(&self, delta: Pages) -> Result { let _guard = self.internal.lock.lock(); let mut local = self.internal.local.get(); - let pages = self.internal.memory.borrow_mut().grow(delta, &mut local); + let mut memory = self.internal.memory.lock().unwrap(); + let pages = memory.grow(delta, &mut local); pages } + /// Size of this memory in pages. pub fn size(&self) -> Pages { let _guard = self.internal.lock.lock(); - self.internal.memory.borrow_mut().size() + let memory = self.internal.memory.lock().unwrap(); + memory.size() } + /// Gets a mutable pointer to the `LocalMemory`. + // This function is scary, because the mutex is not locked here pub(crate) fn vm_local_memory(&self) -> *mut vm::LocalMemory { self.internal.local.as_ptr() } @@ -342,26 +365,17 @@ mod memory_tests { #[test] fn test_initial_memory_size() { - let unshared_memory = Memory::new(MemoryDescriptor { - minimum: Pages(10), - maximum: Some(Pages(20)), - shared: false, - }) - .unwrap(); + let memory_desc = MemoryDescriptor::new(Pages(10), Some(Pages(20)), false).unwrap(); + let unshared_memory = Memory::new(memory_desc).unwrap(); assert_eq!(unshared_memory.size(), Pages(10)); } #[test] fn test_invalid_descriptor_returns_error() { - let result = Memory::new(MemoryDescriptor { - minimum: Pages(10), - maximum: None, - shared: true, - }); + let memory_desc = MemoryDescriptor::new(Pages(10), None, true); assert!( - result.is_err(), + memory_desc.is_err(), "Max number of pages is required for shared memory" ) } - } diff --git a/lib/runtime-core/src/memory/ptr.rs b/lib/runtime-core/src/memory/ptr.rs index 0a9454acc..5e31627c2 100644 --- a/lib/runtime-core/src/memory/ptr.rs +++ b/lib/runtime-core/src/memory/ptr.rs @@ -12,9 +12,12 @@ use crate::{ }; use std::{cell::Cell, fmt, marker::PhantomData, mem}; +/// Array. pub struct Array; +/// Item. pub struct Item; +/// A pointer to a Wasm item. #[repr(transparent)] pub struct WasmPtr { offset: u32, @@ -22,6 +25,7 @@ pub struct WasmPtr { } impl WasmPtr { + /// Create a new `WasmPtr` at the given offset. #[inline] pub fn new(offset: u32) -> Self { Self { @@ -30,6 +34,7 @@ impl WasmPtr { } } + /// Get the offset for this `WasmPtr`. #[inline] pub fn offset(self) -> u32 { self.offset @@ -44,11 +49,10 @@ fn align_pointer(ptr: usize, align: usize) -> usize { } impl WasmPtr { + /// Dereference this `WasmPtr`. #[inline] pub fn deref<'a>(self, memory: &'a Memory) -> Option<&'a Cell> { - if self.offset == 0 - || (self.offset as usize) + mem::size_of::() >= memory.size().bytes().0 - { + if (self.offset as usize) + mem::size_of::() >= memory.size().bytes().0 { return None; } unsafe { @@ -60,11 +64,10 @@ impl WasmPtr { } } + /// Mutable dereference this `WasmPtr`. #[inline] pub unsafe fn deref_mut<'a>(self, memory: &'a Memory) -> Option<&'a mut Cell> { - if self.offset == 0 - || (self.offset as usize) + mem::size_of::() >= memory.size().bytes().0 - { + if (self.offset as usize) + mem::size_of::() >= memory.size().bytes().0 { return None; } let cell_ptr = align_pointer( @@ -76,6 +79,7 @@ impl WasmPtr { } impl WasmPtr { + /// Dereference this `WasmPtr`. #[inline] pub fn deref<'a>(self, memory: &'a Memory, index: u32, length: u32) -> Option<&'a [Cell]> { // gets the size of the item in the array with padding added such that @@ -83,9 +87,7 @@ impl WasmPtr { let item_size = mem::size_of::() + (mem::size_of::() % mem::align_of::()); let slice_full_len = index as usize + length as usize; - if self.offset == 0 - || (self.offset as usize) + (item_size * slice_full_len) >= memory.size().bytes().0 - { + if (self.offset as usize) + (item_size * slice_full_len) >= memory.size().bytes().0 { return None; } @@ -100,6 +102,7 @@ impl WasmPtr { } } + /// Mutable dereference this `WasmPtr`. #[inline] pub unsafe fn deref_mut<'a>( self, @@ -112,9 +115,7 @@ impl WasmPtr { let item_size = mem::size_of::() + (mem::size_of::() % mem::align_of::()); let slice_full_len = index as usize + length as usize; - if self.offset == 0 - || (self.offset as usize) + (item_size * slice_full_len) >= memory.size().bytes().0 - { + if (self.offset as usize) + (item_size * slice_full_len) >= memory.size().bytes().0 { return None; } @@ -127,6 +128,7 @@ impl WasmPtr { Some(cell_ptrs) } + /// Get a UTF-8 string representation of this `WasmPtr` with the given length. pub fn get_utf8_string<'a>(self, memory: &'a Memory, str_len: u32) -> Option<&'a str> { if self.offset as usize + str_len as usize > memory.size().bytes().0 { return None; diff --git a/lib/runtime-core/src/memory/static_.rs b/lib/runtime-core/src/memory/static_.rs index 957e53eaf..c6355b2a2 100644 --- a/lib/runtime-core/src/memory/static_.rs +++ b/lib/runtime-core/src/memory/static_.rs @@ -56,10 +56,12 @@ impl StaticMemory { Ok(storage) } + /// The size of this memory in `Pages`. pub fn size(&self) -> Pages { self.current } + /// Try to grow this memory by the given number of delta pages. pub fn grow(&mut self, delta: Pages, local: &mut vm::LocalMemory) -> Result { if delta == Pages(0) { return Ok(self.current); @@ -94,10 +96,12 @@ impl StaticMemory { Ok(old_pages) } + /// Get this memory represented as a slice of bytes. pub fn as_slice(&self) -> &[u8] { unsafe { &self.memory.as_slice()[0..self.current.bytes().0] } } + /// Get this memory represented as a mutable slice of bytes. pub fn as_slice_mut(&mut self) -> &mut [u8] { unsafe { &mut self.memory.as_slice_mut()[0..self.current.bytes().0] } } diff --git a/lib/runtime-core/src/memory/view.rs b/lib/runtime-core/src/memory/view.rs index 4dbaa5bd5..762e1a614 100644 --- a/lib/runtime-core/src/memory/view.rs +++ b/lib/runtime-core/src/memory/view.rs @@ -39,12 +39,16 @@ impl Atomic for f64 { type Output = AtomicU64; } +/// A trait that represants an atomic type. pub trait Atomicity {} +/// Atomically. pub struct Atomically; impl Atomicity for Atomically {} +/// Non-atomically. pub struct NonAtomically; impl Atomicity for NonAtomically {} +/// A view into a memory. pub struct MemoryView<'a, T: 'a, A = NonAtomically> { ptr: *mut T, length: usize, @@ -65,6 +69,7 @@ where } impl<'a, T: Atomic> MemoryView<'a, T> { + /// Get atomic access to a memory view. pub fn atomically(&self) -> MemoryView<'a, T::Output, Atomically> { MemoryView { ptr: self.ptr as *mut T::Output, diff --git a/lib/runtime-core/src/module.rs b/lib/runtime-core/src/module.rs index 6cc02343f..eb87b85d3 100644 --- a/lib/runtime-core/src/module.rs +++ b/lib/runtime-core/src/module.rs @@ -1,3 +1,5 @@ +//! The module module contains the implementation data structures and helper functions used to +//! manipulate and access wasm modules. use crate::{ backend::{Backend, RunnableModule}, cache::{Artifact, Error as CacheError}, @@ -27,40 +29,59 @@ pub struct ModuleInner { pub info: ModuleInfo, } +/// Container for module data including memories, globals, tables, imports, and exports. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ModuleInfo { - // This are strictly local and the typsystem ensures that. + /// Map of memory index to memory descriptors. + // This are strictly local and the typesystem ensures that. pub memories: Map, + /// Map of global index to global descriptors. pub globals: Map, + /// Map of table index to table descriptors. pub tables: Map, + /// Map of imported function index to import name. // These are strictly imported and the typesystem ensures that. pub imported_functions: Map, + /// Map of imported memory index to import name and memory descriptor. pub imported_memories: Map, + /// Map of imported table index to import name and table descriptor. pub imported_tables: Map, + /// Map of imported global index to import name and global descriptor. pub imported_globals: Map, + /// Map of string to export index. pub exports: IndexMap, + /// Vector of data initializers. pub data_initializers: Vec, + /// Vector of table initializers. pub elem_initializers: Vec, + /// Index of optional start function. pub start_func: Option, + /// Map function index to signature index. pub func_assoc: Map, + /// Map signature index to function signature. pub signatures: Map, + /// Backend. pub backend: Backend, + /// Table of namespace indexes. pub namespace_table: StringTable, + /// Table of name indexes. pub name_table: StringTable, - /// Symbol information from emscripten + /// Symbol information from emscripten. pub em_symbol_map: Option>, + /// Custom sections. pub custom_sections: HashMap>, } impl ModuleInfo { + /// Creates custom section info from the given wasm file. pub fn import_custom_sections(&mut self, wasm: &[u8]) -> crate::error::ParseResult<()> { let mut parser = wasmparser::ModuleReader::new(wasm)?; while !parser.eof() { @@ -120,6 +141,7 @@ impl Module { Instance::new(Arc::clone(&self.inner), import_object) } + /// Create a cache artifact from this module. pub fn cache(&self) -> Result { let (backend_metadata, code) = self.inner.cache_gen.generate_cache()?; Ok(Artifact::from_parts( @@ -129,6 +151,7 @@ impl Module { )) } + /// Get the module data for this module. pub fn info(&self) -> &ModuleInfo { &self.inner.info } @@ -151,11 +174,16 @@ pub struct ImportName { pub name_index: NameIndex, } +/// Kinds of export indexes. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum ExportIndex { + /// Function export index. Func(FuncIndex), + /// Memory export index. Memory(MemoryIndex), + /// Global export index. Global(GlobalIndex), + /// Table export index. Table(TableIndex), } @@ -182,6 +210,7 @@ pub struct TableInitializer { pub elements: Vec, } +/// String table builder. pub struct StringTableBuilder { map: IndexMap, buffer: String, @@ -189,6 +218,7 @@ pub struct StringTableBuilder { } impl StringTableBuilder { + /// Creates a new `StringTableBuilder`. pub fn new() -> Self { Self { map: IndexMap::new(), @@ -197,6 +227,7 @@ impl StringTableBuilder { } } + /// Register a new string into table. pub fn register(&mut self, s: S) -> K where S: Into + AsRef, @@ -219,6 +250,7 @@ impl StringTableBuilder { } } + /// Finish building the `StringTable`. pub fn finish(self) -> StringTable { let table = self .map @@ -233,6 +265,7 @@ impl StringTableBuilder { } } +/// A map of index to string. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct StringTable { table: Map, @@ -240,6 +273,7 @@ pub struct StringTable { } impl StringTable { + /// Creates a `StringTable`. pub fn new() -> Self { Self { table: Map::new(), @@ -247,6 +281,7 @@ impl StringTable { } } + /// Gets a reference to a string at the given index. pub fn get(&self, index: K) -> &str { let (offset, length) = self.table[index]; let offset = offset as usize; @@ -256,6 +291,7 @@ impl StringTable { } } +/// Namespace index. #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct NamespaceIndex(u32); @@ -271,6 +307,7 @@ impl TypedIndex for NamespaceIndex { } } +/// Name index. #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct NameIndex(u32); diff --git a/lib/runtime-core/src/parse.rs b/lib/runtime-core/src/parse.rs index 259a628dd..ab6c79cbc 100644 --- a/lib/runtime-core/src/parse.rs +++ b/lib/runtime-core/src/parse.rs @@ -1,3 +1,6 @@ +//! The parse module contains common data structures and functions using to parse wasm files into +//! runtime data structures. + use crate::codegen::*; use crate::{ backend::{Backend, CompilerConfig, RunnableModule}, @@ -22,9 +25,12 @@ use wasmparser::{ WasmDecoder, }; +/// Kind of load error. #[derive(Debug)] pub enum LoadError { + /// Parse error. Parse(BinaryReaderError), + /// Code generation error. Codegen(String), } @@ -42,6 +48,8 @@ impl From for LoadError { } } +/// Read wasm binary into module data using the given backend, module code generator, middlewares, +/// and compiler configuration. pub fn read_module< MCG: ModuleCodeGenerator, FCG: FunctionCodeGenerator, @@ -136,11 +144,13 @@ pub fn read_module< .push((import_name, table_desc)); } ImportSectionEntryType::Memory(memory_ty) => { - let mem_desc = MemoryDescriptor { - minimum: Pages(memory_ty.limits.initial), - maximum: memory_ty.limits.maximum.map(|max| Pages(max)), - shared: memory_ty.shared, - }; + let mem_desc = MemoryDescriptor::new( + Pages(memory_ty.limits.initial), + memory_ty.limits.maximum.map(|max| Pages(max)), + memory_ty.shared, + ) + .map_err(|x| LoadError::Codegen(format!("{:?}", x)))?; + info.write() .unwrap() .imported_memories @@ -172,11 +182,12 @@ pub fn read_module< info.write().unwrap().tables.push(table_desc); } ParserState::MemorySectionEntry(memory_ty) => { - let mem_desc = MemoryDescriptor { - minimum: Pages(memory_ty.limits.initial), - maximum: memory_ty.limits.maximum.map(|max| Pages(max)), - shared: memory_ty.shared, - }; + let mem_desc = MemoryDescriptor::new( + Pages(memory_ty.limits.initial), + memory_ty.limits.maximum.map(|max| Pages(max)), + memory_ty.shared, + ) + .map_err(|x| LoadError::Codegen(format!("{:?}", x)))?; info.write().unwrap().memories.push(mem_desc); } @@ -391,17 +402,24 @@ pub fn read_module< Ok(info) } +/// Convert given `WpType` to `Type`. pub fn wp_type_to_type(ty: WpType) -> Result { - Ok(match ty { - WpType::I32 => Type::I32, - WpType::I64 => Type::I64, - WpType::F32 => Type::F32, - WpType::F64 => Type::F64, - WpType::V128 => Type::V128, - _ => panic!("broken invariant, invalid type"), - }) + match ty { + WpType::I32 => Ok(Type::I32), + WpType::I64 => Ok(Type::I64), + WpType::F32 => Ok(Type::F32), + WpType::F64 => Ok(Type::F64), + WpType::V128 => Ok(Type::V128), + _ => { + return Err(BinaryReaderError { + message: "broken invariant, invalid type", + offset: -1isize as usize, + }); + } + } } +/// Convert given `Type` to `WpType`. pub fn type_to_wp_type(ty: Type) -> WpType { match ty { Type::I32 => WpType::I32, diff --git a/lib/runtime-core/src/state.rs b/lib/runtime-core/src/state.rs index 51bc3066e..a5a0a0036 100644 --- a/lib/runtime-core/src/state.rs +++ b/lib/runtime-core/src/state.rs @@ -1,134 +1,200 @@ +//! The state module is used to track state of a running web assembly instances so that +//! state could read or updated at runtime. Use cases include generating stack traces, switching +//! generated code from one tier to another, or serializing state of a running instace. + +use crate::backend::Backend; use std::collections::BTreeMap; use std::ops::Bound::{Included, Unbounded}; -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +/// An index to a register +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct RegisterIndex(pub usize); -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +/// A kind of wasm or constant value +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum WasmAbstractValue { + /// A wasm runtime value Runtime, + /// A wasm constant value Const(u64), } -#[derive(Clone, Debug)] +/// A container for the state of a running wasm instance. +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MachineState { + /// Stack values. pub stack_values: Vec, + /// Register values. pub register_values: Vec, - + /// Previous frame. + pub prev_frame: BTreeMap, + /// Wasm stack. pub wasm_stack: Vec, + /// Private depth of the wasm stack. pub wasm_stack_private_depth: usize, - + /// Wasm instruction offset. pub wasm_inst_offset: usize, } -#[derive(Clone, Debug, Default)] +/// A diff of two `MachineState`s. +#[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct MachineStateDiff { + /// Last. pub last: Option, + /// Stack push. pub stack_push: Vec, + /// Stack pop. pub stack_pop: usize, + + /// Register diff. pub reg_diff: Vec<(RegisterIndex, MachineValue)>, - pub wasm_stack_push: Vec, - pub wasm_stack_pop: usize, - pub wasm_stack_private_depth: usize, // absolute value; not a diff. + /// Previous frame diff. + pub prev_frame_diff: BTreeMap>, // None for removal + /// Wasm stack push. + pub wasm_stack_push: Vec, + /// Wasm stack pop. + pub wasm_stack_pop: usize, + /// Private depth of the wasm stack. + pub wasm_stack_private_depth: usize, // absolute value; not a diff. + /// Wasm instruction offset. pub wasm_inst_offset: usize, // absolute value; not a diff. } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +/// A kind of machine value. +#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum MachineValue { + /// Undefined. Undefined, + /// Vmctx. Vmctx, + /// Vmctx Deref. + VmctxDeref(Vec), + /// Preserve Register. PreserveRegister(RegisterIndex), + /// Copy Stack BP Relative. CopyStackBPRelative(i32), // relative to Base Pointer, in byte offset - ExplicitShadow, // indicates that all values above this are above the shadow region + /// Explicit Shadow. + ExplicitShadow, // indicates that all values above this are above the shadow region + /// Wasm Stack. WasmStack(usize), + /// Wasm Local. WasmLocal(usize), + /// Two Halves. + TwoHalves(Box<(MachineValue, MachineValue)>), // 32-bit values. TODO: optimize: add another type for inner "half" value to avoid boxing? } -#[derive(Clone, Debug)] +/// A map of function states. +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct FunctionStateMap { + /// Initial. pub initial: MachineState, + /// Local Function Id. pub local_function_id: usize, + /// Locals. pub locals: Vec, + /// Shadow size. pub shadow_size: usize, // for single-pass backend, 32 bytes on x86-64 + /// Diffs. pub diffs: Vec, + /// Wasm Function Header target offset. pub wasm_function_header_target_offset: Option, + /// Wasm offset to target offset pub wasm_offset_to_target_offset: BTreeMap, + /// Loop offsets. pub loop_offsets: BTreeMap, /* suspend_offset -> info */ + /// Call offsets. pub call_offsets: BTreeMap, /* suspend_offset -> info */ + /// Trappable offsets. pub trappable_offsets: BTreeMap, /* suspend_offset -> info */ } -#[derive(Clone, Copy, Debug)] +/// A kind of suspend offset. +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub enum SuspendOffset { + /// A loop. Loop(usize), + /// A call. Call(usize), + /// A trappable. Trappable(usize), } -#[derive(Clone, Debug)] +/// Info for an offset. +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct OffsetInfo { + /// End offset. + pub end_offset: usize, // excluded bound + /// Diff Id. pub diff_id: usize, + /// Activate offset. pub activate_offset: usize, } -#[derive(Clone, Debug)] +/// A map of module state. +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct ModuleStateMap { + /// Local functions. pub local_functions: BTreeMap, + /// Total size. pub total_size: usize, } +/// State dump of a wasm function. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct WasmFunctionStateDump { + /// Local function id. pub local_function_id: usize, + /// Wasm instruction offset. pub wasm_inst_offset: usize, + /// Stack. pub stack: Vec>, + /// Locals. pub locals: Vec>, } +/// An image of the execution state. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct ExecutionStateImage { + /// Frames. pub frames: Vec, } +/// Represents an image of an `Instance` including its memory, globals, and execution state. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct InstanceImage { + /// Memory for this `InstanceImage` pub memory: Option>, + /// Stored globals for this `InstanceImage` pub globals: Vec, + /// `ExecutionStateImage` for this `InstanceImage` pub execution_state: ExecutionStateImage, } +/// A `CodeVersion` is a container for a unit of generated code for a module. +#[derive(Debug, Clone)] +pub struct CodeVersion { + /// Indicates if this code version is the baseline version. + pub baseline: bool, + + /// `ModuleStateMap` for this code version. + pub msm: ModuleStateMap, + + /// A pointer to the machine code for this module. + pub base: usize, + + /// The backend used to compile this module. + pub backend: Backend, +} + impl ModuleStateMap { - #[warn(dead_code)] - fn lookup_call_ip(&self, ip: usize, base: usize) -> Option<(&FunctionStateMap, MachineState)> { - if ip < base || ip - base >= self.total_size { - None - } else { - let (_, fsm) = self - .local_functions - .range((Unbounded, Included(&(ip - base)))) - .last() - .unwrap(); - - match fsm.call_offsets.get(&(ip - base)) { - Some(x) => { - if x.diff_id < fsm.diffs.len() { - Some((fsm, fsm.diffs[x.diff_id].build_state(fsm))) - } else { - None - } - } - None => None, - } - } - } - - #[warn(dead_code)] - fn lookup_trappable_ip( + /// Looks up an ip from self using the given ip, base, and offset table provider. + pub fn lookup_ip &BTreeMap>( &self, ip: usize, base: usize, + offset_table_provider: F, ) -> Option<(&FunctionStateMap, MachineState)> { if ip < base || ip - base >= self.total_size { None @@ -139,9 +205,14 @@ impl ModuleStateMap { .last() .unwrap(); - match fsm.trappable_offsets.get(&(ip - base)) { - Some(x) => { - if x.diff_id < fsm.diffs.len() { + match offset_table_provider(fsm) + .range((Unbounded, Included(&(ip - base)))) + .last() + { + Some((_, x)) => { + if ip - base >= x.end_offset { + None + } else if x.diff_id < fsm.diffs.len() { Some((fsm, fsm.diffs[x.diff_id].build_state(fsm))) } else { None @@ -151,33 +222,36 @@ impl ModuleStateMap { } } } + /// Looks up a call ip from self using the given ip and base values. + pub fn lookup_call_ip( + &self, + ip: usize, + base: usize, + ) -> Option<(&FunctionStateMap, MachineState)> { + self.lookup_ip(ip, base, |fsm| &fsm.call_offsets) + } - #[warn(dead_code)] - fn lookup_loop_ip(&self, ip: usize, base: usize) -> Option<(&FunctionStateMap, MachineState)> { - if ip < base || ip - base >= self.total_size { - None - } else { - let (_, fsm) = self - .local_functions - .range((Unbounded, Included(&(ip - base)))) - .last() - .unwrap(); + /// Looks up a trappable ip from self using the given ip and base values. + pub fn lookup_trappable_ip( + &self, + ip: usize, + base: usize, + ) -> Option<(&FunctionStateMap, MachineState)> { + self.lookup_ip(ip, base, |fsm| &fsm.trappable_offsets) + } - match fsm.loop_offsets.get(&(ip - base)) { - Some(x) => { - if x.diff_id < fsm.diffs.len() { - Some((fsm, fsm.diffs[x.diff_id].build_state(fsm))) - } else { - None - } - } - None => None, - } - } + /// Looks up a loop ip from self using the given ip and base values. + pub fn lookup_loop_ip( + &self, + ip: usize, + base: usize, + ) -> Option<(&FunctionStateMap, MachineState)> { + self.lookup_ip(ip, base, |fsm| &fsm.loop_offsets) } } impl FunctionStateMap { + /// Creates a new `FunctionStateMap` with the given parameters. pub fn new( initial: MachineState, local_function_id: usize, @@ -200,13 +274,14 @@ impl FunctionStateMap { } impl MachineState { + /// Creates a `MachineStateDiff` from self and the given `&MachineState`. pub fn diff(&self, old: &MachineState) -> MachineStateDiff { let first_diff_stack_depth: usize = self .stack_values .iter() .zip(old.stack_values.iter()) .enumerate() - .find(|&(_, (&a, &b))| a != b) + .find(|&(_, (a, b))| a != b) .map(|x| x.0) .unwrap_or(old.stack_values.len().min(self.stack_values.len())); assert_eq!(self.register_values.len(), old.register_values.len()); @@ -215,22 +290,42 @@ impl MachineState { .iter() .zip(old.register_values.iter()) .enumerate() - .filter(|&(_, (&a, &b))| a != b) - .map(|(i, (&a, _))| (RegisterIndex(i), a)) + .filter(|&(_, (a, b))| a != b) + .map(|(i, (a, _))| (RegisterIndex(i), a.clone())) + .collect(); + let prev_frame_diff: BTreeMap> = self + .prev_frame + .iter() + .filter(|(k, v)| { + if let Some(ref old_v) = old.prev_frame.get(k) { + v != old_v + } else { + true + } + }) + .map(|(&k, v)| (k, Some(v.clone()))) + .chain( + old.prev_frame + .iter() + .filter(|(k, _)| self.prev_frame.get(k).is_none()) + .map(|(&k, _)| (k, None)), + ) .collect(); let first_diff_wasm_stack_depth: usize = self .wasm_stack .iter() .zip(old.wasm_stack.iter()) .enumerate() - .find(|&(_, (&a, &b))| a != b) + .find(|&(_, (a, b))| a != b) .map(|x| x.0) .unwrap_or(old.wasm_stack.len().min(self.wasm_stack.len())); MachineStateDiff { last: None, stack_push: self.stack_values[first_diff_stack_depth..].to_vec(), stack_pop: old.stack_values.len() - first_diff_stack_depth, - reg_diff: reg_diff, + reg_diff, + + prev_frame_diff, wasm_stack_push: self.wasm_stack[first_diff_wasm_stack_depth..].to_vec(), wasm_stack_pop: old.wasm_stack.len() - first_diff_wasm_stack_depth, @@ -242,6 +337,7 @@ impl MachineState { } impl MachineStateDiff { + /// Creates a `MachineState` from the given `&FunctionStateMap`. pub fn build_state(&self, m: &FunctionStateMap) -> MachineState { let mut chain: Vec<&MachineStateDiff> = vec![]; chain.push(self); @@ -258,10 +354,17 @@ impl MachineStateDiff { state.stack_values.pop().unwrap(); } for v in &x.stack_push { - state.stack_values.push(*v); + state.stack_values.push(v.clone()); } - for &(index, v) in &x.reg_diff { - state.register_values[index.0] = v; + for &(index, ref v) in &x.reg_diff { + state.register_values[index.0] = v.clone(); + } + for (index, ref v) in &x.prev_frame_diff { + if let Some(ref x) = v { + state.prev_frame.insert(*index, x.clone()); + } else { + state.prev_frame.remove(index).unwrap(); + } } for _ in 0..x.wasm_stack_pop { state.wasm_stack.pop().unwrap(); @@ -277,12 +380,13 @@ impl MachineStateDiff { } impl ExecutionStateImage { + /// Prints a backtrace if the `WASMER_BACKTRACE` environment variable is 1. pub fn print_backtrace_if_needed(&self) { use std::env; if let Ok(x) = env::var("WASMER_BACKTRACE") { if x == "1" { - eprintln!("{}", self.colored_output()); + eprintln!("{}", self.output()); return; } } @@ -290,9 +394,8 @@ impl ExecutionStateImage { eprintln!("Run with `WASMER_BACKTRACE=1` environment variable to display a backtrace."); } - pub fn colored_output(&self) -> String { - use colored::*; - + /// Converts self into a `String`, used for display purposes. + pub fn output(&self) -> String { fn join_strings(x: impl Iterator, sep: &str) -> String { let mut ret = String::new(); let mut first = true; @@ -320,8 +423,6 @@ impl ExecutionStateImage { i, x.map(|x| format!("{}", x)) .unwrap_or_else(|| "?".to_string()) - .bold() - .cyan() ) }), ", ", @@ -332,27 +433,23 @@ impl ExecutionStateImage { let mut ret = String::new(); if self.frames.len() == 0 { - ret += &"Unknown fault address, cannot read stack.".yellow(); + ret += &"Unknown fault address, cannot read stack."; ret += "\n"; } else { - ret += &"Backtrace:".bold(); + ret += &"Backtrace:"; ret += "\n"; for (i, f) in self.frames.iter().enumerate() { - ret += &format!("* Frame {} @ Local function {}", i, f.local_function_id).bold(); + ret += &format!("* Frame {} @ Local function {}", i, f.local_function_id); ret += "\n"; + ret += &format!(" {} {}\n", "Offset:", format!("{}", f.wasm_inst_offset),); ret += &format!( " {} {}\n", - "Offset:".bold().yellow(), - format!("{}", f.wasm_inst_offset).bold().cyan(), - ); - ret += &format!( - " {} {}\n", - "Locals:".bold().yellow(), + "Locals:", format_optional_u64_sequence(&f.locals) ); ret += &format!( " {} {}\n\n", - "Stack:".bold().yellow(), + "Stack:", format_optional_u64_sequence(&f.stack) ); } @@ -363,6 +460,7 @@ impl ExecutionStateImage { } impl InstanceImage { + /// Converts a slice of bytes into an `Option` pub fn from_bytes(input: &[u8]) -> Option { use bincode::deserialize; match deserialize(input) { @@ -371,32 +469,182 @@ impl InstanceImage { } } + /// Converts self into a vector of bytes. pub fn to_bytes(&self) -> Vec { use bincode::serialize; serialize(self).unwrap() } } -#[cfg(all(unix, target_arch = "x86_64"))] +/// Declarations for x86-64 registers. +#[cfg(unix)] +pub mod x64_decl { + use super::*; + + /// General-purpose registers. + #[repr(u8)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub enum GPR { + /// RAX register + RAX, + /// RCX register + RCX, + /// RDX register + RDX, + /// RBX register + RBX, + /// RSP register + RSP, + /// RBP register + RBP, + /// RSI register + RSI, + /// RDI register + RDI, + /// R8 register + R8, + /// R9 register + R9, + /// R10 register + R10, + /// R11 register + R11, + /// R12 register + R12, + /// R13 register + R13, + /// R14 register + R14, + /// R15 register + R15, + } + + /// XMM registers. + #[repr(u8)] + #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub enum XMM { + /// XMM register 0 + XMM0, + /// XMM register 1 + XMM1, + /// XMM register 2 + XMM2, + /// XMM register 3 + XMM3, + /// XMM register 4 + XMM4, + /// XMM register 5 + XMM5, + /// XMM register 6 + XMM6, + /// XMM register 7 + XMM7, + /// XMM register 8 + XMM8, + /// XMM register 9 + XMM9, + /// XMM register 10 + XMM10, + /// XMM register 11 + XMM11, + /// XMM register 12 + XMM12, + /// XMM register 13 + XMM13, + /// XMM register 14 + XMM14, + /// XMM register 15 + XMM15, + } + + /// A machine register under the x86-64 architecture. + #[derive(Copy, Clone, Debug, Eq, PartialEq)] + pub enum X64Register { + /// General-purpose registers. + GPR(GPR), + /// XMM (floating point/SIMD) registers. + XMM(XMM), + } + + impl X64Register { + /// Returns the index of the register. + pub fn to_index(&self) -> RegisterIndex { + match *self { + X64Register::GPR(x) => RegisterIndex(x as usize), + X64Register::XMM(x) => RegisterIndex(x as usize + 16), + } + } + + /// Converts a DWARD regnum to X64Register. + pub fn from_dwarf_regnum(x: u16) -> Option { + Some(match x { + 0 => X64Register::GPR(GPR::RAX), + 1 => X64Register::GPR(GPR::RDX), + 2 => X64Register::GPR(GPR::RCX), + 3 => X64Register::GPR(GPR::RBX), + 4 => X64Register::GPR(GPR::RSI), + 5 => X64Register::GPR(GPR::RDI), + 6 => X64Register::GPR(GPR::RBP), + 7 => X64Register::GPR(GPR::RSP), + 8 => X64Register::GPR(GPR::R8), + 9 => X64Register::GPR(GPR::R9), + 10 => X64Register::GPR(GPR::R10), + 11 => X64Register::GPR(GPR::R11), + 12 => X64Register::GPR(GPR::R12), + 13 => X64Register::GPR(GPR::R13), + 14 => X64Register::GPR(GPR::R14), + 15 => X64Register::GPR(GPR::R15), + + 17 => X64Register::XMM(XMM::XMM0), + 18 => X64Register::XMM(XMM::XMM1), + 19 => X64Register::XMM(XMM::XMM2), + 20 => X64Register::XMM(XMM::XMM3), + 21 => X64Register::XMM(XMM::XMM4), + 22 => X64Register::XMM(XMM::XMM5), + 23 => X64Register::XMM(XMM::XMM6), + 24 => X64Register::XMM(XMM::XMM7), + _ => return None, + }) + } + } +} + +#[cfg(unix)] pub mod x64 { + //! The x64 state module contains functions to generate state and code for x64 targets. + pub use super::x64_decl::*; use super::*; use crate::codegen::BreakpointMap; - use crate::fault::{catch_unsafe_unwind, run_on_alternative_stack}; + use crate::fault::{ + catch_unsafe_unwind, get_boundary_register_preservation, run_on_alternative_stack, + }; use crate::structures::TypedIndex; use crate::types::LocalGlobalIndex; use crate::vm::Ctx; use std::any::Any; + unsafe fn compute_vmctx_deref(vmctx: *const Ctx, seq: &[usize]) -> u64 { + let mut ptr = &vmctx as *const *const Ctx as *const u8; + for x in seq { + ptr = (*(ptr as *const *const u8)).offset(*x as isize); + } + ptr as usize as u64 + } + + /// Create a new `MachineState` with default values. pub fn new_machine_state() -> MachineState { MachineState { stack_values: vec![], register_values: vec![MachineValue::Undefined; 16 + 8], + prev_frame: BTreeMap::new(), wasm_stack: vec![], wasm_stack_private_depth: 0, wasm_inst_offset: ::std::usize::MAX, } } + /// Invokes a call return on the stack for the given module state map, code base, instance + /// image and context. #[warn(unused_variables)] pub unsafe fn invoke_call_return_on_stack( msm: &ModuleStateMap, @@ -412,7 +660,7 @@ pub mod x64 { let mut last_stack_offset: u64 = 0; // rbp - let mut known_registers: [Option; 24] = [None; 24]; + let mut known_registers: [Option; 32] = [None; 32]; let local_functions_vec: Vec<&FunctionStateMap> = msm.local_functions.iter().map(|(_, v)| v).collect(); @@ -453,6 +701,10 @@ pub mod x64 { stack_offset -= 1; stack[stack_offset] = vmctx as *mut Ctx as usize as u64; } + MachineValue::VmctxDeref(ref seq) => { + stack_offset -= 1; + stack[stack_offset] = compute_vmctx_deref(vmctx as *const Ctx, seq); + } MachineValue::PreserveRegister(index) => { stack_offset -= 1; stack[stack_offset] = known_registers[index.0].unwrap_or(0); @@ -491,6 +743,73 @@ pub mod x64 { } } } + MachineValue::TwoHalves(ref inner) => { + stack_offset -= 1; + // TODO: Cleanup + match inner.0 { + MachineValue::WasmStack(x) => match state.wasm_stack[x] { + WasmAbstractValue::Const(x) => { + assert!(x <= std::u32::MAX as u64); + stack[stack_offset] |= x; + } + WasmAbstractValue::Runtime => { + let v = f.stack[x].unwrap(); + assert!(v <= std::u32::MAX as u64); + stack[stack_offset] |= v; + } + }, + MachineValue::WasmLocal(x) => match fsm.locals[x] { + WasmAbstractValue::Const(x) => { + assert!(x <= std::u32::MAX as u64); + stack[stack_offset] |= x; + } + WasmAbstractValue::Runtime => { + let v = f.locals[x].unwrap(); + assert!(v <= std::u32::MAX as u64); + stack[stack_offset] |= v; + } + }, + MachineValue::VmctxDeref(ref seq) => { + stack[stack_offset] |= + compute_vmctx_deref(vmctx as *const Ctx, seq) + & (std::u32::MAX as u64); + } + MachineValue::Undefined => {} + _ => unimplemented!("TwoHalves.0"), + } + match inner.1 { + MachineValue::WasmStack(x) => match state.wasm_stack[x] { + WasmAbstractValue::Const(x) => { + assert!(x <= std::u32::MAX as u64); + stack[stack_offset] |= x << 32; + } + WasmAbstractValue::Runtime => { + let v = f.stack[x].unwrap(); + assert!(v <= std::u32::MAX as u64); + stack[stack_offset] |= v << 32; + } + }, + MachineValue::WasmLocal(x) => match fsm.locals[x] { + WasmAbstractValue::Const(x) => { + assert!(x <= std::u32::MAX as u64); + stack[stack_offset] |= x << 32; + } + WasmAbstractValue::Runtime => { + let v = f.locals[x].unwrap(); + assert!(v <= std::u32::MAX as u64); + stack[stack_offset] |= v << 32; + } + }, + MachineValue::VmctxDeref(ref seq) => { + stack[stack_offset] |= + (compute_vmctx_deref(vmctx as *const Ctx, seq) + & (std::u32::MAX as u64)) + << 32; + } + MachineValue::Undefined => {} + _ => unimplemented!("TwoHalves.1"), + } + } } } if !got_explicit_shadow { @@ -503,6 +822,9 @@ pub mod x64 { MachineValue::Vmctx => { known_registers[i] = Some(vmctx as *mut Ctx as usize as u64); } + MachineValue::VmctxDeref(ref seq) => { + known_registers[i] = Some(compute_vmctx_deref(vmctx as *const Ctx, seq)); + } MachineValue::WasmStack(x) => match state.wasm_stack[x] { WasmAbstractValue::Const(x) => { known_registers[i] = Some(x); @@ -574,6 +896,37 @@ pub mod x64 { stack_offset -= 1; stack[stack_offset] = stack.as_ptr().offset(last_stack_offset as isize) as usize as u64; // rbp + stack_offset -= 1; + stack[stack_offset] = + known_registers[X64Register::XMM(XMM::XMM15).to_index().0].unwrap_or(0); + + stack_offset -= 1; + stack[stack_offset] = + known_registers[X64Register::XMM(XMM::XMM14).to_index().0].unwrap_or(0); + + stack_offset -= 1; + stack[stack_offset] = + known_registers[X64Register::XMM(XMM::XMM13).to_index().0].unwrap_or(0); + + stack_offset -= 1; + stack[stack_offset] = + known_registers[X64Register::XMM(XMM::XMM12).to_index().0].unwrap_or(0); + + stack_offset -= 1; + stack[stack_offset] = + known_registers[X64Register::XMM(XMM::XMM11).to_index().0].unwrap_or(0); + + stack_offset -= 1; + stack[stack_offset] = + known_registers[X64Register::XMM(XMM::XMM10).to_index().0].unwrap_or(0); + + stack_offset -= 1; + stack[stack_offset] = + known_registers[X64Register::XMM(XMM::XMM9).to_index().0].unwrap_or(0); + + stack_offset -= 1; + stack[stack_offset] = + known_registers[X64Register::XMM(XMM::XMM8).to_index().0].unwrap_or(0); stack_offset -= 1; stack[stack_offset] = known_registers[X64Register::XMM(XMM::XMM7).to_index().0].unwrap_or(0); @@ -620,11 +973,8 @@ pub mod x64 { assert_eq!(vmctx.internal.memory_bound, memory.len()); } - ::std::slice::from_raw_parts_mut( - vmctx.internal.memory_base, - vmctx.internal.memory_bound, - ) - .copy_from_slice(memory); + std::slice::from_raw_parts_mut(vmctx.internal.memory_base, vmctx.internal.memory_bound) + .copy_from_slice(memory); } let globals_len = (*vmctx.module).info.globals.len(); @@ -646,6 +996,7 @@ pub mod x64 { ) } + /// Builds an `InstanceImage` for the given `Ctx` and `ExecutionStateImage`. pub fn build_instance_image( vmctx: &mut Ctx, execution_state: ExecutionStateImage, @@ -655,7 +1006,7 @@ pub mod x64 { None } else { Some( - ::std::slice::from_raw_parts( + std::slice::from_raw_parts( vmctx.internal.memory_base, vmctx.internal.memory_bound, ) @@ -681,32 +1032,84 @@ pub mod x64 { } } + /// Returns a `ExecutionStateImage` for the given versions, stack, initial registers and + /// initial address. #[warn(unused_variables)] - pub unsafe fn read_stack( - msm: &ModuleStateMap, - code_base: usize, + pub unsafe fn read_stack<'a, I: Iterator, F: Fn() -> I + 'a>( + versions: F, mut stack: *const u64, - initially_known_registers: [Option; 24], + initially_known_registers: [Option; 32], mut initial_address: Option, + max_depth: Option, ) -> ExecutionStateImage { - let mut known_registers: [Option; 24] = initially_known_registers; + let mut known_registers: [Option; 32] = initially_known_registers; let mut results: Vec = vec![]; + let mut was_baseline = true; + + for depth in 0.. { + if let Some(max_depth) = max_depth { + if depth >= max_depth { + return ExecutionStateImage { frames: results }; + } + } - for _ in 0.. { let ret_addr = initial_address.take().unwrap_or_else(|| { let x = *stack; stack = stack.offset(1); x }); - let (fsm, state) = match msm - .lookup_call_ip(ret_addr as usize, code_base) - .or_else(|| msm.lookup_trappable_ip(ret_addr as usize, code_base)) - .or_else(|| msm.lookup_loop_ip(ret_addr as usize, code_base)) - { - Some(x) => x, - _ => return ExecutionStateImage { frames: results }, + + let mut fsm_state: Option<(&FunctionStateMap, MachineState)> = None; + let mut is_baseline: Option = None; + + for version in versions() { + //println!("Lookup IP: {:x}", ret_addr); + match version + .msm + .lookup_call_ip(ret_addr as usize, version.base) + .or_else(|| { + version + .msm + .lookup_trappable_ip(ret_addr as usize, version.base) + }) + .or_else(|| version.msm.lookup_loop_ip(ret_addr as usize, version.base)) + { + Some(x) => { + fsm_state = Some(x); + is_baseline = Some(version.baseline); + break; + } + None => {} + }; + } + + let (fsm, state) = if let Some(x) = fsm_state { + x + } else { + return ExecutionStateImage { frames: results }; }; + { + let is_baseline = is_baseline.unwrap(); + + // Are we unwinding through an optimized/baseline boundary? + if is_baseline && !was_baseline { + let callee_saved = &*get_boundary_register_preservation(); + known_registers[X64Register::GPR(GPR::R15).to_index().0] = + Some(callee_saved.r15); + known_registers[X64Register::GPR(GPR::R14).to_index().0] = + Some(callee_saved.r14); + known_registers[X64Register::GPR(GPR::R13).to_index().0] = + Some(callee_saved.r13); + known_registers[X64Register::GPR(GPR::R12).to_index().0] = + Some(callee_saved.r12); + known_registers[X64Register::GPR(GPR::RBX).to_index().0] = + Some(callee_saved.rbx); + } + + was_baseline = is_baseline; + } + let mut wasm_stack: Vec> = state .wasm_stack .iter() @@ -729,6 +1132,7 @@ pub mod x64 { match *v { MachineValue::Undefined => {} MachineValue::Vmctx => {} + MachineValue::VmctxDeref(_) => {} MachineValue::WasmStack(idx) => { if let Some(v) = known_registers[i] { wasm_stack[idx] = Some(v); @@ -773,6 +1177,9 @@ pub mod x64 { MachineValue::Vmctx => { stack = stack.offset(1); } + MachineValue::VmctxDeref(_) => { + stack = stack.offset(1); + } MachineValue::PreserveRegister(idx) => { known_registers[idx.0] = Some(*stack); stack = stack.offset(1); @@ -788,9 +1195,48 @@ pub mod x64 { wasm_locals[idx] = Some(*stack); stack = stack.offset(1); } + MachineValue::TwoHalves(ref inner) => { + let v = *stack; + stack = stack.offset(1); + match inner.0 { + MachineValue::WasmStack(idx) => { + wasm_stack[idx] = Some(v & 0xffffffffu64); + } + MachineValue::WasmLocal(idx) => { + wasm_locals[idx] = Some(v & 0xffffffffu64); + } + MachineValue::VmctxDeref(_) => {} + MachineValue::Undefined => {} + _ => unimplemented!("TwoHalves.0 (read)"), + } + match inner.1 { + MachineValue::WasmStack(idx) => { + wasm_stack[idx] = Some(v >> 32); + } + MachineValue::WasmLocal(idx) => { + wasm_locals[idx] = Some(v >> 32); + } + MachineValue::VmctxDeref(_) => {} + MachineValue::Undefined => {} + _ => unimplemented!("TwoHalves.1 (read)"), + } + } } } - stack = stack.offset(1); // RBP + + for (offset, v) in state.prev_frame.iter() { + let offset = (*offset + 2) as isize; // (saved_rbp, return_address) + match *v { + MachineValue::WasmStack(idx) => { + wasm_stack[idx] = Some(*stack.offset(offset)); + } + MachineValue::WasmLocal(idx) => { + wasm_locals[idx] = Some(*stack.offset(offset)); + } + _ => unreachable!("values in prev frame can only be stack/local"), + } + } + stack = stack.offset(1); // saved_rbp wasm_stack.truncate( wasm_stack @@ -805,57 +1251,10 @@ pub mod x64 { stack: wasm_stack, locals: wasm_locals, }; + //println!("WFS = {:?}", wfs); results.push(wfs); } unreachable!(); } - - #[repr(u8)] - #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] - pub enum GPR { - RAX, - RCX, - RDX, - RBX, - RSP, - RBP, - RSI, - RDI, - R8, - R9, - R10, - R11, - R12, - R13, - R14, - R15, - } - - #[repr(u8)] - #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] - pub enum XMM { - XMM0, - XMM1, - XMM2, - XMM3, - XMM4, - XMM5, - XMM6, - XMM7, - } - - pub enum X64Register { - GPR(GPR), - XMM(XMM), - } - - impl X64Register { - pub fn to_index(&self) -> RegisterIndex { - match *self { - X64Register::GPR(x) => RegisterIndex(x as usize), - X64Register::XMM(x) => RegisterIndex(x as usize + 16), - } - } - } } diff --git a/lib/runtime-core/src/structures/boxed.rs b/lib/runtime-core/src/structures/boxed.rs index 710dc70a6..439bad680 100644 --- a/lib/runtime-core/src/structures/boxed.rs +++ b/lib/runtime-core/src/structures/boxed.rs @@ -5,6 +5,7 @@ use std::{ ops::{Deref, DerefMut}, }; +/// Boxed map. #[derive(Debug, Clone)] pub struct BoxedMap where diff --git a/lib/runtime-core/src/structures/map.rs b/lib/runtime-core/src/structures/map.rs index add5b0257..08773b50a 100644 --- a/lib/runtime-core/src/structures/map.rs +++ b/lib/runtime-core/src/structures/map.rs @@ -21,6 +21,7 @@ impl Map where K: TypedIndex, { + /// Creates a new `Map`. pub fn new() -> Self { Self { elems: Vec::new(), @@ -28,6 +29,7 @@ where } } + /// Creates a new empty `Map` with the given capacity. pub fn with_capacity(capacity: usize) -> Self { Self { elems: Vec::with_capacity(capacity), @@ -35,28 +37,44 @@ where } } + /// Clears the map. Keeps the allocated memory for future use. + pub fn clear(&mut self) { + self.elems.clear(); + } + + /// Returns the size of this map. pub fn len(&self) -> usize { self.elems.len() } + /// Returns true if this map is empty. + pub fn is_empty(&self) -> bool { + self.elems.is_empty() + } + + /// Adds a new value to this map. pub fn push(&mut self, value: V) -> K { let len = self.len(); self.elems.push(value); K::new(len) } + /// Returns the next index into the map. pub fn next_index(&self) -> K { K::new(self.len()) } + /// Reserves the given size. pub fn reserve_exact(&mut self, size: usize) { self.elems.reserve_exact(size); } + /// Convert this into a `BoxedMap`. pub fn into_boxed_map(self) -> BoxedMap { BoxedMap::new(self.elems.into_boxed_slice()) } + /// Convert this into a `Vec`. pub fn into_vec(self) -> Vec { self.elems } @@ -67,6 +85,7 @@ where K: TypedIndex, V: Clone, { + /// Resize this map to the given new length and value. pub fn resize(&mut self, new_len: usize, value: V) { self.elems.resize(new_len, value); } @@ -180,6 +199,7 @@ where } } +/// Iterator for a `Map`. pub struct Iter<'a, K: TypedIndex, V: 'a> { enumerated: iter::Enumerate>, _marker: PhantomData, @@ -202,6 +222,7 @@ impl<'a, K: TypedIndex, V: 'a> Iterator for Iter<'a, K, V> { } } +/// Mutable iterator for a `Map`. pub struct IterMut<'a, K: TypedIndex, V: 'a> { enumerated: iter::Enumerate>, _marker: PhantomData, diff --git a/lib/runtime-core/src/structures/mod.rs b/lib/runtime-core/src/structures/mod.rs index ca7a449df..42af0d2c0 100644 --- a/lib/runtime-core/src/structures/mod.rs +++ b/lib/runtime-core/src/structures/mod.rs @@ -1,3 +1,4 @@ +//! The structures module contains commonly used data structures. mod boxed; mod map; mod slice; @@ -6,6 +7,7 @@ pub use self::boxed::BoxedMap; pub use self::map::{Iter, IterMut, Map}; pub use self::slice::SliceMap; +/// Represents a typed index. pub trait TypedIndex: Copy + Clone { #[doc(hidden)] fn new(index: usize) -> Self; diff --git a/lib/runtime-core/src/structures/slice.rs b/lib/runtime-core/src/structures/slice.rs index 8574b2d5b..5ac0d6190 100644 --- a/lib/runtime-core/src/structures/slice.rs +++ b/lib/runtime-core/src/structures/slice.rs @@ -20,30 +20,37 @@ impl SliceMap where K: TypedIndex, { + /// Gets a reference to the value at the given index. pub fn get(&self, index: K) -> Option<&V> { self.slice.get(index.index()) } + /// Gets a mutable reference to the value at the given index. pub fn get_mut(&mut self, index: K) -> Option<&mut V> { self.slice.get_mut(index.index()) } + /// Gets the length of this slice map. pub fn len(&self) -> usize { self.slice.len() } + /// Returns an iterator for this slice map. pub fn iter(&self) -> Iter { Iter::new(self.slice.iter()) } + /// Returns a mutable iterator for this slice map. pub fn iter_mut(&mut self) -> IterMut { IterMut::new(self.slice.iter_mut()) } + /// Gets a pointer to the `SliceMap`. pub fn as_ptr(&self) -> *const V { self as *const SliceMap as *const V } + /// Gets a mutable pointer to the `SliceMap`. pub fn as_mut_ptr(&mut self) -> *mut V { self as *mut SliceMap as *mut V } diff --git a/lib/runtime-core/src/sys/unix/memory.rs b/lib/runtime-core/src/sys/unix/memory.rs index f4af2493b..051dc1f0e 100644 --- a/lib/runtime-core/src/sys/unix/memory.rs +++ b/lib/runtime-core/src/sys/unix/memory.rs @@ -4,20 +4,22 @@ use errno; use nix::libc; use page_size; use std::ops::{Bound, RangeBounds}; -use std::{fs::File, os::unix::io::IntoRawFd, path::Path, ptr, rc::Rc, slice}; +use std::{fs::File, os::unix::io::IntoRawFd, path::Path, ptr, slice, sync::Arc}; unsafe impl Send for Memory {} unsafe impl Sync for Memory {} +/// Data for a sized and protected region of memory. #[derive(Debug)] pub struct Memory { ptr: *mut u8, size: usize, protection: Protect, - fd: Option>, + fd: Option>, } impl Memory { + /// Create a new memory from the given path value and protection. pub fn from_file_path

(path: P, protection: Protect) -> Result where P: AsRef, @@ -49,11 +51,12 @@ impl Memory { ptr: ptr as *mut u8, size: file_len as usize, protection, - fd: Some(Rc::new(raw_fd)), + fd: Some(Arc::new(raw_fd)), }) } } + /// Create a new memory with the given size and protection. pub fn with_size_protect(size: usize, protection: Protect) -> Result { if size == 0 { return Ok(Self { @@ -89,6 +92,7 @@ impl Memory { } } + /// Create a new memory with the given size. pub fn with_size(size: usize) -> Result { if size == 0 { return Ok(Self { @@ -127,6 +131,7 @@ impl Memory { } } + /// Protect this memory with the given range bounds and protection. pub unsafe fn protect( &mut self, range: impl RangeBounds, @@ -166,6 +171,7 @@ impl Memory { } } + /// Split this memory into multiple memories by the given offset. pub fn split_at(mut self, offset: usize) -> (Memory, Memory) { let page_size = page_size::get(); if offset % page_size == 0 { @@ -187,22 +193,27 @@ impl Memory { } } + /// Gets the size of this memory. pub fn size(&self) -> usize { self.size } + /// Gets a slice for this memory. pub unsafe fn as_slice(&self) -> &[u8] { slice::from_raw_parts(self.ptr, self.size) } + /// Gets a mutable slice for this memory. pub unsafe fn as_slice_mut(&mut self) -> &mut [u8] { slice::from_raw_parts_mut(self.ptr, self.size) } + /// Gets the protect kind of this memory. pub fn protection(&self) -> Protect { self.protection } + /// Gets mutable pointer to the memory. pub fn as_ptr(&self) -> *mut u8 { self.ptr } @@ -238,13 +249,19 @@ impl Clone for Memory { } } +/// Kinds of memory protection. #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] #[allow(dead_code)] pub enum Protect { + /// Read/write/exec allowed. None, + /// Read only. Read, + /// Read/write only. ReadWrite, + /// Read/exec only. ReadExec, + /// Read/write/exec only. ReadWriteExec, } @@ -259,6 +276,7 @@ impl Protect { } } + /// Returns true if this memory is readable. pub fn is_readable(self) -> bool { match self { Protect::Read | Protect::ReadWrite | Protect::ReadExec | Protect::ReadWriteExec => true, @@ -266,6 +284,7 @@ impl Protect { } } + /// Returns true if this memory is writable. pub fn is_writable(self) -> bool { match self { Protect::ReadWrite | Protect::ReadWriteExec => true, diff --git a/lib/runtime-core/src/sys/windows/memory.rs b/lib/runtime-core/src/sys/windows/memory.rs index 888aeff4d..411dee5b7 100644 --- a/lib/runtime-core/src/sys/windows/memory.rs +++ b/lib/runtime-core/src/sys/windows/memory.rs @@ -12,6 +12,7 @@ use winapi::um::winnt::{ unsafe impl Send for Memory {} unsafe impl Sync for Memory {} +/// Data for a sized and protected region of memory. #[derive(Debug)] pub struct Memory { ptr: *mut u8, @@ -20,6 +21,7 @@ pub struct Memory { } impl Memory { + /// Create a new memory from the given path value and protection. pub fn with_size_protect(size: usize, protection: Protect) -> Result { if size == 0 { return Ok(Self { @@ -52,6 +54,7 @@ impl Memory { } } + /// Create a new memory with the given size. pub fn with_size(size: usize) -> Result { if size == 0 { return Ok(Self { @@ -79,6 +82,7 @@ impl Memory { } } + /// Protect this memory with the given range bounds and protection. pub unsafe fn protect( &mut self, range: impl RangeBounds, @@ -120,6 +124,7 @@ impl Memory { } } + /// Split this memory into multiple memories by the given offset. pub fn split_at(mut self, offset: usize) -> (Memory, Memory) { let page_size = page_size::get(); if offset % page_size == 0 { @@ -140,22 +145,27 @@ impl Memory { } } + /// Gets the size of this memory. pub fn size(&self) -> usize { self.size } + /// Gets a slice for this memory. pub unsafe fn as_slice(&self) -> &[u8] { slice::from_raw_parts(self.ptr, self.size) } + /// Gets a mutable slice for this memory. pub unsafe fn as_slice_mut(&mut self) -> &mut [u8] { slice::from_raw_parts_mut(self.ptr, self.size) } + /// Gets the protect kind of this memory. pub fn protection(&self) -> Protect { self.protection } + /// Gets mutable pointer to the memory. pub fn as_ptr(&self) -> *mut u8 { self.ptr } @@ -192,12 +202,17 @@ impl Clone for Memory { } } +/// Kinds of memory protection. #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] #[allow(dead_code)] pub enum Protect { + /// Read/write/exec allowed. None, + /// Read only. Read, + /// Read/write only. ReadWrite, + /// Read/exec only. ReadExec, } @@ -211,6 +226,7 @@ impl Protect { } } + /// Returns true if this memory is readable. pub fn is_readable(self) -> bool { match self { Protect::Read | Protect::ReadWrite | Protect::ReadExec => true, @@ -218,6 +234,7 @@ impl Protect { } } + /// Returns true if this memory is writable. pub fn is_writable(self) -> bool { match self { Protect::ReadWrite => true, diff --git a/lib/runtime-core/src/table/anyfunc.rs b/lib/runtime-core/src/table/anyfunc.rs index 789f67ada..38cb578f2 100644 --- a/lib/runtime-core/src/table/anyfunc.rs +++ b/lib/runtime-core/src/table/anyfunc.rs @@ -17,11 +17,13 @@ enum AnyfuncInner<'a> { Managed(DynFunc<'a>), } +/// Anyfunc data type. pub struct Anyfunc<'a> { inner: AnyfuncInner<'a>, } impl<'a> Anyfunc<'a> { + /// Create a new `Anyfunc`. pub unsafe fn new(func: *const vm::Func, signature: Sig) -> Self where Sig: Into>, @@ -44,7 +46,7 @@ impl<'a> From> for Anyfunc<'a> { } pub struct AnyfuncTable { - backing: Vec, + pub(crate) backing: Vec, max: Option, } diff --git a/lib/runtime-core/src/table/mod.rs b/lib/runtime-core/src/table/mod.rs index 1c97b89f3..997ce5eb1 100644 --- a/lib/runtime-core/src/table/mod.rs +++ b/lib/runtime-core/src/table/mod.rs @@ -1,3 +1,5 @@ +//! The runtime table module contains data structures and functions used to create and update wasm +//! tables. use crate::{ error::CreationError, export::Export, @@ -5,27 +7,34 @@ use crate::{ types::{ElementType, TableDescriptor}, vm, }; -use std::{cell::RefCell, fmt, ptr, rc::Rc}; +use std::{ + fmt, ptr, + sync::{Arc, Mutex}, +}; mod anyfunc; pub use self::anyfunc::Anyfunc; -use self::anyfunc::AnyfuncTable; +pub(crate) use self::anyfunc::AnyfuncTable; use crate::error::GrowError; +/// Kind of table element. pub enum Element<'a> { + /// Anyfunc. Anyfunc(Anyfunc<'a>), } +/// Kind of table storage. // #[derive(Debug)] pub enum TableStorage { /// This is intended to be a caller-checked Anyfunc. Anyfunc(Box), } +/// Container with a descriptor and a reference to a table storage. pub struct Table { desc: TableDescriptor, - storage: Rc>, + storage: Arc>, } impl Table { @@ -71,7 +80,7 @@ impl Table { Ok(Self { desc, - storage: Rc::new(RefCell::new((storage, local))), + storage: Arc::new(Mutex::new((storage, local))), }) } @@ -82,7 +91,8 @@ impl Table { /// Set the element at index. pub fn set(&self, index: u32, element: Element) -> Result<(), ()> { - match &mut *self.storage.borrow_mut() { + let mut storage = self.storage.lock().unwrap(); + match &mut *storage { (TableStorage::Anyfunc(ref mut anyfunc_table), _) => { match element { Element::Anyfunc(anyfunc) => anyfunc_table.set(index, anyfunc), @@ -96,14 +106,16 @@ impl Table { where F: FnOnce(&mut [vm::Anyfunc]) -> R, { - match &mut *self.storage.borrow_mut() { + let mut storage = self.storage.lock().unwrap(); + match &mut *storage { (TableStorage::Anyfunc(ref mut anyfunc_table), _) => f(anyfunc_table.internal_buffer()), } } /// The current size of this table. pub fn size(&self) -> u32 { - match &*self.storage.borrow() { + let storage = self.storage.lock().unwrap(); + match &*storage { (TableStorage::Anyfunc(ref anyfunc_table), _) => anyfunc_table.current_size(), } } @@ -114,15 +126,18 @@ impl Table { return Ok(self.size()); } - match &mut *self.storage.borrow_mut() { + let mut storage = self.storage.lock().unwrap(); + match &mut *storage { (TableStorage::Anyfunc(ref mut anyfunc_table), ref mut local) => anyfunc_table .grow(delta, local) .ok_or(GrowError::TableGrowError), } } + /// Get a mutable pointer to underlying table storage. pub fn vm_local_table(&mut self) -> *mut vm::LocalTable { - &mut self.storage.borrow_mut().1 + let mut storage = self.storage.lock().unwrap(); + &mut storage.1 } } @@ -136,7 +151,7 @@ impl Clone for Table { fn clone(&self) -> Self { Self { desc: self.desc, - storage: Rc::clone(&self.storage), + storage: Arc::clone(&self.storage), } } } @@ -165,5 +180,4 @@ mod table_tests { .unwrap(); assert_eq!(table.size(), 10); } - } diff --git a/lib/runtime-core/src/tiering.rs b/lib/runtime-core/src/tiering.rs new file mode 100644 index 000000000..7d2a27d87 --- /dev/null +++ b/lib/runtime-core/src/tiering.rs @@ -0,0 +1,246 @@ +//! The tiering module supports switching between code compiled with different optimization levels +//! as runtime. +use crate::backend::{Backend, Compiler, CompilerConfig}; +use crate::compile_with_config; +use crate::fault::{ + catch_unsafe_unwind, ensure_sighandler, pop_code_version, push_code_version, with_ctx, +}; +use crate::fault::{set_wasm_interrupt_on_ctx, was_sigint_triggered_fault}; +use crate::import::ImportObject; +use crate::instance::Instance; +use crate::module::{Module, ModuleInfo}; +use crate::state::{x64::invoke_call_return_on_stack, CodeVersion, InstanceImage}; +use crate::vm::Ctx; + +use std::cell::Cell; +use std::sync::{Arc, Mutex}; + +struct Defer(Option); +impl Drop for Defer { + fn drop(&mut self) { + if let Some(f) = self.0.take() { + f(); + } + } +} + +/// Kind of shell exit operation. +pub enum ShellExitOperation { + /// Operation to continue with an instance image. + ContinueWith(InstanceImage), +} + +/// Context for an interactive shell. +pub struct InteractiveShellContext { + /// Optional instance image. + pub image: Option, + /// Flag to indicate patching. + pub patched: bool, +} + +struct OptimizationState { + outcome: Mutex>, +} + +struct OptimizationOutcome { + backend_id: Backend, + module: Module, +} + +#[repr(transparent)] +struct CtxWrapper(*mut Ctx); +unsafe impl Send for CtxWrapper {} +unsafe impl Sync for CtxWrapper {} + +unsafe fn do_optimize( + binary: &[u8], + backend_id: Backend, + compiler: Box, + ctx: &Mutex, + state: &OptimizationState, +) { + let module = match compile_with_config( + &binary[..], + &*compiler, + CompilerConfig { + symbol_map: None, + track_state: true, + ..Default::default() + }, + ) { + Ok(x) => x, + Err(_) => return, + }; + + let ctx_inner = ctx.lock().unwrap(); + if !ctx_inner.0.is_null() { + *state.outcome.lock().unwrap() = Some(OptimizationOutcome { backend_id, module }); + set_wasm_interrupt_on_ctx(ctx_inner.0); + } +} + +/// Runs an instance with tiering. +pub unsafe fn run_tiering ShellExitOperation>( + module_info: &ModuleInfo, + wasm_binary: &[u8], + mut resume_image: Option, + import_object: &ImportObject, + start_raw: extern "C" fn(&mut Ctx), + baseline: &mut Instance, + baseline_backend: Backend, + optimized_backends: Vec<(Backend, Box Box + Send>)>, + interactive_shell: F, +) -> Result<(), String> { + ensure_sighandler(); + + let ctx_box = Arc::new(Mutex::new(CtxWrapper(baseline.context_mut() as *mut _))); + // Ensure that the ctx pointer's lifetime is not longer than Instance's. + let _deferred_ctx_box_cleanup: Defer<_> = { + let ctx_box = ctx_box.clone(); + Defer(Some(move || { + ctx_box.lock().unwrap().0 = ::std::ptr::null_mut(); + })) + }; + let opt_state = Arc::new(OptimizationState { + outcome: Mutex::new(None), + }); + + { + let wasm_binary = wasm_binary.to_vec(); + let ctx_box = ctx_box.clone(); + let opt_state = opt_state.clone(); + ::std::thread::spawn(move || { + for (backend_id, backend) in optimized_backends { + if !ctx_box.lock().unwrap().0.is_null() { + do_optimize(&wasm_binary, backend_id, backend(), &ctx_box, &opt_state); + } + } + }); + } + + let mut optimized_instances: Vec = vec![]; + + push_code_version(CodeVersion { + baseline: true, + msm: baseline + .module + .runnable_module + .get_module_state_map() + .unwrap(), + base: baseline.module.runnable_module.get_code().unwrap().as_ptr() as usize, + backend: baseline_backend, + }); + let n_versions: Cell = Cell::new(1); + + let _deferred_pop_versions = Defer(Some(|| { + for _ in 0..n_versions.get() { + pop_code_version().unwrap(); + } + })); + + loop { + let new_optimized: Option<(Backend, &mut Instance)> = { + let mut outcome = opt_state.outcome.lock().unwrap(); + if let Some(x) = outcome.take() { + let instance = x + .module + .instantiate(&import_object) + .map_err(|e| format!("Can't instantiate module: {:?}", e))?; + // Keep the optimized code alive. + optimized_instances.push(instance); + optimized_instances.last_mut().map(|y| (x.backend_id, y)) + } else { + None + } + }; + if let Some((backend_id, optimized)) = new_optimized { + let base = module_info.imported_functions.len(); + let code_ptr = optimized + .module + .runnable_module + .get_code() + .unwrap() + .as_ptr() as usize; + let target_addresses: Vec = optimized + .module + .runnable_module + .get_local_function_offsets() + .unwrap() + .into_iter() + .map(|x| code_ptr + x) + .collect(); + assert_eq!(target_addresses.len(), module_info.func_assoc.len() - base); + for i in base..module_info.func_assoc.len() { + baseline + .module + .runnable_module + .patch_local_function(i - base, target_addresses[i - base]); + } + + push_code_version(CodeVersion { + baseline: false, + msm: optimized + .module + .runnable_module + .get_module_state_map() + .unwrap(), + base: optimized + .module + .runnable_module + .get_code() + .unwrap() + .as_ptr() as usize, + backend: backend_id, + }); + n_versions.set(n_versions.get() + 1); + + baseline.context_mut().local_functions = optimized.context_mut().local_functions; + } + // Assuming we do not want to do breakpoint-based debugging on optimized backends. + let breakpoints = baseline.module.runnable_module.get_breakpoints(); + let ctx = baseline.context_mut() as *mut _; + let ret = with_ctx(ctx, || { + if let Some(image) = resume_image.take() { + let msm = baseline + .module + .runnable_module + .get_module_state_map() + .unwrap(); + let code_base = + baseline.module.runnable_module.get_code().unwrap().as_ptr() as usize; + invoke_call_return_on_stack( + &msm, + code_base, + image, + baseline.context_mut(), + breakpoints.clone(), + ) + .map(|_| ()) + } else { + catch_unsafe_unwind(|| start_raw(baseline.context_mut()), breakpoints.clone()) + } + }); + if let Err(e) = ret { + if let Ok(new_image) = e.downcast::() { + // Tier switch event + if !was_sigint_triggered_fault() && opt_state.outcome.lock().unwrap().is_some() { + resume_image = Some(*new_image); + continue; + } + let op = interactive_shell(InteractiveShellContext { + image: Some(*new_image), + patched: n_versions.get() > 1, + }); + match op { + ShellExitOperation::ContinueWith(new_image) => { + resume_image = Some(new_image); + } + } + } else { + return Err("Error while executing WebAssembly".into()); + } + } else { + return Ok(()); + } + } +} diff --git a/lib/runtime-core/src/trampoline_x64.rs b/lib/runtime-core/src/trampoline_x64.rs index 80de3e0d6..3d07484c7 100644 --- a/lib/runtime-core/src/trampoline_x64.rs +++ b/lib/runtime-core/src/trampoline_x64.rs @@ -62,6 +62,7 @@ pub fn get_context() -> *const CallContext { } impl TrampolineBufferBuilder { + /// Creates a new empty `TrampolineBufferBuilder`. pub fn new() -> TrampolineBufferBuilder { TrampolineBufferBuilder { code: vec![], @@ -100,6 +101,7 @@ impl TrampolineBufferBuilder { idx } + /// Adds context RSP state preserving trampoline to the buffer. pub fn add_context_rsp_state_preserving_trampoline( &mut self, target: unsafe extern "C" fn(&mut Ctx, *const CallContext, *const u64), diff --git a/lib/runtime-core/src/typed_func.rs b/lib/runtime-core/src/typed_func.rs index 91da26cbe..f1e62c238 100644 --- a/lib/runtime-core/src/typed_func.rs +++ b/lib/runtime-core/src/typed_func.rs @@ -1,9 +1,11 @@ +//! The typed func module implements a way of representing a wasm function +//! with the correct types from rust. Function calls using a typed func have a low overhead. use crate::{ error::RuntimeError, export::{Context, Export, FuncPointer}, import::IsExport, types::{FuncSig, NativeWasmType, Type, WasmExternType}, - vm::{self, Ctx}, + vm, }; use std::{ any::Any, @@ -16,14 +18,22 @@ use std::{ sync::Arc, }; +/// Wasm trap info. #[repr(C)] pub enum WasmTrapInfo { + /// Unreachable trap. Unreachable = 0, + /// Call indirect incorrect signature trap. IncorrectCallIndirectSignature = 1, + /// Memory out of bounds trap. MemoryOutOfBounds = 2, + /// Call indirect out of bounds trap. CallIndirectOOB = 3, + /// Illegal arithmetic trap. IllegalArithmetic = 4, + /// Misaligned atomic access trap. MisalignedAtomicAccess = 5, + /// Unknown trap. Unknown, } @@ -52,16 +62,24 @@ impl fmt::Display for WasmTrapInfo { /// of the `Func` struct. pub trait Kind {} -pub type Trampoline = unsafe extern "C" fn(*mut Ctx, NonNull, *const u64, *mut u64); +/// Aliases to an extern "C" type used as a trampoline to a function. +pub type Trampoline = unsafe extern "C" fn( + vmctx: *mut vm::Ctx, + func: NonNull, + args: *const u64, + rets: *mut u64, +); + +/// Aliases to an extern "C" type used to invoke a function. pub type Invoke = unsafe extern "C" fn( - Trampoline, - *mut Ctx, - NonNull, - *const u64, - *mut u64, - *mut WasmTrapInfo, - *mut Option>, - Option>, + trampoline: Trampoline, + vmctx: *mut vm::Ctx, + func: NonNull, + args: *const u64, + rets: *mut u64, + trap_info: *mut WasmTrapInfo, + user_error: *mut Option>, + extra: Option>, ) -> bool; /// TODO(lachlan): Naming TBD. @@ -75,6 +93,7 @@ pub struct Wasm { } impl Wasm { + /// Create new `Wasm` from given parts. pub unsafe fn from_raw_parts( trampoline: Trampoline, invoke: Invoke, @@ -91,40 +110,99 @@ impl Wasm { /// This type, as part of the `Func` type signature, represents a function that is created /// by the host. pub struct Host(()); + impl Kind for Wasm {} impl Kind for Host {} +/// Represents a list of WebAssembly values. pub trait WasmTypeList { + /// CStruct type. type CStruct; + + /// Array of return values. type RetArray: AsMut<[u64]>; + + /// Construct `Self` based on an array of returned values. fn from_ret_array(array: Self::RetArray) -> Self; + + /// Generates an empty array that will hold the returned values of + /// the WebAssembly function. fn empty_ret_array() -> Self::RetArray; + + /// Transforms C values into Rust values. fn from_c_struct(c_struct: Self::CStruct) -> Self; + + /// Transforms Rust values into C values. fn into_c_struct(self) -> Self::CStruct; + + /// Get types of the current values. fn types() -> &'static [Type]; + + /// This method is used to distribute the values onto a function, + /// e.g. `(1, 2).call(func, …)`. This form is unlikely to be used + /// directly in the code, see the `Func:call` implementation. unsafe fn call( self, f: NonNull, wasm: Wasm, - ctx: *mut Ctx, + ctx: *mut vm::Ctx, ) -> Result where Rets: WasmTypeList; } -pub trait ExternalFunction +/// Empty trait to specify the kind of `ExternalFunction`: With or +/// without a `vm::Ctx` argument. See the `ExplicitVmCtx` and the +/// `ImplicitVmCtx` structures. +/// +/// This type is never aimed to be used by a user. It is used by the +/// trait system to automatically generate an appropriate `wrap` +/// function. +pub trait ExternalFunctionKind {} + +/// This empty structure indicates that an external function must +/// contain an explicit `vm::Ctx` argument (at first position). +/// +/// ```rs,ignore +/// fn add_one(_: mut &vm::Ctx, x: i32) -> i32 { +/// x + 1 +/// } +/// ``` +pub struct ExplicitVmCtx {} + +/// This empty structure indicates that an external function has no +/// `vm::Ctx` argument (at first position). Its signature is: +/// +/// ```rs,ignore +/// fn add_one(x: i32) -> i32 { +/// x + 1 +/// } +/// ``` +pub struct ImplicitVmCtx {} + +impl ExternalFunctionKind for ExplicitVmCtx {} +impl ExternalFunctionKind for ImplicitVmCtx {} + +/// Represents a function that can be converted to a `vm::Func` +/// (function pointer) that can be called within WebAssembly. +pub trait ExternalFunction where + Kind: ExternalFunctionKind, Args: WasmTypeList, Rets: WasmTypeList, { - fn to_raw(&self) -> NonNull; + /// Conver to function pointer. + fn to_raw(self) -> (NonNull, Option>); } +/// Represents a TrapEarly type. pub trait TrapEarly where Rets: WasmTypeList, { + /// The error type for this trait. type Error: 'static; + /// Get returns or error result. fn report(self) -> Result; } @@ -149,22 +227,18 @@ where } } -// pub fn Func<'a, Args, Rets, F>(f: F) -> Func<'a, Args, Rets, Unsafe> -// where -// Args: WasmTypeList, -// Rets: WasmTypeList, -// F: ExternalFunction -// { -// Func::new(f) -// } - +/// Represents a function that can be used by WebAssembly. pub struct Func<'a, Args = (), Rets = (), Inner: Kind = Wasm> { inner: Inner, - f: NonNull, - ctx: *mut Ctx, + func: NonNull, + func_env: Option>, + vmctx: *mut vm::Ctx, _phantom: PhantomData<(&'a (), Args, Rets)>, } +unsafe impl<'a, Args, Rets> Send for Func<'a, Args, Rets, Wasm> {} +unsafe impl<'a, Args, Rets> Send for Func<'a, Args, Rets, Host> {} + impl<'a, Args, Rets> Func<'a, Args, Rets, Wasm> where Args: WasmTypeList, @@ -172,19 +246,22 @@ where { pub(crate) unsafe fn from_raw_parts( inner: Wasm, - f: NonNull, - ctx: *mut Ctx, + func: NonNull, + func_env: Option>, + vmctx: *mut vm::Ctx, ) -> Func<'a, Args, Rets, Wasm> { Func { inner, - f, - ctx, + func, + func_env, + vmctx, _phantom: PhantomData, } } + /// Get the underlying func pointer. pub fn get_vm_func(&self) -> NonNull { - self.f + self.func } } @@ -193,14 +270,19 @@ where Args: WasmTypeList, Rets: WasmTypeList, { - pub fn new(f: F) -> Func<'a, Args, Rets, Host> + /// Creates a new `Func`. + pub fn new(func: F) -> Func<'a, Args, Rets, Host> where - F: ExternalFunction, + Kind: ExternalFunctionKind, + F: ExternalFunction, { + let (func, func_env) = func.to_raw(); + Func { inner: Host(()), - f: f.to_raw(), - ctx: ptr::null_mut(), + func, + func_env, + vmctx: ptr::null_mut(), _phantom: PhantomData, } } @@ -212,9 +294,12 @@ where Rets: WasmTypeList, Inner: Kind, { + /// Returns the types of the function inputs. pub fn params(&self) -> &'static [Type] { Args::types() } + + /// Returns the types of the function outputs. pub fn returns(&self) -> &'static [Type] { Rets::types() } @@ -223,139 +308,112 @@ where impl WasmTypeList for Infallible { type CStruct = Infallible; type RetArray = [u64; 0]; + fn from_ret_array(_: Self::RetArray) -> Self { unreachable!() } + fn empty_ret_array() -> Self::RetArray { unreachable!() } + fn from_c_struct(_: Self::CStruct) -> Self { unreachable!() } + fn into_c_struct(self) -> Self::CStruct { unreachable!() } + fn types() -> &'static [Type] { &[] } + #[allow(non_snake_case)] - unsafe fn call( + unsafe fn call( self, _: NonNull, _: Wasm, - _: *mut Ctx, - ) -> Result { + _: *mut vm::Ctx, + ) -> Result + where + Rets: WasmTypeList, + { unreachable!() } } -impl WasmTypeList for (A,) { - type CStruct = S1; - type RetArray = [u64; 1]; - fn from_ret_array(array: Self::RetArray) -> Self { - (WasmExternType::from_native(NativeWasmType::from_binary( - array[0], - )),) - } - fn empty_ret_array() -> Self::RetArray { - [0u64] - } - fn from_c_struct(c_struct: Self::CStruct) -> Self { - let S1(a) = c_struct; - (WasmExternType::from_native(a),) - } - fn into_c_struct(self) -> Self::CStruct { - #[allow(unused_parens, non_snake_case)] - let (a,) = self; - S1(WasmExternType::to_native(a)) - } - fn types() -> &'static [Type] { - &[A::Native::TYPE] - } - #[allow(non_snake_case)] - unsafe fn call( - self, - f: NonNull, - wasm: Wasm, - ctx: *mut Ctx, - ) -> Result { - let (a,) = self; - let args = [a.to_native().to_binary()]; - let mut rets = Rets::empty_ret_array(); - let mut trap = WasmTrapInfo::Unknown; - let mut user_error = None; - - if (wasm.invoke)( - wasm.trampoline, - ctx, - f, - args.as_ptr(), - rets.as_mut().as_mut_ptr(), - &mut trap, - &mut user_error, - wasm.invoke_env, - ) { - Ok(Rets::from_ret_array(rets)) - } else { - if let Some(data) = user_error { - Err(RuntimeError::Error { data }) - } else { - Err(RuntimeError::Trap { - msg: trap.to_string().into(), - }) - } - } - } -} - -impl<'a, A: WasmExternType, Rets> Func<'a, (A,), Rets, Wasm> -where - Rets: WasmTypeList, -{ - pub fn call(&self, a: A) -> Result { - unsafe { ::call(a, self.f, self.inner, self.ctx) } - } -} - macro_rules! impl_traits { ( [$repr:ident] $struct_name:ident, $( $x:ident ),* ) => { + /// Struct for typed funcs. #[repr($repr)] - pub struct $struct_name <$( $x: WasmExternType ),*> ( $( <$x as WasmExternType>::Native ),* ); + pub struct $struct_name< $( $x ),* > ( $( <$x as WasmExternType>::Native ),* ) + where + $( $x: WasmExternType ),*; - impl< $( $x: WasmExternType, )* > WasmTypeList for ( $( $x ),* ) { + impl< $( $x ),* > WasmTypeList for ( $( $x ),* ) + where + $( $x: WasmExternType ),* + { type CStruct = $struct_name<$( $x ),*>; + type RetArray = [u64; count_idents!( $( $x ),* )]; + fn from_ret_array(array: Self::RetArray) -> Self { #[allow(non_snake_case)] let [ $( $x ),* ] = array; + ( $( WasmExternType::from_native(NativeWasmType::from_binary($x)) ),* ) } + fn empty_ret_array() -> Self::RetArray { [0; count_idents!( $( $x ),* )] } + fn from_c_struct(c_struct: Self::CStruct) -> Self { #[allow(non_snake_case)] let $struct_name ( $( $x ),* ) = c_struct; + ( $( WasmExternType::from_native($x) ),* ) } + + #[allow(unused_parens, non_snake_case)] fn into_c_struct(self) -> Self::CStruct { - #[allow(unused_parens, non_snake_case)] let ( $( $x ),* ) = self; + $struct_name ( $( WasmExternType::to_native($x) ),* ) } + fn types() -> &'static [Type] { - &[$( $x::Native::TYPE, )*] + &[$( $x::Native::TYPE ),*] } - #[allow(non_snake_case)] - unsafe fn call(self, f: NonNull, wasm: Wasm, ctx: *mut Ctx) -> Result { - #[allow(unused_parens)] + + #[allow(unused_parens, non_snake_case)] + unsafe fn call( + self, + f: NonNull, + wasm: Wasm, + ctx: *mut vm::Ctx, + ) -> Result + where + Rets: WasmTypeList + { let ( $( $x ),* ) = self; let args = [ $( $x.to_native().to_binary()),* ]; let mut rets = Rets::empty_ret_array(); let mut trap = WasmTrapInfo::Unknown; let mut user_error = None; - if (wasm.invoke)(wasm.trampoline, ctx, f, args.as_ptr(), rets.as_mut().as_mut_ptr(), &mut trap, &mut user_error, wasm.invoke_env) { + if (wasm.invoke)( + wasm.trampoline, + ctx, + f, + args.as_ptr(), + rets.as_mut().as_mut_ptr(), + &mut trap, + &mut user_error, + wasm.invoke_env + ) { Ok(Rets::from_ret_array(rets)) } else { if let Some(data) = user_error { @@ -367,49 +425,250 @@ macro_rules! impl_traits { } } - impl< $( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly, FN: Fn( &mut Ctx $( ,$x )* ) -> Trap> ExternalFunction<($( $x ),*), Rets> for FN { + impl< $( $x, )* Rets, Trap, FN > ExternalFunction for FN + where + $( $x: WasmExternType, )* + Rets: WasmTypeList, + Trap: TrapEarly, + FN: Fn(&mut vm::Ctx $( , $x )*) -> Trap + 'static, + { #[allow(non_snake_case)] - fn to_raw(&self) -> NonNull { - if mem::size_of::() == 0 { - /// This is required for the llvm backend to be able to unwind through this function. - #[cfg_attr(nightly, unwind(allowed))] - extern fn wrap<$( $x: WasmExternType, )* Rets: WasmTypeList, Trap: TrapEarly, FN: Fn( &mut Ctx $( ,$x )* ) -> Trap>( ctx: &mut Ctx $( ,$x: <$x as WasmExternType>::Native )* ) -> Rets::CStruct { - let f: FN = unsafe { mem::transmute_copy(&()) }; + fn to_raw(self) -> (NonNull, Option>) { + // The `wrap` function is a wrapper around the + // imported function. It manages the argument passed + // to the imported function (in this case, the + // `vmctx` along with the regular WebAssembly + // arguments), and it manages the trapping. + // + // It is also required for the LLVM backend to be + // able to unwind through this function. + #[cfg_attr(nightly, unwind(allowed))] + extern fn wrap<$( $x, )* Rets, Trap, FN>( + vmctx: &vm::Ctx $( , $x: <$x as WasmExternType>::Native )* + ) -> Rets::CStruct + where + $( $x: WasmExternType, )* + Rets: WasmTypeList, + Trap: TrapEarly, + FN: Fn(&mut vm::Ctx, $( $x, )*) -> Trap, + { + // Get the pointer to this `wrap` function. + let self_pointer = wrap::<$( $x, )* Rets, Trap, FN> as *const vm::Func; - let err = match panic::catch_unwind(panic::AssertUnwindSafe(|| { - f( ctx $( ,WasmExternType::from_native($x) )* ).report() - })) { - Ok(Ok(returns)) => return returns.into_c_struct(), - Ok(Err(err)) => { - let b: Box<_> = err.into(); - b as Box - }, - Err(err) => err, - }; + // Get the collection of imported functions. + let vm_imported_functions = unsafe { &(*vmctx.import_backing).vm_functions }; - unsafe { - (&*ctx.module).runnable_module.do_early_trap(err) - } + // Retrieve the `vm::FuncCtx`. + let mut func_ctx: NonNull = vm_imported_functions + .iter() + .find_map(|(_, imported_func)| { + if imported_func.func == self_pointer { + Some(imported_func.func_ctx) + } else { + None + } + }) + .expect("Import backing is not well-formed, cannot find `func_ctx`."); + let func_ctx = unsafe { func_ctx.as_mut() }; + + // Extract `vm::Ctx` from `vm::FuncCtx`. The + // pointer is always non-null. + let vmctx = unsafe { func_ctx.vmctx.as_mut() }; + + // Extract `vm::FuncEnv` from `vm::FuncCtx`. + let func_env = func_ctx.func_env; + + let func: &FN = match func_env { + // The imported function is a regular + // function, a closure without a captured + // environment, or a closure with a captured + // environment. + Some(func_env) => unsafe { + let func: NonNull = func_env.cast(); + + &*func.as_ptr() + }, + + // This branch is supposed to be unreachable. + None => unreachable!() + }; + + // Catch unwind in case of errors. + let err = match panic::catch_unwind( + panic::AssertUnwindSafe( + || { + func(vmctx $( , WasmExternType::from_native($x) )* ).report() + // ^^^^^ The imported function + // expects `vm::Ctx` as first + // argument; provide it. + } + ) + ) { + Ok(Ok(returns)) => return returns.into_c_struct(), + Ok(Err(err)) => { + let b: Box<_> = err.into(); + b as Box + }, + Err(err) => err, + }; + + // At this point, there is an error that needs to + // be trapped. + unsafe { + (&*vmctx.module).runnable_module.do_early_trap(err) } - - NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap() - } else { - assert_eq!(mem::size_of::(), mem::size_of::(), "you cannot use a closure that captures state for `Func`."); - NonNull::new(unsafe { - ::std::mem::transmute_copy::<_, *mut vm::Func>(self) - }).unwrap() } + + // Extract the captured environment of the imported + // function if any. + let func_env: Option> = + // `FN` is a function pointer, or a closure + // _without_ a captured environment. + if mem::size_of::() == 0 { + NonNull::new(&self as *const _ as *mut vm::FuncEnv) + } + // `FN` is a closure _with_ a captured + // environment. + else { + NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast) + }; + + ( + NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap(), + func_env + ) } } - impl<'a, $( $x: WasmExternType, )* Rets> Func<'a, ( $( $x ),* ), Rets, Wasm> + impl< $( $x, )* Rets, Trap, FN > ExternalFunction for FN where + $( $x: WasmExternType, )* + Rets: WasmTypeList, + Trap: TrapEarly, + FN: Fn($( $x, )*) -> Trap + 'static, + { + #[allow(non_snake_case)] + fn to_raw(self) -> (NonNull, Option>) { + // The `wrap` function is a wrapper around the + // imported function. It manages the argument passed + // to the imported function (in this case, only the + // regular WebAssembly arguments), and it manages the + // trapping. + // + // It is also required for the LLVM backend to be + // able to unwind through this function. + #[cfg_attr(nightly, unwind(allowed))] + extern fn wrap<$( $x, )* Rets, Trap, FN>( + vmctx: &vm::Ctx $( , $x: <$x as WasmExternType>::Native )* + ) -> Rets::CStruct + where + $( $x: WasmExternType, )* + Rets: WasmTypeList, + Trap: TrapEarly, + FN: Fn($( $x, )*) -> Trap, + { + // Get the pointer to this `wrap` function. + let self_pointer = wrap::<$( $x, )* Rets, Trap, FN> as *const vm::Func; + + // Get the collection of imported functions. + let vm_imported_functions = unsafe { &(*vmctx.import_backing).vm_functions }; + + // Retrieve the `vm::FuncCtx`. + let mut func_ctx: NonNull = vm_imported_functions + .iter() + .find_map(|(_, imported_func)| { + if imported_func.func == self_pointer { + Some(imported_func.func_ctx) + } else { + None + } + }) + .expect("Import backing is not well-formed, cannot find `func_ctx`."); + let func_ctx = unsafe { func_ctx.as_mut() }; + + // Extract `vm::Ctx` from `vm::FuncCtx`. The + // pointer is always non-null. + let vmctx = unsafe { func_ctx.vmctx.as_mut() }; + + // Extract `vm::FuncEnv` from `vm::FuncCtx`. + let func_env = func_ctx.func_env; + + let func: &FN = match func_env { + // The imported function is a regular + // function, a closure without a captured + // environment, or a closure with a captured + // environment. + Some(func_env) => unsafe { + let func: NonNull = func_env.cast(); + + &*func.as_ptr() + }, + + // This branch is supposed to be unreachable. + None => unreachable!() + }; + + // Catch unwind in case of errors. + let err = match panic::catch_unwind( + panic::AssertUnwindSafe( + || { + func($( WasmExternType::from_native($x), )* ).report() + } + ) + ) { + Ok(Ok(returns)) => return returns.into_c_struct(), + Ok(Err(err)) => { + let b: Box<_> = err.into(); + b as Box + }, + Err(err) => err, + }; + + // At this point, there is an error that needs to + // be trapped. + unsafe { + (&*vmctx.module).runnable_module.do_early_trap(err) + } + } + + // Extract the captured environment of the imported + // function if any. + let func_env: Option> = + // `FN` is a function pointer, or a closure + // _without_ a captured environment. + if mem::size_of::() == 0 { + NonNull::new(&self as *const _ as *mut vm::FuncEnv) + } + // `FN` is a closure _with_ a captured + // environment. + else { + NonNull::new(Box::into_raw(Box::new(self))).map(NonNull::cast) + }; + + ( + NonNull::new(wrap::<$( $x, )* Rets, Trap, Self> as *mut vm::Func).unwrap(), + func_env + ) + } + } + + impl<'a $( , $x )*, Rets> Func<'a, ( $( $x ),* ), Rets, Wasm> + where + $( $x: WasmExternType, )* Rets: WasmTypeList, { + /// Call the typed func and return results. #[allow(non_snake_case)] pub fn call(&self, $( $x: $x, )* ) -> Result { #[allow(unused_parens)] - unsafe { <( $( $x ),* ) as WasmTypeList>::call(( $($x),* ), self.f, self.inner, self.ctx) } + unsafe { + <( $( $x ),* ) as WasmTypeList>::call( + ( $( $x ),* ), + self.func, + self.inner, + self.vmctx + ) + } } } }; @@ -445,8 +704,11 @@ where Inner: Kind, { fn to_export(&self) -> Export { - let func = unsafe { FuncPointer::new(self.f.as_ptr()) }; - let ctx = Context::Internal; + let func = unsafe { FuncPointer::new(self.func.as_ptr()) }; + let ctx = match self.func_env { + func_env @ Some(_) => Context::ExternalWithEnv(self.vmctx, func_env), + None => Context::Internal, + }; let signature = Arc::new(FuncSig::new(Args::types(), Rets::types())); Export::Function { @@ -460,9 +722,65 @@ where #[cfg(test)] mod tests { use super::*; + + macro_rules! test_func_arity_n { + ($test_name:ident, $($x:ident),*) => { + #[test] + fn $test_name() { + use crate::vm; + + fn with_vmctx(_: &mut vm::Ctx, $($x: i32),*) -> i32 { + vec![$($x),*].iter().sum() + } + + fn without_vmctx($($x: i32),*) -> i32 { + vec![$($x),*].iter().sum() + } + + let _ = Func::new(with_vmctx); + let _ = Func::new(without_vmctx); + let _ = Func::new(|_: &mut vm::Ctx, $($x: i32),*| -> i32 { + vec![$($x),*].iter().sum() + }); + let _ = Func::new(|$($x: i32),*| -> i32 { + vec![$($x),*].iter().sum() + }); + } + } + } + + #[test] + fn test_func_arity_0() { + fn foo(_: &mut vm::Ctx) -> i32 { + 0 + } + + fn bar() -> i32 { + 0 + } + + let _ = Func::new(foo); + let _ = Func::new(bar); + let _ = Func::new(|_: &mut vm::Ctx| -> i32 { 0 }); + let _ = Func::new(|| -> i32 { 0 }); + } + + test_func_arity_n!(test_func_arity_1, a); + test_func_arity_n!(test_func_arity_2, a, b); + test_func_arity_n!(test_func_arity_3, a, b, c); + test_func_arity_n!(test_func_arity_4, a, b, c, d); + test_func_arity_n!(test_func_arity_5, a, b, c, d, e); + test_func_arity_n!(test_func_arity_6, a, b, c, d, e, f); + test_func_arity_n!(test_func_arity_7, a, b, c, d, e, f, g); + test_func_arity_n!(test_func_arity_8, a, b, c, d, e, f, g, h); + test_func_arity_n!(test_func_arity_9, a, b, c, d, e, f, g, h, i); + test_func_arity_n!(test_func_arity_10, a, b, c, d, e, f, g, h, i, j); + test_func_arity_n!(test_func_arity_11, a, b, c, d, e, f, g, h, i, j, k); + test_func_arity_n!(test_func_arity_12, a, b, c, d, e, f, g, h, i, j, k, l); + #[test] fn test_call() { - fn foo(_ctx: &mut Ctx, a: i32, b: i32) -> (i32, i32) { + fn foo(_ctx: &mut vm::Ctx, a: i32, b: i32) -> (i32, i32) { (a, b) } @@ -473,7 +791,7 @@ mod tests { fn test_imports() { use crate::{func, imports}; - fn foo(_ctx: &mut Ctx, a: i32) -> i32 { + fn foo(_ctx: &mut vm::Ctx, a: i32) -> i32 { a } diff --git a/lib/runtime-core/src/types.rs b/lib/runtime-core/src/types.rs index ad00a1588..1b19bbc3d 100644 --- a/lib/runtime-core/src/types.rs +++ b/lib/runtime-core/src/types.rs @@ -1,3 +1,6 @@ +//! The runtime types modules represent type used within the wasm runtime and helper functions to +//! convert to other represenations. + use crate::{memory::MemoryType, module::ModuleInfo, structures::TypedIndex, units::Pages}; use std::borrow::Cow; @@ -41,6 +44,7 @@ pub enum Value { } impl Value { + /// The `Type` of this `Value`. pub fn ty(&self) -> Type { match self { Value::I32(_) => Type::I32, @@ -51,6 +55,7 @@ impl Value { } } + /// Convert this `Value` to a u128 binary representation. pub fn to_u128(&self) -> u128 { match *self { Value::I32(x) => x as u128, @@ -92,12 +97,16 @@ impl From for Value { } } +/// Represents a native wasm type. pub unsafe trait NativeWasmType: Copy + Into where Self: Sized, { + /// Type for this `NativeWasmType`. const TYPE: Type; + /// Convert from u64 bites to self. fn from_binary(bits: u64) -> Self; + /// Convert self to u64 binary representation. fn to_binary(self) -> u64; } @@ -138,12 +147,16 @@ unsafe impl NativeWasmType for f64 { } } +/// A trait to represent a wasm extern type. pub unsafe trait WasmExternType: Copy where Self: Sized, { + /// Native wasm type for this `WasmExternType`. type Native: NativeWasmType; + /// Convert from given `Native` type to self. fn from_native(native: Self::Native) -> Self; + /// Convert self to `Native` type. fn to_native(self) -> Self::Native; } @@ -255,6 +268,7 @@ unsafe impl WasmExternType for f64 { // fn swap(&self, other: Self::Primitive) -> Self::Primitive; // } +/// Trait for a Value type. pub unsafe trait ValueType: Copy where Self: Sized, @@ -274,12 +288,15 @@ macro_rules! convert_value_impl { convert_value_impl!(u8, i8, u16, i16, u32, i32, u64, i64, f32, f64); +/// Kinds of element types. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub enum ElementType { /// Any wasm function. Anyfunc, } +/// Describes the properties of a table including the element types, minimum and optional maximum, +/// number of elements in the table. #[derive(Serialize, Deserialize, Debug, Clone, Copy)] pub struct TableDescriptor { /// Type of data stored in this table. @@ -315,18 +332,22 @@ pub enum Initializer { /// Describes the mutability and type of a Global #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub struct GlobalDescriptor { + /// Mutable flag. pub mutable: bool, + /// Wasm type. pub ty: Type, } /// A wasm global. #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GlobalInit { + /// Global descriptor. pub desc: GlobalDescriptor, + /// Global initializer. pub init: Initializer, } -/// A wasm memory. +/// A wasm memory descriptor. #[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq)] pub struct MemoryDescriptor { /// The minimum number of allowed pages. @@ -335,16 +356,32 @@ pub struct MemoryDescriptor { pub maximum: Option, /// This memory can be shared between wasm threads. pub shared: bool, + /// The type of the memory + pub memory_type: MemoryType, } impl MemoryDescriptor { - pub fn memory_type(self) -> MemoryType { - match (self.maximum.is_some(), self.shared) { + /// Create a new memory descriptor with the given min/max pages and shared flag. + pub fn new(minimum: Pages, maximum: Option, shared: bool) -> Result { + let memory_type = match (maximum.is_some(), shared) { (true, true) => MemoryType::SharedStatic, (true, false) => MemoryType::Static, (false, false) => MemoryType::Dynamic, - (false, true) => panic!("shared memory without a max is not allowed"), - } + (false, true) => { + return Err("Max number of pages is required for shared memory".to_string()); + } + }; + Ok(MemoryDescriptor { + minimum, + maximum, + shared, + memory_type, + }) + } + + /// Returns the `MemoryType` for this descriptor. + pub fn memory_type(&self) -> MemoryType { + self.memory_type } pub(crate) fn fits_in_imported(&self, imported: MemoryDescriptor) -> bool { @@ -366,6 +403,7 @@ pub struct FuncSig { } impl FuncSig { + /// Creates a new function signatures with the given parameter and return types. pub fn new(params: Params, returns: Returns) -> Self where Params: Into>, @@ -377,14 +415,17 @@ impl FuncSig { } } + /// Parameter types. pub fn params(&self) -> &[Type] { &self.params } + /// Return types. pub fn returns(&self) -> &[Type] { &self.returns } + /// Returns true if parameter types match the function signature. pub fn check_param_value_types(&self, params: &[Value]) -> bool { self.params.len() == params.len() && self @@ -413,14 +454,18 @@ impl std::fmt::Display for FuncSig { } } +/// Trait that represents Local or Import. pub trait LocalImport { + /// Local type. type Local: TypedIndex; + /// Import type. type Import: TypedIndex; } #[rustfmt::skip] macro_rules! define_map_index { ($ty:ident) => { + /// Typed Index #[derive(Serialize, Deserialize)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct $ty (u32); @@ -461,6 +506,7 @@ define_map_index![ macro_rules! define_local_or_import { ($ty:ident, $local_ty:ident, $imported_ty:ident, $imports:ident) => { impl $ty { + /// Converts self into `LocalOrImport`. pub fn local_or_import(self, info: &ModuleInfo) -> LocalOrImport<$ty> { if self.index() < info.$imports.len() { LocalOrImport::Import(::Import::new(self.index())) @@ -471,12 +517,14 @@ macro_rules! define_local_or_import { } impl $local_ty { + /// Convert up. pub fn convert_up(self, info: &ModuleInfo) -> $ty { $ty ((self.index() + info.$imports.len()) as u32) } } impl $imported_ty { + /// Convert up. pub fn convert_up(self, _info: &ModuleInfo) -> $ty { $ty (self.index() as u32) } @@ -497,6 +545,7 @@ define_local_or_import![ (GlobalIndex | (LocalGlobalIndex, ImportedGlobalIndex): imported_globals), ]; +/// Index for signature. #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct SigIndex(u32); impl TypedIndex for SigIndex { @@ -511,11 +560,14 @@ impl TypedIndex for SigIndex { } } +/// Kind of local or import type. pub enum LocalOrImport where T: LocalImport, { + /// Local. Local(T::Local), + /// Import. Import(T::Import), } @@ -523,6 +575,7 @@ impl LocalOrImport where T: LocalImport, { + /// Returns `Some` if self is local, `None` if self is an import. pub fn local(self) -> Option { match self { LocalOrImport::Local(local) => Some(local), @@ -530,6 +583,7 @@ where } } + /// Returns `Some` if self is an import, `None` if self is local. pub fn import(self) -> Option { match self { LocalOrImport::Import(import) => Some(import), @@ -590,5 +644,4 @@ mod tests { f64::from_native(f64::from_binary((yf64).to_native().to_binary())) ); } - } diff --git a/lib/runtime-core/src/units.rs b/lib/runtime-core/src/units.rs index e8232efc8..2e8f2c0a5 100644 --- a/lib/runtime-core/src/units.rs +++ b/lib/runtime-core/src/units.rs @@ -1,12 +1,17 @@ +//! The units module provides common WebAssembly units like `Pages` and conversion functions into +//! other units. use crate::error::PageError; use std::{ fmt, ops::{Add, Sub}, }; +/// The page size in bytes of a wasm page. pub const WASM_PAGE_SIZE: usize = 65_536; +/// Tbe max number of wasm pages allowed. pub const WASM_MAX_PAGES: usize = 65_536; // From emscripten resize_heap implementation +/// The minimum number of wasm pages allowed. pub const WASM_MIN_PAGES: usize = 256; /// Units of WebAssembly pages (as specified to be 65,536 bytes). @@ -14,6 +19,7 @@ pub const WASM_MIN_PAGES: usize = 256; pub struct Pages(pub u32); impl Pages { + /// Checked add of Pages to Pages. pub fn checked_add(self, rhs: Pages) -> Result { let added = (self.0 as usize) + (rhs.0 as usize); if added <= WASM_MAX_PAGES { @@ -27,6 +33,7 @@ impl Pages { } } + /// Calculate number of bytes from pages. pub fn bytes(self) -> Bytes { self.into() } diff --git a/lib/runtime-core/src/vm.rs b/lib/runtime-core/src/vm.rs index 51f883318..a976336a6 100644 --- a/lib/runtime-core/src/vm.rs +++ b/lib/runtime-core/src/vm.rs @@ -1,15 +1,21 @@ +//! The runtime vm module contains data structures and helper functions used during runtime to +//! execute wasm instance functions. pub use crate::backing::{ImportBacking, LocalBacking, INTERNALS_SIZE}; use crate::{ + error::CallResult, + instance::call_func_with_index_inner, memory::{Memory, MemoryType}, module::{ModuleInfo, ModuleInner}, + sig_registry::SigRegistry, structures::TypedIndex, - types::{LocalOrImport, MemoryIndex}, + types::{LocalOrImport, MemoryIndex, TableIndex, Value}, vmcalls, }; use std::{ cell::UnsafeCell, ffi::c_void, - mem, ptr, + mem, + ptr::{self, NonNull}, sync::atomic::{AtomicUsize, Ordering}, sync::Once, }; @@ -32,6 +38,7 @@ use std::collections::HashMap; #[repr(C)] pub struct Ctx { // `internal` must be the first field of `Ctx`. + /// InternalCtx data field pub internal: InternalCtx, pub(crate) local_functions: *const *const Func, @@ -39,7 +46,9 @@ pub struct Ctx { /// These are pointers to things that are known to be owned /// by the owning `Instance`. pub local_backing: *mut LocalBacking, + /// Mutable pointer to import data pub import_backing: *mut ImportBacking, + /// Const pointer to module inner data pub module: *const ModuleInner, /// This is intended to be user-supplied, per-instance @@ -106,22 +115,31 @@ pub struct InternalCtx { /// modules safely. pub dynamic_sigindices: *const SigId, + /// Const pointer to Intrinsics. pub intrinsics: *const Intrinsics, + /// Stack lower bound. pub stack_lower_bound: *mut u8, + /// Mutable pointer to memory base. pub memory_base: *mut u8, + /// Memory bound. pub memory_bound: usize, + /// Mutable pointer to internal fields. pub internals: *mut [u64; INTERNALS_SIZE], // TODO: Make this dynamic? + /// Interrupt signal mem. pub interrupt_signal_mem: *mut u8, } static INTERNAL_FIELDS: AtomicUsize = AtomicUsize::new(0); +/// An internal field. pub struct InternalField { + /// Init once field. init: Once, + /// Inner field. inner: UnsafeCell, } @@ -129,6 +147,7 @@ unsafe impl Send for InternalField {} unsafe impl Sync for InternalField {} impl InternalField { + /// Allocate and return an `InternalField`. pub const fn allocate() -> InternalField { InternalField { init: Once::new(), @@ -136,6 +155,7 @@ impl InternalField { } } + /// Get the index of this `InternalField`. pub fn index(&self) -> usize { let inner: *mut usize = self.inner.get(); self.init.call_once(|| { @@ -153,9 +173,12 @@ impl InternalField { } } +/// A container for VM instrinsic functions #[repr(C)] pub struct Intrinsics { + /// Const pointer to memory grow `Func`. pub memory_grow: *const Func, + /// Const pointer to memory size `Func`. pub memory_size: *const Func, /*pub memory_grow: unsafe extern "C" fn( ctx: &mut Ctx, @@ -172,27 +195,33 @@ unsafe impl Send for Intrinsics {} unsafe impl Sync for Intrinsics {} impl Intrinsics { + /// Memory grow offset #[allow(clippy::erasing_op)] pub fn offset_memory_grow() -> u8 { (0 * ::std::mem::size_of::()) as u8 } + /// Memory size offset pub fn offset_memory_size() -> u8 { (1 * ::std::mem::size_of::()) as u8 } } +/// Local static memory intrinsics pub static INTRINSICS_LOCAL_STATIC_MEMORY: Intrinsics = Intrinsics { memory_grow: vmcalls::local_static_memory_grow as _, memory_size: vmcalls::local_static_memory_size as _, }; +/// Local dynamic memory intrinsics pub static INTRINSICS_LOCAL_DYNAMIC_MEMORY: Intrinsics = Intrinsics { memory_grow: vmcalls::local_dynamic_memory_grow as _, memory_size: vmcalls::local_dynamic_memory_size as _, }; +/// Imported static memory intrinsics pub static INTRINSICS_IMPORTED_STATIC_MEMORY: Intrinsics = Intrinsics { memory_grow: vmcalls::imported_static_memory_grow as _, memory_size: vmcalls::imported_static_memory_size as _, }; +/// Imported dynamic memory intrinsics pub static INTRINSICS_IMPORTED_DYNAMIC_MEMORY: Intrinsics = Intrinsics { memory_grow: vmcalls::imported_dynamic_memory_grow as _, memory_size: vmcalls::imported_dynamic_memory_size as _, @@ -393,6 +422,41 @@ impl Ctx { (*self.internal.internals)[field.index()] = value; } } + + /// Calls a host or Wasm function at the given table index + pub fn call_with_table_index( + &mut self, + index: TableIndex, + args: &[Value], + ) -> CallResult> { + let anyfunc_table = + unsafe { &*((**self.internal.tables).table as *mut crate::table::AnyfuncTable) }; + let Anyfunc { func, ctx, sig_id } = anyfunc_table.backing[index.index()]; + + let signature = SigRegistry.lookup_signature(unsafe { std::mem::transmute(sig_id.0) }); + let mut rets = vec![]; + + let wasm = { + let module = unsafe { &*self.module }; + let runnable = &module.runnable_module; + + let sig_index = SigRegistry.lookup_sig_index(signature.clone()); + runnable + .get_trampoline(&module.info, sig_index) + .expect("wasm trampoline") + }; + + call_func_with_index_inner( + ctx, + NonNull::new(func as *mut _).unwrap(), + &signature, + wasm, + args, + &mut rets, + )?; + + Ok(rets) + } } #[doc(hidden)] @@ -459,31 +523,80 @@ impl Ctx { } } -enum InnerFunc {} -/// Used to provide type safety (ish) for passing around function pointers. -/// The typesystem ensures this cannot be dereferenced since an -/// empty enum cannot actually exist. -#[repr(C)] -pub struct Func(InnerFunc); +/// Represents a function pointer. It is mostly used in the +/// `typed_func` module within the `wrap` functions, to wrap imported +/// functions. +#[repr(transparent)] +pub struct Func(pub(self) *mut c_void); -/// An imported function, which contains the vmctx that owns this function. +/// Represents a function environment pointer, like a captured +/// environment of a closure. It is mostly used in the `typed_func` +/// module within the `wrap` functions, to wrap imported functions. +#[repr(transparent)] +pub struct FuncEnv(pub(self) *mut c_void); + +/// Represents a function context. It is used by imported functions +/// only. +#[derive(Debug)] +#[repr(C)] +pub struct FuncCtx { + /// The `Ctx` pointer. + pub(crate) vmctx: NonNull, + + /// A pointer to the function environment. It is used by imported + /// functions only to store the pointer to the real host function, + /// whether it is a regular function, or a closure with or without + /// a captured environment. + pub(crate) func_env: Option>, +} + +impl FuncCtx { + /// Offset to `vmctx`. + pub fn offset_vmctx() -> u8 { + 0 * (mem::size_of::() as u8) + } + + /// Offset to `func_env`. + pub fn offset_func_env() -> u8 { + 1 * (mem::size_of::() as u8) + } + + /// Size of a `FuncCtx`. + pub fn size() -> u8 { + mem::size_of::() as u8 + } +} + +/// An imported function is a function pointer associated to a +/// function context. #[derive(Debug, Clone)] #[repr(C)] pub struct ImportedFunc { - pub func: *const Func, - pub vmctx: *mut Ctx, + /// Const pointer to `Func`. + pub(crate) func: *const Func, + + /// Mutable non-null pointer to `FuncCtx`. + pub(crate) func_ctx: NonNull, } +// Manually implemented because ImportedFunc contains raw pointers +// directly; `Func` is marked Send (But `Ctx` actually isn't! (TODO: +// review this, shouldn't `Ctx` be Send?)) +unsafe impl Send for ImportedFunc {} + impl ImportedFunc { + /// Offset to func. #[allow(clippy::erasing_op)] // TODO pub fn offset_func() -> u8 { 0 * (mem::size_of::() as u8) } - pub fn offset_vmctx() -> u8 { + /// Offset to func_ctx. + pub fn offset_func_ctx() -> u8 { 1 * (mem::size_of::() as u8) } + /// Size of an `ImportedFunc`. pub fn size() -> u8 { mem::size_of::() as u8 } @@ -501,16 +614,22 @@ pub struct LocalTable { pub table: *mut (), } +// manually implemented because LocalTable contains raw pointers directly +unsafe impl Send for LocalTable {} + impl LocalTable { + /// Offset to base. #[allow(clippy::erasing_op)] // TODO pub fn offset_base() -> u8 { 0 * (mem::size_of::() as u8) } + /// Offset count. pub fn offset_count() -> u8 { 1 * (mem::size_of::() as u8) } + /// Size of a `LocalTable`. pub fn size() -> u8 { mem::size_of::() as u8 } @@ -530,16 +649,22 @@ pub struct LocalMemory { pub memory: *mut (), } +// manually implemented because LocalMemory contains raw pointers +unsafe impl Send for LocalMemory {} + impl LocalMemory { + /// Offset base. #[allow(clippy::erasing_op)] // TODO pub fn offset_base() -> u8 { 0 * (mem::size_of::() as u8) } + /// Offset bound. pub fn offset_bound() -> u8 { 1 * (mem::size_of::() as u8) } + /// Size of a `LocalMemory`. pub fn size() -> u8 { mem::size_of::() as u8 } @@ -549,24 +674,29 @@ impl LocalMemory { #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct LocalGlobal { + /// Data. pub data: u128, } impl LocalGlobal { + /// Offset data. #[allow(clippy::erasing_op)] // TODO pub fn offset_data() -> u8 { 0 * (mem::size_of::() as u8) } + /// A null `LocalGlobal`. pub fn null() -> Self { Self { data: 0 } } + /// Size of a `LocalGlobal`. pub fn size() -> u8 { mem::size_of::() as u8 } } +/// Identifier for a function signature. #[derive(Debug, Clone, Copy)] #[repr(transparent)] pub struct SigId(pub u32); @@ -575,12 +705,19 @@ pub struct SigId(pub u32); #[derive(Debug, Clone, Copy)] #[repr(C)] pub struct Anyfunc { + /// Const pointer to `Func`. pub func: *const Func, + /// Mutable pointer to `Ctx`. pub ctx: *mut Ctx, + /// Sig id of this function pub sig_id: SigId, } +// manually implemented because Anyfunc contains raw pointers directly +unsafe impl Send for Anyfunc {} + impl Anyfunc { + /// A null `Anyfunc` value. pub fn null() -> Self { Self { func: ptr::null(), @@ -589,19 +726,23 @@ impl Anyfunc { } } + /// The offset for this func. #[allow(clippy::erasing_op)] // TODO pub fn offset_func() -> u8 { 0 * (mem::size_of::() as u8) } + /// The offset of the vmctx. pub fn offset_vmctx() -> u8 { 1 * (mem::size_of::() as u8) } + /// The offset of the sig id. pub fn offset_sig_id() -> u8 { 2 * (mem::size_of::() as u8) } + /// The size of `Anyfunc`. pub fn size() -> u8 { mem::size_of::() as u8 } @@ -609,93 +750,160 @@ impl Anyfunc { #[cfg(test)] mod vm_offset_tests { - use super::{Anyfunc, Ctx, ImportedFunc, InternalCtx, LocalGlobal, LocalMemory, LocalTable}; + use super::{ + Anyfunc, Ctx, FuncCtx, ImportedFunc, InternalCtx, LocalGlobal, LocalMemory, LocalTable, + }; + + // Inspired by https://internals.rust-lang.org/t/discussion-on-offset-of/7440/2. + macro_rules! offset_of { + ($struct:path, $field:ident) => {{ + fn offset() -> usize { + use std::mem; + + let structure = mem::MaybeUninit::<$struct>::uninit(); + + let &$struct { + $field: ref field, .. + } = unsafe { &*structure.as_ptr() }; + + let offset = + (field as *const _ as usize).wrapping_sub(&structure as *const _ as usize); + + assert!((0..=mem::size_of_val(&structure)).contains(&offset)); + + offset + } + + offset() + }}; + } + + #[test] + fn offset_of() { + use std::{mem, ptr::NonNull}; + + struct S0; + + #[repr(C)] + struct S1 { + f1: u8, + f2: u16, + f3: u32, + f4: u64, + f5: u128, + f6: f32, + f7: f64, + f8: NonNull, + f9: Option>, + f10: *mut S0, + z: u8, + } + + assert_eq!(offset_of!(S1, f1), 0); + assert_eq!(offset_of!(S1, f2), 2); + assert_eq!(offset_of!(S1, f3), 4); + assert_eq!(offset_of!(S1, f4), 8); + assert_eq!(offset_of!(S1, f5), 16); + assert_eq!(offset_of!(S1, f6), 32); + assert_eq!(offset_of!(S1, f7), 40); + assert_eq!(offset_of!(S1, f8), 40 + mem::size_of::()); + assert_eq!(offset_of!(S1, f9), 48 + mem::size_of::()); + assert_eq!(offset_of!(S1, f10), 56 + mem::size_of::()); + assert_eq!(offset_of!(S1, z), 64 + mem::size_of::()); + } #[test] fn vmctx() { - assert_eq!(0usize, offset_of!(Ctx => internal).get_byte_offset(),); + assert_eq!(0usize, offset_of!(Ctx, internal)); assert_eq!( Ctx::offset_memories() as usize, - offset_of!(InternalCtx => memories).get_byte_offset(), + offset_of!(InternalCtx, memories), ); assert_eq!( Ctx::offset_tables() as usize, - offset_of!(InternalCtx => tables).get_byte_offset(), + offset_of!(InternalCtx, tables), ); assert_eq!( Ctx::offset_globals() as usize, - offset_of!(InternalCtx => globals).get_byte_offset(), + offset_of!(InternalCtx, globals), ); assert_eq!( Ctx::offset_imported_memories() as usize, - offset_of!(InternalCtx => imported_memories).get_byte_offset(), + offset_of!(InternalCtx, imported_memories), ); assert_eq!( Ctx::offset_imported_tables() as usize, - offset_of!(InternalCtx => imported_tables).get_byte_offset(), + offset_of!(InternalCtx, imported_tables), ); assert_eq!( Ctx::offset_imported_globals() as usize, - offset_of!(InternalCtx => imported_globals).get_byte_offset(), + offset_of!(InternalCtx, imported_globals), ); assert_eq!( Ctx::offset_imported_funcs() as usize, - offset_of!(InternalCtx => imported_funcs).get_byte_offset(), + offset_of!(InternalCtx, imported_funcs), ); assert_eq!( Ctx::offset_intrinsics() as usize, - offset_of!(InternalCtx => intrinsics).get_byte_offset(), + offset_of!(InternalCtx, intrinsics), ); assert_eq!( Ctx::offset_stack_lower_bound() as usize, - offset_of!(InternalCtx => stack_lower_bound).get_byte_offset(), + offset_of!(InternalCtx, stack_lower_bound), ); assert_eq!( Ctx::offset_memory_base() as usize, - offset_of!(InternalCtx => memory_base).get_byte_offset(), + offset_of!(InternalCtx, memory_base), ); assert_eq!( Ctx::offset_memory_bound() as usize, - offset_of!(InternalCtx => memory_bound).get_byte_offset(), + offset_of!(InternalCtx, memory_bound), ); assert_eq!( Ctx::offset_internals() as usize, - offset_of!(InternalCtx => internals).get_byte_offset(), + offset_of!(InternalCtx, internals), ); assert_eq!( Ctx::offset_interrupt_signal_mem() as usize, - offset_of!(InternalCtx => interrupt_signal_mem).get_byte_offset(), + offset_of!(InternalCtx, interrupt_signal_mem), ); assert_eq!( Ctx::offset_local_functions() as usize, - offset_of!(Ctx => local_functions).get_byte_offset(), + offset_of!(Ctx, local_functions), ); } + #[test] + fn func_ctx() { + assert_eq!(FuncCtx::offset_vmctx() as usize, 0,); + + assert_eq!(FuncCtx::offset_func_env() as usize, 8,); + } + #[test] fn imported_func() { assert_eq!( ImportedFunc::offset_func() as usize, - offset_of!(ImportedFunc => func).get_byte_offset(), + offset_of!(ImportedFunc, func), ); assert_eq!( - ImportedFunc::offset_vmctx() as usize, - offset_of!(ImportedFunc => vmctx).get_byte_offset(), + ImportedFunc::offset_func_ctx() as usize, + offset_of!(ImportedFunc, func_ctx), ); } @@ -703,12 +911,12 @@ mod vm_offset_tests { fn local_table() { assert_eq!( LocalTable::offset_base() as usize, - offset_of!(LocalTable => base).get_byte_offset(), + offset_of!(LocalTable, base), ); assert_eq!( LocalTable::offset_count() as usize, - offset_of!(LocalTable => count).get_byte_offset(), + offset_of!(LocalTable, count), ); } @@ -716,12 +924,12 @@ mod vm_offset_tests { fn local_memory() { assert_eq!( LocalMemory::offset_base() as usize, - offset_of!(LocalMemory => base).get_byte_offset(), + offset_of!(LocalMemory, base), ); assert_eq!( LocalMemory::offset_bound() as usize, - offset_of!(LocalMemory => bound).get_byte_offset(), + offset_of!(LocalMemory, bound), ); } @@ -729,25 +937,19 @@ mod vm_offset_tests { fn local_global() { assert_eq!( LocalGlobal::offset_data() as usize, - offset_of!(LocalGlobal => data).get_byte_offset(), + offset_of!(LocalGlobal, data), ); } #[test] fn cc_anyfunc() { - assert_eq!( - Anyfunc::offset_func() as usize, - offset_of!(Anyfunc => func).get_byte_offset(), - ); + assert_eq!(Anyfunc::offset_func() as usize, offset_of!(Anyfunc, func),); - assert_eq!( - Anyfunc::offset_vmctx() as usize, - offset_of!(Anyfunc => ctx).get_byte_offset(), - ); + assert_eq!(Anyfunc::offset_vmctx() as usize, offset_of!(Anyfunc, ctx),); assert_eq!( Anyfunc::offset_sig_id() as usize, - offset_of!(Anyfunc => sig_id).get_byte_offset(), + offset_of!(Anyfunc, sig_id), ); } } @@ -865,15 +1067,15 @@ mod vm_ctx_tests { } fn get_trampoline(&self, _module: &ModuleInfo, _sig_index: SigIndex) -> Option { - unimplemented!() + unimplemented!("generate_module::get_trampoline") } unsafe fn do_early_trap(&self, _: Box) -> ! { - unimplemented!() + unimplemented!("generate_module::do_early_trap") } } impl CacheGen for Placeholder { fn generate_cache(&self) -> Result<(Box<[u8]>, Memory), CacheError> { - unimplemented!() + unimplemented!("generate_module::generate_cache") } } diff --git a/lib/runtime-core/src/vmcalls.rs b/lib/runtime-core/src/vmcalls.rs index 5205a0d69..32aa9367f 100644 --- a/lib/runtime-core/src/vmcalls.rs +++ b/lib/runtime-core/src/vmcalls.rs @@ -10,7 +10,7 @@ use crate::{ // +*****************************+ // | LOCAL MEMORIES | -// +****************************+ +// +*****************************+ pub unsafe extern "C" fn local_static_memory_grow( ctx: &mut vm::Ctx, @@ -72,7 +72,7 @@ pub unsafe extern "C" fn local_dynamic_memory_size( // +*****************************+ // | IMPORTED MEMORIES | -// +****************************+ +// +*****************************+ pub unsafe extern "C" fn imported_static_memory_grow( ctx: &mut vm::Ctx, @@ -140,7 +140,7 @@ pub unsafe extern "C" fn imported_dynamic_memory_size( // +*****************************+ // | LOCAL TABLES | -// +****************************+ +// +*****************************+ pub unsafe extern "C" fn local_table_grow( ctx: &mut vm::Ctx, @@ -150,11 +150,11 @@ pub unsafe extern "C" fn local_table_grow( let _ = table_index; let _ = delta; let _ = ctx; - unimplemented!() + unimplemented!("vmcalls::local_table_grow") } pub unsafe extern "C" fn local_table_size(ctx: &vm::Ctx, table_index: LocalTableIndex) -> u32 { let _ = table_index; let _ = ctx; - unimplemented!() + unimplemented!("vmcalls::local_table_size") } diff --git a/lib/runtime/Cargo.toml b/lib/runtime/Cargo.toml index b1383fa20..e6fe8bb9b 100644 --- a/lib/runtime/Cargo.toml +++ b/lib/runtime/Cargo.toml @@ -1,29 +1,31 @@ [package] name = "wasmer-runtime" -version = "0.6.0" +version = "0.11.0" description = "Wasmer runtime library" license = "MIT" authors = ["The Wasmer Engineering Team "] repository = "https://github.com/wasmerio/wasmer" +keywords = ["wasm", "webassembly", "runtime", "sandbox", "secure"] +categories = ["wasm", "api-bindings"] edition = "2018" readme = "README.md" [dependencies] -wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.6.0", optional = true } -lazy_static = "1.3.0" -memmap = "0.7.0" +wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.11.0", optional = true } +lazy_static = "1.4" +memmap = "0.7" [dependencies.wasmer-runtime-core] path = "../runtime-core" -version = "0.6.0" +version = "0.11.0" [dependencies.wasmer-clif-backend] path = "../clif-backend" -version = "0.6.0" +version = "0.11.0" optional = true [dev-dependencies] -tempfile = "3.1.0" +tempfile = "3.1" criterion = "0.2" wabt = "0.9.1" @@ -33,6 +35,7 @@ optional = true [features] default = ["cranelift", "default-backend-cranelift"] +docs = [] cranelift = ["wasmer-clif-backend"] cache = ["cranelift"] debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"] diff --git a/lib/runtime/README.md b/lib/runtime/README.md index cbb6388aa..ce18500bf 100644 --- a/lib/runtime/README.md +++ b/lib/runtime/README.md @@ -1,21 +1,21 @@

- Wasmer logo + Wasmer logo

- - Build Status + + Build Status - License + License Join the Wasmer Community - Number of downloads from crates.io + Number of downloads from crates.io Read our API documentation @@ -80,10 +80,10 @@ fn main() -> error::Result<()> { // We're not importing anything, so make an empty import object. let import_object = imports! {}; - let mut instance = instantiate(WASM, &import_object)?; + let instance = instantiate(WASM, &import_object)?; let values = instance - .func("add_one")? + .dyn_func("add_one")? .call(&[Value::I32(42)])?; assert_eq!(values[0], Value::I32(43)); @@ -94,12 +94,17 @@ fn main() -> error::Result<()> { ## Additional Notes -The `wasmer-runtime` crate is build to support multiple compiler -backends. Currently, we support the [Cranelift] compiler with the -[`wasmer-clif-backend`] crate by default. +The `wasmer-runtime` crate is built to support multiple compiler +backends. We support having a [Cranelift] backend in the +[`wasmer-clif-backend`] crate, a [LLVM] backend in the +[`wasmer-llvm-backend`] crate, and the [Singlepass] backend in the +[`wasmer-singlepass-backend`] crate. Currently, the Cranelift backend +is the default. You can specify the compiler you wish to use with the [`compile_with`] function. [Cranelift]: https://github.com/CraneStation/cranelift +[LLVM]: https://llvm.org +[Singlepass]: https://github.com/wasmerio/wasmer/tree/master/lib/singlepass-backend [`wasmer-clif-backend`]: https://crates.io/crates/wasmer-clif-backend [`compile_with`]: https://docs.rs/wasmer-runtime/*/wasmer_runtime/fn.compile_with.html diff --git a/lib/runtime/benches/many_instances.rs b/lib/runtime/benches/many_instances.rs index e1c655c78..c10cf34e9 100644 --- a/lib/runtime/benches/many_instances.rs +++ b/lib/runtime/benches/many_instances.rs @@ -4,7 +4,7 @@ use criterion::Criterion; use tempfile::tempdir; use wasmer_runtime::{ cache::{Cache, FileSystemCache, WasmHash}, - compile, func, imports, instantiate, validate, ImportObject, + compile, func, imports, instantiate, validate, }; use wasmer_runtime_core::vm::Ctx; @@ -71,7 +71,7 @@ fn calling_fn_benchmark(c: &mut Criterion) { ); let instance = instantiate(SIMPLE_WASM, &imports).unwrap(); c.bench_function("calling fn", move |b| { - let entry_point = instance.func::<(i32), i32>("plugin_entrypoint").unwrap(); + let entry_point = instance.func::("plugin_entrypoint").unwrap(); b.iter(|| entry_point.call(2).unwrap()) }); } diff --git a/lib/runtime/src/cache.rs b/lib/runtime/src/cache.rs index 8fd5219a1..714fd9f54 100644 --- a/lib/runtime/src/cache.rs +++ b/lib/runtime/src/cache.rs @@ -1,3 +1,7 @@ +//! The cache module provides the common data structures used by compiler backends to allow +//! serializing compiled wasm code to a binary format. The binary format can be persisted, +//! and loaded to allow skipping compilation and fast startup. + use crate::Module; use memmap::Mmap; use std::{ @@ -125,7 +129,7 @@ impl Cache for FileSystemCache { } } -#[cfg(all(test, not(feature = "singlepass")))] +#[cfg(test)] mod tests { use super::*; @@ -173,5 +177,4 @@ mod tests { // verify it works assert_eq!(value, 43); } - } diff --git a/lib/runtime/src/lib.rs b/lib/runtime/src/lib.rs index 5b3ba8526..0c832bf30 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/src/lib.rs @@ -1,5 +1,6 @@ #![deny( dead_code, + missing_docs, nonstandard_style, unused_imports, unused_mut, @@ -7,6 +8,9 @@ unused_unsafe, unreachable_patterns )] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + //! Wasmer-runtime is a library that makes embedding WebAssembly //! in your application easy, efficient, and safe. //! @@ -83,12 +87,13 @@ //! [`wasmer-clif-backend`]: https://crates.io/crates/wasmer-clif-backend //! [`compile_with`]: fn.compile_with.html -pub use wasmer_runtime_core::backend::Backend; +pub use wasmer_runtime_core::backend::{Backend, Features}; pub use wasmer_runtime_core::codegen::{MiddlewareChain, StreamingCompiler}; pub use wasmer_runtime_core::export::Export; pub use wasmer_runtime_core::global::Global; -pub use wasmer_runtime_core::import::ImportObject; +pub use wasmer_runtime_core::import::{ImportObject, LikeNamespace}; pub use wasmer_runtime_core::instance::{DynFunc, Instance}; +pub use wasmer_runtime_core::memory::ptr::{Array, Item, WasmPtr}; pub use wasmer_runtime_core::memory::Memory; pub use wasmer_runtime_core::module::Module; pub use wasmer_runtime_core::table::Table; @@ -100,6 +105,8 @@ pub use wasmer_runtime_core::{compile_with, validate}; pub use wasmer_runtime_core::{func, imports}; pub mod memory { + //! The memory module contains the implementation data structures and helper functions used to + //! manipulate and access wasm memory. pub use wasmer_runtime_core::memory::{Atomically, Memory, MemoryView}; } @@ -113,6 +120,8 @@ pub mod wasm { } pub mod error { + //! The error module contains the data structures and helper functions used to implement errors that + //! are produced and returned from the wasmer runtime. pub use wasmer_runtime_core::cache::Error as CacheError; pub use wasmer_runtime_core::error::*; } @@ -122,9 +131,14 @@ pub mod units { pub use wasmer_runtime_core::units::{Bytes, Pages}; } +pub mod types { + //! Various types. + pub use wasmer_runtime_core::types::*; +} + pub mod cache; -use wasmer_runtime_core::backend::{Compiler, CompilerConfig}; +pub use wasmer_runtime_core::backend::{Compiler, CompilerConfig}; /// Compile WebAssembly binary code into a [`Module`]. /// This function is useful if it is necessary to @@ -186,16 +200,22 @@ pub fn instantiate(wasm: &[u8], import_object: &ImportObject) -> error::Result impl Compiler { #[cfg(any( all( feature = "default-backend-llvm", + not(feature = "docs"), any( feature = "default-backend-cranelift", feature = "default-backend-singlepass" ) ), all( + not(feature = "docs"), feature = "default-backend-cranelift", feature = "default-backend-singlepass" ) @@ -204,18 +224,23 @@ pub fn default_compiler() -> impl Compiler { "The `default-backend-X` features are mutually exclusive. Please choose just one" ); - #[cfg(feature = "default-backend-llvm")] + #[cfg(all(feature = "default-backend-llvm", not(feature = "docs")))] use wasmer_llvm_backend::LLVMCompiler as DefaultCompiler; - #[cfg(feature = "default-backend-singlepass")] + #[cfg(all(feature = "default-backend-singlepass", not(feature = "docs")))] use wasmer_singlepass_backend::SinglePassCompiler as DefaultCompiler; - #[cfg(feature = "default-backend-cranelift")] + #[cfg(any(feature = "default-backend-cranelift", feature = "docs"))] use wasmer_clif_backend::CraneliftCompiler as DefaultCompiler; DefaultCompiler::new() } +/// Get the `Compiler` as a trait object for the given `Backend`. +/// Returns `Option` because support for the requested `Compiler` may +/// not be enabled by feature flags. +/// +/// To get a list of the enabled backends as strings, call `Backend::variants()`. pub fn compiler_for_backend(backend: Backend) -> Option> { match backend { #[cfg(feature = "cranelift")] @@ -229,14 +254,9 @@ pub fn compiler_for_backend(backend: Backend) -> Option> { #[cfg(feature = "llvm")] Backend::LLVM => Some(Box::new(wasmer_llvm_backend::LLVMCompiler::new())), - #[cfg(any( - not(feature = "llvm"), - not(feature = "singlepass"), - not(feature = "cranelift") - ))] _ => None, } } -/// The current version of this crate +/// The current version of this crate. pub const VERSION: &str = env!("CARGO_PKG_VERSION"); diff --git a/lib/singlepass-backend/Cargo.toml b/lib/singlepass-backend/Cargo.toml index af1dbe437..6ed704837 100644 --- a/lib/singlepass-backend/Cargo.toml +++ b/lib/singlepass-backend/Cargo.toml @@ -1,21 +1,24 @@ [package] name = "wasmer-singlepass-backend" -version = "0.6.0" +version = "0.11.0" repository = "https://github.com/wasmerio/wasmer" description = "Wasmer runtime single pass compiler backend" license = "MIT" authors = ["The Wasmer Engineering Team "] +keywords = ["wasm", "webassembly", "compiler", "JIT", "AOT"] +categories = ["wasm"] edition = "2018" readme = "README.md" [dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } -wasmparser = "0.35.1" -dynasm = "0.3.2" -dynasmrt = "0.3.1" -lazy_static = "1.3.0" -byteorder = "1.3.2" -nix = "0.15.0" +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } +dynasm = "0.5" +dynasmrt = "0.5" +lazy_static = "1.4" +byteorder = "1.3" +nix = "0.15" libc = "0.2.60" -smallvec = "0.6.10" -colored = "1.8" +smallvec = "0.6" +serde = "1.0" +serde_derive = "1.0" +bincode = "1.2" diff --git a/lib/singlepass-backend/README.md b/lib/singlepass-backend/README.md index bf477a338..cf9ee566c 100644 --- a/lib/singlepass-backend/README.md +++ b/lib/singlepass-backend/README.md @@ -1,21 +1,21 @@

- Wasmer logo + Wasmer logo

- - Build Status + + Build Status - License + License Join the Wasmer Community - Number of downloads from crates.io + Number of downloads from crates.io Read our API documentation @@ -37,7 +37,7 @@ This crate represents the Singlepass backend integration for Wasmer. If you are using the `wasmer` CLI, you can specify the backend with: -```bash +```sh wasmer run program.wasm --backend=singlepass ``` diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index 0d808a3e0..eee81e3b5 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -4,22 +4,33 @@ use crate::emitter_x64::*; use crate::machine::*; use crate::protect_unix; -use dynasmrt::{ - x64::Assembler, AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi, ExecutableBuffer, -}; +#[cfg(target_arch = "aarch64")] +use dynasmrt::aarch64::Assembler; +#[cfg(target_arch = "x86_64")] +use dynasmrt::x64::Assembler; +use dynasmrt::{AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi}; use smallvec::SmallVec; -use std::ptr::NonNull; use std::{ any::Any, collections::{BTreeMap, HashMap}, + ffi::c_void, + iter, mem, + ptr::NonNull, + slice, sync::{Arc, RwLock}, + usize, }; use wasmer_runtime_core::{ backend::{ - sys::Memory, Backend, CacheGen, CompilerConfig, MemoryBoundCheckMode, RunnableModule, Token, + get_inline_breakpoint_size, + sys::{Memory, Protect}, + Architecture, Backend, CacheGen, CompilerConfig, MemoryBoundCheckMode, RunnableModule, + Token, }, cache::{Artifact, Error as CacheError}, codegen::*, + fault::raw::register_preservation_trampoline, + loader::CodeMemory, memory::MemoryType, module::{ModuleInfo, ModuleInner}, state::{ @@ -27,15 +38,23 @@ use wasmer_runtime_core::{ ModuleStateMap, OffsetInfo, SuspendOffset, WasmAbstractValue, }, structures::{Map, TypedIndex}, - typed_func::Wasm, + typed_func::{Trampoline, Wasm, WasmTrapInfo}, types::{ FuncIndex, FuncSig, GlobalIndex, LocalFuncIndex, LocalOrImport, MemoryIndex, SigIndex, TableIndex, Type, }, vm::{self, LocalGlobal, LocalTable, INTERNALS_SIZE}, + wasmparser::{MemoryImmediate, Operator, Type as WpType, TypeOrFuncType as WpTypeOrFuncType}, }; -use wasmparser::{Operator, Type as WpType, TypeOrFuncType as WpTypeOrFuncType}; +#[cfg(target_arch = "aarch64")] +#[allow(dead_code)] +static ARCH: Architecture = Architecture::Aarch64; +#[cfg(target_arch = "x86_64")] +#[allow(dead_code)] +static ARCH: Architecture = Architecture::X64; + +#[cfg(target_arch = "x86_64")] lazy_static! { /// Performs a System V call to `target` with [stack_top..stack_base] as the argument list, from right to left. static ref CONSTRUCT_STACK_AND_CALL_WASM: unsafe extern "C" fn (stack_top: *const u64, stack_base: *const u64, ctx: *mut vm::Ctx, target: *const vm::Func) -> u64 = { @@ -118,8 +137,47 @@ lazy_static! { ; ret ); let buf = assembler.finalize().unwrap(); - let ret = unsafe { ::std::mem::transmute(buf.ptr(offset)) }; - ::std::mem::forget(buf); + let ret = unsafe { mem::transmute(buf.ptr(offset)) }; + mem::forget(buf); + ret + }; +} + +#[cfg(target_arch = "aarch64")] +#[repr(C)] +#[allow(dead_code)] +struct CallCtx { + ctx: *mut vm::Ctx, + stack: *mut u64, + target: *mut u8, +} + +#[cfg(target_arch = "aarch64")] +lazy_static! { + /// Switches stack and executes the provided callback. + static ref SWITCH_STACK: unsafe extern "C" fn (stack: *mut u64, cb: extern "C" fn (*mut u8) -> u64, userdata: *mut u8) -> u64 = { + let mut assembler = Assembler::new().unwrap(); + let offset = assembler.offset(); + dynasm!( + assembler + ; .arch aarch64 + ; sub x0, x0, 16 + ; mov x8, sp + ; str x8, [x0, 0] + ; str x30, [x0, 8] + ; adr x30, >done + ; mov sp, x0 + ; mov x0, x2 + ; br x1 + ; done: + ; ldr x30, [sp, 8] + ; ldr x8, [sp, 0] + ; mov sp, x8 + ; br x30 + ); + let buf = assembler.finalize().unwrap(); + let ret = unsafe { mem::transmute(buf.ptr(offset)) }; + mem::forget(buf); ret }; } @@ -172,9 +230,7 @@ unsafe impl Sync for FuncPtr {} pub struct X64ExecutionContext { #[allow(dead_code)] - code: ExecutableBuffer, - #[allow(dead_code)] - functions: Vec, + code: CodeMemory, function_pointers: Vec, function_offsets: Vec, signatures: Arc>, @@ -183,6 +239,28 @@ pub struct X64ExecutionContext { msm: ModuleStateMap, } +/// On-disk cache format. +/// Offsets are relative to the start of the executable image. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CacheImage { + /// The executable image. + code: Vec, + + /// Offsets to the start of each function. Including trampoline, if any. + /// Trampolines are only present on AArch64. + /// On x86-64, `function_pointers` are identical to `function_offsets`. + function_pointers: Vec, + + /// Offsets to the start of each function after trampoline. + function_offsets: Vec, + + /// Number of imported functions. + func_import_count: usize, + + /// Module state map. + msm: ModuleStateMap, +} + #[derive(Debug)] pub struct ControlFrame { pub label: DynamicLabel, @@ -201,6 +279,25 @@ pub enum IfElseState { Else, } +pub struct SinglepassCache { + buffer: Arc<[u8]>, +} + +impl CacheGen for SinglepassCache { + fn generate_cache(&self) -> Result<(Box<[u8]>, Memory), CacheError> { + let mut memory = Memory::with_size_protect(self.buffer.len(), Protect::ReadWrite) + .map_err(CacheError::SerializeError)?; + + let buffer = &*self.buffer; + + unsafe { + memory.as_slice_mut()[..buffer.len()].copy_from_slice(buffer); + } + + Ok(([].as_ref().into(), memory)) + } +} + impl RunnableModule for X64ExecutionContext { fn get_func( &self, @@ -220,17 +317,43 @@ impl RunnableModule for X64ExecutionContext { Some(self.breakpoints.clone()) } - fn get_trampoline(&self, _: &ModuleInfo, sig_index: SigIndex) -> Option { - use std::ffi::c_void; - use wasmer_runtime_core::typed_func::WasmTrapInfo; + unsafe fn patch_local_function(&self, idx: usize, target_address: usize) -> bool { + /* + 0: 48 b8 42 42 42 42 42 42 42 42 movabsq $4774451407313060418, %rax + a: 49 bb 43 43 43 43 43 43 43 43 movabsq $4846791580151137091, %r11 + 14: 41 ff e3 jmpq *%r11 + */ + #[repr(packed)] + struct LocalTrampoline { + movabsq_rax: [u8; 2], + addr_rax: u64, + movabsq_r11: [u8; 2], + addr_r11: u64, + jmpq_r11: [u8; 3], + } + self.code.make_writable(); + + let trampoline = &mut *(self.function_pointers[self.func_import_count + idx].0 + as *const LocalTrampoline as *mut LocalTrampoline); + trampoline.movabsq_rax[0] = 0x48; + trampoline.movabsq_rax[1] = 0xb8; + trampoline.addr_rax = target_address as u64; + trampoline.movabsq_r11[0] = 0x49; + trampoline.movabsq_r11[1] = 0xbb; + trampoline.addr_r11 = + register_preservation_trampoline as unsafe extern "C" fn() as usize as u64; + trampoline.jmpq_r11[0] = 0x41; + trampoline.jmpq_r11[1] = 0xff; + trampoline.jmpq_r11[2] = 0xe3; + + self.code.make_executable(); + true + } + + fn get_trampoline(&self, _: &ModuleInfo, sig_index: SigIndex) -> Option { unsafe extern "C" fn invoke( - _trampoline: unsafe extern "C" fn( - *mut vm::Ctx, - NonNull, - *const u64, - *mut u64, - ), + _trampoline: Trampoline, ctx: *mut vm::Ctx, func: NonNull, args: *const u64, @@ -241,21 +364,137 @@ impl RunnableModule for X64ExecutionContext { ) -> bool { let rm: &Box = &(&*(*ctx).module).runnable_module; let execution_context = - ::std::mem::transmute_copy::<&dyn RunnableModule, &X64ExecutionContext>(&&**rm); + mem::transmute_copy::<&dyn RunnableModule, &X64ExecutionContext>(&&**rm); + + let args = + slice::from_raw_parts(args, num_params_plus_one.unwrap().as_ptr() as usize - 1); - let args = ::std::slice::from_raw_parts( - args, - num_params_plus_one.unwrap().as_ptr() as usize - 1, - ); - let args_reverse: SmallVec<[u64; 8]> = args.iter().cloned().rev().collect(); let ret = match protect_unix::call_protected( || { - CONSTRUCT_STACK_AND_CALL_WASM( - args_reverse.as_ptr(), - args_reverse.as_ptr().offset(args_reverse.len() as isize), - ctx, - func.as_ptr(), - ) + #[cfg(target_arch = "x86_64")] + { + let args_reverse: SmallVec<[u64; 8]> = args.iter().cloned().rev().collect(); + CONSTRUCT_STACK_AND_CALL_WASM( + args_reverse.as_ptr(), + args_reverse.as_ptr().offset(args_reverse.len() as isize), + ctx, + func.as_ptr(), + ) + } + #[cfg(target_arch = "aarch64")] + { + struct CallCtx<'a> { + args: &'a [u64], + ctx: *mut vm::Ctx, + callable: NonNull, + } + extern "C" fn call_fn(f: *mut u8) -> u64 { + unsafe { + let f = &*(f as *const CallCtx); + let callable: extern "C" fn( + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + u64, + ) + -> u64 = std::mem::transmute(f.callable); + let mut args = f.args.iter(); + callable( + f.ctx as u64, + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + args.next().cloned().unwrap_or(0), + ) + } + } + let mut cctx = CallCtx { + args: &args, + ctx: ctx, + callable: func, + }; + use libc::{ + mmap, munmap, MAP_ANON, MAP_NORESERVE, MAP_PRIVATE, PROT_READ, + PROT_WRITE, + }; + const STACK_SIZE: usize = 1048576 * 1024; // 1GB of virtual address space for stack. + let stack_ptr = mmap( + ::std::ptr::null_mut(), + STACK_SIZE, + PROT_READ | PROT_WRITE, + MAP_PRIVATE | MAP_ANON | MAP_NORESERVE, + -1, + 0, + ); + if stack_ptr as isize == -1 { + panic!("unable to allocate stack"); + } + // TODO: Mark specific regions in the stack as PROT_NONE. + let ret = SWITCH_STACK( + (stack_ptr as *mut u8).offset(STACK_SIZE as isize) as *mut u64, + call_fn, + &mut cctx as *mut CallCtx as *mut u8, + ); + munmap(stack_ptr, STACK_SIZE); + ret + } }, Some(execution_context.breakpoints.clone()), ) { @@ -306,11 +545,20 @@ impl RunnableModule for X64ExecutionContext { fn get_offsets(&self) -> Option> { Some(self.function_offsets.iter().map(|x| x.0).collect()) } + + fn get_local_function_offsets(&self) -> Option> { + Some( + self.function_offsets[self.func_import_count..] + .iter() + .map(|x| x.0) + .collect(), + ) + } } #[derive(Debug)] pub struct CodegenError { - pub message: &'static str, + pub message: String, } #[derive(Copy, Clone, Debug)] @@ -324,17 +572,23 @@ impl ModuleCodeGenerator for X64ModuleCodeGenerator { fn new() -> X64ModuleCodeGenerator { + let a = Assembler::new().unwrap(); + X64ModuleCodeGenerator { functions: vec![], signatures: None, function_signatures: None, function_labels: Some(HashMap::new()), - assembler: Some(Assembler::new().unwrap()), + assembler: Some(a), func_import_count: 0, config: None, } } + fn new_with_target(_: Option, _: Option, _: Option) -> Self { + unimplemented!("cross compilation is not available for singlepass backend") + } + fn backend_id() -> Backend { Backend::Singlepass } @@ -366,15 +620,12 @@ impl ModuleCodeGenerator .or_insert_with(|| (assembler.new_dynamic_label(), None)); begin_label_info.1 = Some(begin_offset); + assembler.arch_emit_entry_trampoline(); let begin_label = begin_label_info.0; let mut machine = Machine::new(); machine.track_state = self.config.as_ref().unwrap().track_state; - dynasm!( - assembler - ; => begin_label - //; int 3 - ); + assembler.emit_label(begin_label); let code = X64FunctionCode { local_function_id: self.functions.len(), @@ -418,7 +669,10 @@ impl ModuleCodeGenerator }; let total_size = assembler.get_offset().0; - let output = assembler.finalize().unwrap(); + let _output = assembler.finalize().unwrap(); + let mut output = CodeMemory::new(_output.len()); + output[0.._output.len()].copy_from_slice(&_output); + output.make_executable(); let mut out_labels: Vec = vec![]; let mut out_offsets: Vec = vec![]; @@ -428,7 +682,7 @@ impl ModuleCodeGenerator Some(x) => x, None => { return Err(CodegenError { - message: "label not found", + message: format!("label not found"), }); } }; @@ -436,18 +690,25 @@ impl ModuleCodeGenerator Some(x) => x, None => { return Err(CodegenError { - message: "offset is none", + message: format!("offset is none"), }); } }; - out_labels.push(FuncPtr(output.ptr(*offset) as _)); + out_labels.push(FuncPtr( + unsafe { output.as_ptr().offset(offset.0 as isize) } as _, + )); out_offsets.push(*offset); } let breakpoints: Arc> = Arc::new( breakpoints .into_iter() - .map(|(offset, f)| (output.ptr(offset) as usize, f)) + .map(|(offset, f)| { + ( + unsafe { output.as_ptr().offset(offset.0 as isize) } as usize, + f, + ) + }) .collect(), ); @@ -457,29 +718,41 @@ impl ModuleCodeGenerator .map(|x| (x.offset, x.fsm.clone())) .collect(); - struct Placeholder; - impl CacheGen for Placeholder { - fn generate_cache(&self) -> Result<(Box<[u8]>, Memory), CacheError> { - Err(CacheError::Unknown( - "the singlepass backend doesn't support caching yet".to_string(), - )) - } - } + let msm = ModuleStateMap { + local_functions: local_function_maps, + total_size, + }; + + let cache_image = CacheImage { + code: output.to_vec(), + function_pointers: out_labels + .iter() + .map(|x| { + (x.0 as usize) + .checked_sub(output.as_ptr() as usize) + .unwrap() + }) + .collect(), + function_offsets: out_offsets.iter().map(|x| x.0 as usize).collect(), + func_import_count: self.func_import_count, + msm: msm.clone(), + }; + + let cache = SinglepassCache { + buffer: Arc::from(bincode::serialize(&cache_image).unwrap().into_boxed_slice()), + }; + Ok(( X64ExecutionContext { code: output, - functions: self.functions, signatures: self.signatures.as_ref().unwrap().clone(), breakpoints: breakpoints, func_import_count: self.func_import_count, function_pointers: out_labels, function_offsets: out_offsets, - msm: ModuleStateMap { - local_functions: local_function_maps, - total_size, - }, + msm: msm, }, - Box::new(Placeholder), + Box::new(cache), )) } @@ -502,6 +775,7 @@ impl ModuleCodeGenerator let a = self.assembler.as_mut().unwrap(); let offset = a.offset(); + a.arch_emit_entry_trampoline(); let label = a.get_label(); a.emit_label(label); labels.insert(id, (label, Some(offset))); @@ -509,21 +783,33 @@ impl ModuleCodeGenerator // Emits a tail call trampoline that loads the address of the target import function // from Ctx and jumps to it. + let imported_funcs_addr = vm::Ctx::offset_imported_funcs(); + let imported_func = vm::ImportedFunc::size() as usize * id; + let imported_func_addr = imported_func + vm::ImportedFunc::offset_func() as usize; + let imported_func_ctx_addr = imported_func + vm::ImportedFunc::offset_func_ctx() as usize; + let imported_func_ctx_vmctx_addr = vm::FuncCtx::offset_vmctx() as usize; + a.emit_mov( Size::S64, - Location::Memory(GPR::RDI, vm::Ctx::offset_imported_funcs() as i32), + Location::Memory(GPR::RDI, imported_funcs_addr as i32), Location::GPR(GPR::RAX), ); a.emit_mov( Size::S64, - Location::Memory( - GPR::RAX, - (vm::ImportedFunc::size() as usize * id + vm::ImportedFunc::offset_func() as usize) - as i32, - ), + Location::Memory(GPR::RAX, imported_func_ctx_addr as i32), + Location::GPR(GPR::RDI), + ); + a.emit_mov( + Size::S64, + Location::Memory(GPR::RDI, imported_func_ctx_vmctx_addr as i32), + Location::GPR(GPR::RDI), + ); + a.emit_mov( + Size::S64, + Location::Memory(GPR::RAX, imported_func_addr as i32), Location::GPR(GPR::RAX), ); - a.emit_jmp_location(Location::GPR(GPR::RAX)); + a.emit_host_redirection(GPR::RAX); self.func_import_count += 1; @@ -538,10 +824,45 @@ impl ModuleCodeGenerator })); Ok(()) } - unsafe fn from_cache(_artifact: Artifact, _: Token) -> Result { - Err(CacheError::Unknown( - "the singlepass compiler API doesn't support caching yet".to_string(), - )) + unsafe fn from_cache(artifact: Artifact, _: Token) -> Result { + let (info, _, memory) = artifact.consume(); + + let cache_image: CacheImage = bincode::deserialize(memory.as_slice()) + .map_err(|x| CacheError::DeserializeError(format!("{:?}", x)))?; + + let mut code_mem = CodeMemory::new(cache_image.code.len()); + code_mem[0..cache_image.code.len()].copy_from_slice(&cache_image.code); + code_mem.make_executable(); + + let function_pointers: Vec = cache_image + .function_pointers + .iter() + .map(|&x| FuncPtr(code_mem.as_ptr().offset(x as isize) as *const FuncPtrInner)) + .collect(); + let function_offsets: Vec = cache_image + .function_offsets + .iter() + .cloned() + .map(AssemblyOffset) + .collect(); + + let ec = X64ExecutionContext { + code: code_mem, + function_pointers, + function_offsets, + signatures: Arc::new(info.signatures.clone()), + breakpoints: Arc::new(HashMap::new()), + func_import_count: cache_image.func_import_count, + msm: cache_image.msm, + }; + Ok(ModuleInner { + runnable_module: Box::new(ec), + cache_gen: Box::new(SinglepassCache { + buffer: Arc::from(memory.as_slice().to_vec().into_boxed_slice()), + }), + + info, + }) } } @@ -557,6 +878,30 @@ impl X64FunctionCode { fsm.trappable_offsets.insert( offset, OffsetInfo { + end_offset: offset + 1, + activate_offset: offset, + diff_id: state_diff_id, + }, + ); + fsm.wasm_offset_to_target_offset + .insert(m.state.wasm_inst_offset, SuspendOffset::Trappable(offset)); + } + + #[allow(dead_code)] + fn mark_inline_breakpoint( + a: &mut Assembler, + m: &Machine, + fsm: &mut FunctionStateMap, + control_stack: &mut [ControlFrame], + ) { + let state_diff_id = Self::get_state_diff(m, fsm, control_stack); + let offset = a.get_offset().0; + fsm.trappable_offsets.insert( + offset, + OffsetInfo { + end_offset: offset + + get_inline_breakpoint_size(ARCH, Backend::Singlepass) + .expect("cannot get inline breakpoint size"), activate_offset: offset, diff_id: state_diff_id, }, @@ -600,32 +945,34 @@ impl X64FunctionCode { sz_dst: Size, dst: Location, ) { - let tmp_src = m.acquire_temp_gpr().unwrap(); - let tmp_dst = m.acquire_temp_gpr().unwrap(); - - match src { - Location::Imm32(_) | Location::Imm64(_) => { - a.emit_mov(Size::S64, src, Location::GPR(tmp_src)); - src = Location::GPR(tmp_src); - } - Location::Memory(_, _) | Location::GPR(_) => {} - _ => unreachable!(), - } - - match dst { + let inner = |m: &mut Machine, a: &mut Assembler, src: Location| match dst { Location::Imm32(_) | Location::Imm64(_) => unreachable!(), Location::Memory(_, _) => { + let tmp_dst = m.acquire_temp_gpr().unwrap(); op(a, sz_src, src, sz_dst, Location::GPR(tmp_dst)); a.emit_mov(Size::S64, Location::GPR(tmp_dst), dst); + + m.release_temp_gpr(tmp_dst); } Location::GPR(_) => { op(a, sz_src, src, sz_dst, dst); } _ => unreachable!(), - } + }; - m.release_temp_gpr(tmp_dst); - m.release_temp_gpr(tmp_src); + match src { + Location::Imm32(_) | Location::Imm64(_) => { + let tmp_src = m.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S64, src, Location::GPR(tmp_src)); + src = Location::GPR(tmp_src); + + inner(m, a, src); + + m.release_temp_gpr(tmp_src); + } + Location::GPR(_) | Location::Memory(_, _) => inner(m, a, src), + _ => unreachable!(), + } } /// Moves `src` and `dst` to valid locations for generic instructions. @@ -1139,7 +1486,10 @@ impl X64FunctionCode { value_stack.push(ret); Self::emit_relaxed_avx(a, m, f, loc_a, loc_b, ret); - a.emit_and(Size::S32, Location::Imm32(1), ret); // FIXME: Why? + + // Workaround for behavior inconsistency among different backing implementations. + // (all bits or only the least significant bit are set to one?) + a.emit_and(Size::S32, Location::Imm32(1), ret); } /// Floating point (AVX) binary operation with both operands popped from the virtual stack. @@ -1179,7 +1529,7 @@ impl X64FunctionCode { let used_gprs = m.get_used_gprs(); for r in used_gprs.iter() { a.emit_push(Size::S64, Location::GPR(*r)); - let content = m.state.register_values[X64Register::GPR(*r).to_index().0]; + let content = m.state.register_values[X64Register::GPR(*r).to_index().0].clone(); assert!(content != MachineValue::Undefined); m.state.stack_values.push(content); } @@ -1204,7 +1554,7 @@ impl X64FunctionCode { ); } for r in used_xmms.iter().rev() { - let content = m.state.register_values[X64Register::XMM(*r).to_index().0]; + let content = m.state.register_values[X64Register::XMM(*r).to_index().0].clone(); assert!(content != MachineValue::Undefined); m.state.stack_values.push(content); } @@ -1244,7 +1594,8 @@ impl X64FunctionCode { Location::Memory(_, _) => { match *param { Location::GPR(x) => { - let content = m.state.register_values[X64Register::GPR(x).to_index().0]; + let content = + m.state.register_values[X64Register::GPR(x).to_index().0].clone(); // FIXME: There might be some corner cases (release -> emit_call_sysv -> acquire?) that cause this assertion to fail. // Hopefully nothing would be incorrect at runtime. @@ -1252,7 +1603,8 @@ impl X64FunctionCode { m.state.stack_values.push(content); } Location::XMM(x) => { - let content = m.state.register_values[X64Register::XMM(x).to_index().0]; + let content = + m.state.register_values[X64Register::XMM(x).to_index().0].clone(); //assert!(content != MachineValue::Undefined); m.state.stack_values.push(content); } @@ -1262,7 +1614,8 @@ impl X64FunctionCode { } m.state .stack_values - .push(MachineValue::CopyStackBPRelative(offset)); // TODO: Read value at this offset + .push(MachineValue::CopyStackBPRelative(offset)); + // TODO: Read value at this offset } _ => { m.state.stack_values.push(MachineValue::Undefined); @@ -1335,6 +1688,7 @@ impl X64FunctionCode { fsm.call_offsets.insert( offset, OffsetInfo { + end_offset: offset + 1, activate_offset: offset, diff_id: state_diff_id, }, @@ -1407,7 +1761,8 @@ impl X64FunctionCode { a: &mut Assembler, m: &mut Machine, addr: Location, - offset: usize, + memarg: &MemoryImmediate, + check_alignment: bool, value_size: usize, cb: F, ) { @@ -1429,7 +1784,6 @@ impl X64FunctionCode { let tmp_addr = m.acquire_temp_gpr().unwrap(); let tmp_base = m.acquire_temp_gpr().unwrap(); - let tmp_bound = m.acquire_temp_gpr().unwrap(); // Load base into temporary register. a.emit_mov( @@ -1442,6 +1796,8 @@ impl X64FunctionCode { ); if need_check { + let tmp_bound = m.acquire_temp_gpr().unwrap(); + a.emit_mov( Size::S64, Location::Memory( @@ -1455,14 +1811,15 @@ impl X64FunctionCode { a.emit_mov(Size::S32, addr, Location::GPR(tmp_addr)); // This branch is used for emitting "faster" code for the special case of (offset + value_size) not exceeding u32 range. - match (offset as u32).checked_add(value_size as u32) { + match (memarg.offset as u32).checked_add(value_size as u32) { + Some(0) => {} Some(x) => { a.emit_add(Size::S64, Location::Imm32(x), Location::GPR(tmp_addr)); } None => { a.emit_add( Size::S64, - Location::Imm32(offset as u32), + Location::Imm32(memarg.offset as u32), Location::GPR(tmp_addr), ); a.emit_add( @@ -1477,25 +1834,103 @@ impl X64FunctionCode { a.emit_add(Size::S64, Location::GPR(tmp_base), Location::GPR(tmp_addr)); a.emit_cmp(Size::S64, Location::GPR(tmp_bound), Location::GPR(tmp_addr)); a.emit_conditional_trap(Condition::Above); - } - m.release_temp_gpr(tmp_bound); + m.release_temp_gpr(tmp_bound); + } // Calculates the real address, and loads from it. a.emit_mov(Size::S32, addr, Location::GPR(tmp_addr)); - a.emit_add( - Size::S64, - Location::Imm32(offset as u32), - Location::GPR(tmp_addr), - ); + if memarg.offset != 0 { + a.emit_add( + Size::S64, + Location::Imm32(memarg.offset as u32), + Location::GPR(tmp_addr), + ); + } a.emit_add(Size::S64, Location::GPR(tmp_base), Location::GPR(tmp_addr)); m.release_temp_gpr(tmp_base); + let align = match memarg.flags & 3 { + 0 => 1, + 1 => 2, + 2 => 4, + 3 => 8, + _ => unreachable!("this match is fully covered"), + }; + if check_alignment && align != 1 { + let tmp_aligncheck = m.acquire_temp_gpr().unwrap(); + a.emit_mov( + Size::S32, + Location::GPR(tmp_addr), + Location::GPR(tmp_aligncheck), + ); + a.emit_and( + Size::S64, + Location::Imm32(align - 1), + Location::GPR(tmp_aligncheck), + ); + a.emit_conditional_trap(Condition::NotEqual); + m.release_temp_gpr(tmp_aligncheck); + } + cb(a, m, tmp_addr); m.release_temp_gpr(tmp_addr); } + /// Emits a memory operation. + fn emit_compare_and_swap( + module_info: &ModuleInfo, + config: &CodegenConfig, + a: &mut Assembler, + m: &mut Machine, + loc: Location, + target: Location, + ret: Location, + memarg: &MemoryImmediate, + value_size: usize, + memory_sz: Size, + stack_sz: Size, + cb: F, + ) { + assert!(memory_sz <= stack_sz); + + let compare = m.reserve_unused_temp_gpr(GPR::RAX); + let value = if loc == Location::GPR(GPR::R14) { + GPR::R13 + } else { + GPR::R14 + }; + a.emit_push(Size::S64, Location::GPR(value)); + + a.emit_mov(stack_sz, loc, Location::GPR(value)); + + let retry = a.get_label(); + a.emit_label(retry); + + Self::emit_memory_op( + module_info, + config, + a, + m, + target, + memarg, + true, + value_size, + |a, m, addr| { + a.emit_mov(memory_sz, Location::Memory(addr, 0), Location::GPR(compare)); + a.emit_mov(stack_sz, Location::GPR(compare), ret); + cb(a, m, compare, value); + a.emit_lock_cmpxchg(memory_sz, Location::GPR(value), Location::Memory(addr, 0)); + }, + ); + + a.emit_jmp(Condition::NotEqual, retry); + + a.emit_pop(Size::S64, Location::GPR(value)); + m.release_temp_gpr(compare); + } + // Checks for underflow/overflow/nan before IxxTrunc{U/S}F32. fn emit_f32_int_conv_check( a: &mut Assembler, @@ -1598,7 +2033,7 @@ impl X64FunctionCode { control_stack: &mut [ControlFrame], ) -> usize { if !m.track_state { - return ::std::usize::MAX; + return usize::MAX; } let last_frame = control_stack.last_mut().unwrap(); let mut diff = m.state.diff(&last_frame.state); @@ -1630,6 +2065,14 @@ impl FunctionCodeGenerator for X64FunctionCode { fn begin_body(&mut self, _module_info: &ModuleInfo) -> Result<(), CodegenError> { let a = self.assembler.as_mut().unwrap(); + let start_label = a.get_label(); + // skip the patchpoint during normal execution + a.emit_jmp(Condition::None, start_label); + // patchpoint of 32 1-byte nops + for _ in 0..32 { + a.emit_nop(); + } + a.emit_label(start_label); a.emit_push(Size::S64, Location::GPR(GPR::RBP)); a.emit_mov(Size::S64, Location::GPR(GPR::RSP), Location::GPR(GPR::RBP)); @@ -1694,6 +2137,7 @@ impl FunctionCodeGenerator for X64FunctionCode { self.fsm.loop_offsets.insert( a.get_offset().0, OffsetInfo { + end_offset: a.get_offset().0 + 1, activate_offset, diff_id: state_diff_id, }, @@ -1705,7 +2149,7 @@ impl FunctionCodeGenerator for X64FunctionCode { Location::GPR(GPR::RAX), ); - assert_eq!(self.machine.state.wasm_inst_offset, ::std::usize::MAX); + assert_eq!(self.machine.state.wasm_inst_offset, usize::MAX); Ok(()) } @@ -1769,11 +2213,18 @@ impl FunctionCodeGenerator for X64FunctionCode { Event::Internal(x) => { match x { InternalEvent::Breakpoint(callback) => { - a.emit_bkpt(); + use wasmer_runtime_core::backend::InlineBreakpointType; self.breakpoints .as_mut() .unwrap() .insert(a.get_offset(), callback); + Self::mark_trappable( + a, + &self.machine, + &mut self.fsm, + &mut self.control_stack, + ); + a.emit_inline_breakpoint(InlineBreakpointType::Middleware); } InternalEvent::FunctionBegin(_) | InternalEvent::FunctionEnd => {} InternalEvent::GetInternal(idx) => { @@ -2193,18 +2644,117 @@ impl FunctionCodeGenerator for X64FunctionCode { Condition::Equal, Location::Imm32(0), ), - Operator::I32Clz => Self::emit_xcnt_i32( - a, - &mut self.machine, - &mut self.value_stack, - Assembler::emit_lzcnt, - ), - Operator::I32Ctz => Self::emit_xcnt_i32( - a, - &mut self.machine, - &mut self.value_stack, - Assembler::emit_tzcnt, - ), + Operator::I32Clz => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let src = match loc { + Location::Imm32(_) | Location::Memory(_, _) => { + let tmp = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(tmp)); + tmp + } + Location::GPR(reg) => reg, + _ => unreachable!(), + }; + + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let dst = match ret { + Location::Memory(_, _) => self.machine.acquire_temp_gpr().unwrap(), + Location::GPR(reg) => reg, + _ => unreachable!(), + }; + + if a.arch_has_xzcnt() { + a.arch_emit_lzcnt(Size::S32, Location::GPR(src), Location::GPR(dst)); + } else { + let zero_path = a.get_label(); + let end = a.get_label(); + + a.emit_test_gpr_64(src); + a.emit_jmp(Condition::Equal, zero_path); + a.emit_bsr(Size::S32, Location::GPR(src), Location::GPR(dst)); + a.emit_xor(Size::S32, Location::Imm32(31), Location::GPR(dst)); + a.emit_jmp(Condition::None, end); + a.emit_label(zero_path); + a.emit_mov(Size::S32, Location::Imm32(32), Location::GPR(dst)); + a.emit_label(end); + } + + match loc { + Location::Imm32(_) | Location::Memory(_, _) => { + self.machine.release_temp_gpr(src); + } + _ => {} + }; + match ret { + Location::Memory(_, _) => { + a.emit_mov(Size::S32, Location::GPR(dst), ret); + self.machine.release_temp_gpr(dst); + } + _ => {} + }; + } + Operator::I32Ctz => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let src = match loc { + Location::Imm32(_) | Location::Memory(_, _) => { + let tmp = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(tmp)); + tmp + } + Location::GPR(reg) => reg, + _ => unreachable!(), + }; + + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let dst = match ret { + Location::Memory(_, _) => self.machine.acquire_temp_gpr().unwrap(), + Location::GPR(reg) => reg, + _ => unreachable!(), + }; + + if a.arch_has_xzcnt() { + a.arch_emit_tzcnt(Size::S32, Location::GPR(src), Location::GPR(dst)); + } else { + let zero_path = a.get_label(); + let end = a.get_label(); + + a.emit_test_gpr_64(src); + a.emit_jmp(Condition::Equal, zero_path); + a.emit_bsf(Size::S32, Location::GPR(src), Location::GPR(dst)); + a.emit_jmp(Condition::None, end); + a.emit_label(zero_path); + a.emit_mov(Size::S32, Location::Imm32(32), Location::GPR(dst)); + a.emit_label(end); + } + + match loc { + Location::Imm32(_) | Location::Memory(_, _) => { + self.machine.release_temp_gpr(src); + } + _ => {} + }; + match ret { + Location::Memory(_, _) => { + a.emit_mov(Size::S32, Location::GPR(dst), ret); + self.machine.release_temp_gpr(dst); + } + _ => {} + }; + } Operator::I32Popcnt => Self::emit_xcnt_i32( a, &mut self.machine, @@ -2484,18 +3034,117 @@ impl FunctionCodeGenerator for X64FunctionCode { Condition::Equal, Location::Imm64(0), ), - Operator::I64Clz => Self::emit_xcnt_i64( - a, - &mut self.machine, - &mut self.value_stack, - Assembler::emit_lzcnt, - ), - Operator::I64Ctz => Self::emit_xcnt_i64( - a, - &mut self.machine, - &mut self.value_stack, - Assembler::emit_tzcnt, - ), + Operator::I64Clz => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let src = match loc { + Location::Imm64(_) | Location::Imm32(_) | Location::Memory(_, _) => { + let tmp = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S64, loc, Location::GPR(tmp)); + tmp + } + Location::GPR(reg) => reg, + _ => unreachable!(), + }; + + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let dst = match ret { + Location::Memory(_, _) => self.machine.acquire_temp_gpr().unwrap(), + Location::GPR(reg) => reg, + _ => unreachable!(), + }; + + if a.arch_has_xzcnt() { + a.arch_emit_lzcnt(Size::S64, Location::GPR(src), Location::GPR(dst)); + } else { + let zero_path = a.get_label(); + let end = a.get_label(); + + a.emit_test_gpr_64(src); + a.emit_jmp(Condition::Equal, zero_path); + a.emit_bsr(Size::S64, Location::GPR(src), Location::GPR(dst)); + a.emit_xor(Size::S64, Location::Imm32(63), Location::GPR(dst)); + a.emit_jmp(Condition::None, end); + a.emit_label(zero_path); + a.emit_mov(Size::S64, Location::Imm32(64), Location::GPR(dst)); + a.emit_label(end); + } + + match loc { + Location::Imm64(_) | Location::Imm32(_) | Location::Memory(_, _) => { + self.machine.release_temp_gpr(src); + } + _ => {} + }; + match ret { + Location::Memory(_, _) => { + a.emit_mov(Size::S64, Location::GPR(dst), ret); + self.machine.release_temp_gpr(dst); + } + _ => {} + }; + } + Operator::I64Ctz => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let src = match loc { + Location::Imm64(_) | Location::Imm32(_) | Location::Memory(_, _) => { + let tmp = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S64, loc, Location::GPR(tmp)); + tmp + } + Location::GPR(reg) => reg, + _ => unreachable!(), + }; + + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let dst = match ret { + Location::Memory(_, _) => self.machine.acquire_temp_gpr().unwrap(), + Location::GPR(reg) => reg, + _ => unreachable!(), + }; + + if a.arch_has_xzcnt() { + a.arch_emit_tzcnt(Size::S64, Location::GPR(src), Location::GPR(dst)); + } else { + let zero_path = a.get_label(); + let end = a.get_label(); + + a.emit_test_gpr_64(src); + a.emit_jmp(Condition::Equal, zero_path); + a.emit_bsf(Size::S64, Location::GPR(src), Location::GPR(dst)); + a.emit_jmp(Condition::None, end); + a.emit_label(zero_path); + a.emit_mov(Size::S64, Location::Imm32(64), Location::GPR(dst)); + a.emit_label(end); + } + + match loc { + Location::Imm64(_) | Location::Imm32(_) | Location::Memory(_, _) => { + self.machine.release_temp_gpr(src); + } + _ => {} + }; + match ret { + Location::Memory(_, _) => { + a.emit_mov(Size::S64, Location::GPR(dst), ret); + self.machine.release_temp_gpr(dst); + } + _ => {} + }; + } Operator::I64Popcnt => Self::emit_xcnt_i64( a, &mut self.machine, @@ -2614,6 +3263,106 @@ impl FunctionCodeGenerator for X64FunctionCode { ret, ); } + Operator::I32Extend8S => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_relaxed_zx_sx( + a, + &mut self.machine, + Assembler::emit_movsx, + Size::S8, + loc, + Size::S32, + ret, + ); + } + Operator::I32Extend16S => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_relaxed_zx_sx( + a, + &mut self.machine, + Assembler::emit_movsx, + Size::S16, + loc, + Size::S32, + ret, + ); + } + Operator::I64Extend8S => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_relaxed_zx_sx( + a, + &mut self.machine, + Assembler::emit_movsx, + Size::S8, + loc, + Size::S64, + ret, + ); + } + Operator::I64Extend16S => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_relaxed_zx_sx( + a, + &mut self.machine, + Assembler::emit_movsx, + Size::S16, + loc, + Size::S64, + ret, + ); + } + Operator::I64Extend32S => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_relaxed_zx_sx( + a, + &mut self.machine, + Assembler::emit_movsx, + Size::S32, + loc, + Size::S64, + ret, + ); + } Operator::I32WrapI64 => { let loc = get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); @@ -2664,18 +3413,234 @@ impl FunctionCodeGenerator for X64FunctionCode { &mut self.value_stack, Assembler::emit_vdivss, ), - Operator::F32Max => Self::emit_fp_binop_avx( - a, - &mut self.machine, - &mut self.value_stack, - Assembler::emit_vmaxss, - ), - Operator::F32Min => Self::emit_fp_binop_avx( - a, - &mut self.machine, - &mut self.value_stack, - Assembler::emit_vminss, - ), + Operator::F32Max => { + if !a.arch_supports_canonicalize_nan() { + Self::emit_fp_binop_avx( + a, + &mut self.machine, + &mut self.value_stack, + Assembler::emit_vmaxss, + ); + } else { + let src2 = get_location_released( + a, + &mut self.machine, + self.value_stack.pop().unwrap(), + ); + let src1 = get_location_released( + a, + &mut self.machine, + self.value_stack.pop().unwrap(), + ); + let ret = self.machine.acquire_locations( + a, + &[(WpType::F64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let tmp1 = self.machine.acquire_temp_xmm().unwrap(); + let tmp2 = self.machine.acquire_temp_xmm().unwrap(); + let tmpg1 = self.machine.acquire_temp_gpr().unwrap(); + let tmpg2 = self.machine.acquire_temp_gpr().unwrap(); + + let src1 = match src1 { + Location::XMM(x) => x, + Location::GPR(_) | Location::Memory(_, _) => { + a.emit_mov(Size::S64, src1, Location::XMM(tmp1)); + tmp1 + } + Location::Imm32(_) => { + a.emit_mov(Size::S32, src1, Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::GPR(tmpg1), Location::XMM(tmp1)); + tmp1 + } + Location::Imm64(_) => { + a.emit_mov(Size::S64, src1, Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp1)); + tmp1 + } + _ => unreachable!(), + }; + let src2 = match src2 { + Location::XMM(x) => x, + Location::GPR(_) | Location::Memory(_, _) => { + a.emit_mov(Size::S64, src2, Location::XMM(tmp2)); + tmp2 + } + Location::Imm32(_) => { + a.emit_mov(Size::S32, src2, Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::GPR(tmpg1), Location::XMM(tmp2)); + tmp2 + } + Location::Imm64(_) => { + a.emit_mov(Size::S64, src2, Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp2)); + tmp2 + } + _ => unreachable!(), + }; + + let tmp_xmm1 = XMM::XMM8; + let tmp_xmm2 = XMM::XMM9; + let tmp_xmm3 = XMM::XMM10; + + a.emit_mov(Size::S32, Location::XMM(src1), Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::XMM(src2), Location::GPR(tmpg2)); + a.emit_cmp(Size::S32, Location::GPR(tmpg2), Location::GPR(tmpg1)); + a.emit_vmaxss(src1, XMMOrMemory::XMM(src2), tmp_xmm1); + let label1 = a.get_label(); + let label2 = a.get_label(); + a.emit_jmp(Condition::NotEqual, label1); + a.emit_vmovaps(XMMOrMemory::XMM(tmp_xmm1), XMMOrMemory::XMM(tmp_xmm2)); + a.emit_jmp(Condition::None, label2); + a.emit_label(label1); + a.emit_vxorps(tmp_xmm2, XMMOrMemory::XMM(tmp_xmm2), tmp_xmm2); + a.emit_label(label2); + a.emit_vcmpeqss(src1, XMMOrMemory::XMM(src2), tmp_xmm3); + a.emit_vblendvps(tmp_xmm3, XMMOrMemory::XMM(tmp_xmm2), tmp_xmm1, tmp_xmm1); + a.emit_vcmpunordss(src1, XMMOrMemory::XMM(src2), src1); + // load float canonical nan + a.emit_mov( + Size::S64, + Location::Imm32(0x7FC0_0000), // Canonical NaN + Location::GPR(tmpg1), + ); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(src2)); + a.emit_vblendvps(src1, XMMOrMemory::XMM(src2), tmp_xmm1, src1); + match ret { + Location::XMM(x) => { + a.emit_vmovaps(XMMOrMemory::XMM(src1), XMMOrMemory::XMM(x)); + } + Location::Memory(_, _) | Location::GPR(_) => { + a.emit_mov(Size::S64, Location::XMM(src1), ret); + } + _ => unreachable!(), + } + + self.machine.release_temp_gpr(tmpg2); + self.machine.release_temp_gpr(tmpg1); + self.machine.release_temp_xmm(tmp2); + self.machine.release_temp_xmm(tmp1); + } + } + Operator::F32Min => { + if !a.arch_supports_canonicalize_nan() { + Self::emit_fp_binop_avx( + a, + &mut self.machine, + &mut self.value_stack, + Assembler::emit_vminss, + ); + } else { + let src2 = get_location_released( + a, + &mut self.machine, + self.value_stack.pop().unwrap(), + ); + let src1 = get_location_released( + a, + &mut self.machine, + self.value_stack.pop().unwrap(), + ); + let ret = self.machine.acquire_locations( + a, + &[(WpType::F64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let tmp1 = self.machine.acquire_temp_xmm().unwrap(); + let tmp2 = self.machine.acquire_temp_xmm().unwrap(); + let tmpg1 = self.machine.acquire_temp_gpr().unwrap(); + let tmpg2 = self.machine.acquire_temp_gpr().unwrap(); + + let src1 = match src1 { + Location::XMM(x) => x, + Location::GPR(_) | Location::Memory(_, _) => { + a.emit_mov(Size::S64, src1, Location::XMM(tmp1)); + tmp1 + } + Location::Imm32(_) => { + a.emit_mov(Size::S32, src1, Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::GPR(tmpg1), Location::XMM(tmp1)); + tmp1 + } + Location::Imm64(_) => { + a.emit_mov(Size::S64, src1, Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp1)); + tmp1 + } + _ => unreachable!(), + }; + let src2 = match src2 { + Location::XMM(x) => x, + Location::GPR(_) | Location::Memory(_, _) => { + a.emit_mov(Size::S64, src2, Location::XMM(tmp2)); + tmp2 + } + Location::Imm32(_) => { + a.emit_mov(Size::S32, src2, Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::GPR(tmpg1), Location::XMM(tmp2)); + tmp2 + } + Location::Imm64(_) => { + a.emit_mov(Size::S64, src2, Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp2)); + tmp2 + } + _ => unreachable!(), + }; + + let tmp_xmm1 = XMM::XMM8; + let tmp_xmm2 = XMM::XMM9; + let tmp_xmm3 = XMM::XMM10; + + a.emit_mov(Size::S32, Location::XMM(src1), Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::XMM(src2), Location::GPR(tmpg2)); + a.emit_cmp(Size::S32, Location::GPR(tmpg2), Location::GPR(tmpg1)); + a.emit_vminss(src1, XMMOrMemory::XMM(src2), tmp_xmm1); + let label1 = a.get_label(); + let label2 = a.get_label(); + a.emit_jmp(Condition::NotEqual, label1); + a.emit_vmovaps(XMMOrMemory::XMM(tmp_xmm1), XMMOrMemory::XMM(tmp_xmm2)); + a.emit_jmp(Condition::None, label2); + a.emit_label(label1); + // load float -0.0 + a.emit_mov( + Size::S64, + Location::Imm32(0x8000_0000), // Negative zero + Location::GPR(tmpg1), + ); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp_xmm2)); + a.emit_label(label2); + a.emit_vcmpeqss(src1, XMMOrMemory::XMM(src2), tmp_xmm3); + a.emit_vblendvps(tmp_xmm3, XMMOrMemory::XMM(tmp_xmm2), tmp_xmm1, tmp_xmm1); + a.emit_vcmpunordss(src1, XMMOrMemory::XMM(src2), src1); + // load float canonical nan + a.emit_mov( + Size::S64, + Location::Imm32(0x7FC0_0000), // Canonical NaN + Location::GPR(tmpg1), + ); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(src2)); + a.emit_vblendvps(src1, XMMOrMemory::XMM(src2), tmp_xmm1, src1); + match ret { + Location::XMM(x) => { + a.emit_vmovaps(XMMOrMemory::XMM(src1), XMMOrMemory::XMM(x)); + } + Location::Memory(_, _) | Location::GPR(_) => { + a.emit_mov(Size::S64, Location::XMM(src1), ret); + } + _ => unreachable!(), + } + + self.machine.release_temp_gpr(tmpg2); + self.machine.release_temp_gpr(tmpg1); + self.machine.release_temp_xmm(tmp2); + self.machine.release_temp_xmm(tmp1); + } + } Operator::F32Eq => Self::emit_fp_cmpop_avx( a, &mut self.machine, @@ -2804,11 +3769,34 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S32, loc, Location::GPR(tmp)); - a.emit_btc_gpr_imm8_32(31, tmp); - a.emit_mov(Size::S32, Location::GPR(tmp), ret); - self.machine.release_temp_gpr(tmp); + + if a.arch_has_fneg() { + let tmp = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp), + ); + a.arch_emit_f32_neg(tmp, tmp); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::XMM(tmp), + ret, + ); + self.machine.release_temp_xmm(tmp); + } else { + let tmp = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(tmp)); + a.emit_btc_gpr_imm8_32(31, tmp); + a.emit_mov(Size::S32, Location::GPR(tmp), ret); + self.machine.release_temp_gpr(tmp); + } } Operator::F64Const { value } => { @@ -2842,18 +3830,234 @@ impl FunctionCodeGenerator for X64FunctionCode { &mut self.value_stack, Assembler::emit_vdivsd, ), - Operator::F64Max => Self::emit_fp_binop_avx( - a, - &mut self.machine, - &mut self.value_stack, - Assembler::emit_vmaxsd, - ), - Operator::F64Min => Self::emit_fp_binop_avx( - a, - &mut self.machine, - &mut self.value_stack, - Assembler::emit_vminsd, - ), + Operator::F64Max => { + if !a.arch_supports_canonicalize_nan() { + Self::emit_fp_binop_avx( + a, + &mut self.machine, + &mut self.value_stack, + Assembler::emit_vmaxsd, + ); + } else { + let src2 = get_location_released( + a, + &mut self.machine, + self.value_stack.pop().unwrap(), + ); + let src1 = get_location_released( + a, + &mut self.machine, + self.value_stack.pop().unwrap(), + ); + let ret = self.machine.acquire_locations( + a, + &[(WpType::F64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let tmp1 = self.machine.acquire_temp_xmm().unwrap(); + let tmp2 = self.machine.acquire_temp_xmm().unwrap(); + let tmpg1 = self.machine.acquire_temp_gpr().unwrap(); + let tmpg2 = self.machine.acquire_temp_gpr().unwrap(); + + let src1 = match src1 { + Location::XMM(x) => x, + Location::GPR(_) | Location::Memory(_, _) => { + a.emit_mov(Size::S64, src1, Location::XMM(tmp1)); + tmp1 + } + Location::Imm32(_) => { + a.emit_mov(Size::S32, src1, Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::GPR(tmpg1), Location::XMM(tmp1)); + tmp1 + } + Location::Imm64(_) => { + a.emit_mov(Size::S64, src1, Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp1)); + tmp1 + } + _ => unreachable!(), + }; + let src2 = match src2 { + Location::XMM(x) => x, + Location::GPR(_) | Location::Memory(_, _) => { + a.emit_mov(Size::S64, src2, Location::XMM(tmp2)); + tmp2 + } + Location::Imm32(_) => { + a.emit_mov(Size::S32, src2, Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::GPR(tmpg1), Location::XMM(tmp2)); + tmp2 + } + Location::Imm64(_) => { + a.emit_mov(Size::S64, src2, Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp2)); + tmp2 + } + _ => unreachable!(), + }; + + let tmp_xmm1 = XMM::XMM8; + let tmp_xmm2 = XMM::XMM9; + let tmp_xmm3 = XMM::XMM10; + + a.emit_mov(Size::S64, Location::XMM(src1), Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::XMM(src2), Location::GPR(tmpg2)); + a.emit_cmp(Size::S64, Location::GPR(tmpg2), Location::GPR(tmpg1)); + a.emit_vmaxsd(src1, XMMOrMemory::XMM(src2), tmp_xmm1); + let label1 = a.get_label(); + let label2 = a.get_label(); + a.emit_jmp(Condition::NotEqual, label1); + a.emit_vmovapd(XMMOrMemory::XMM(tmp_xmm1), XMMOrMemory::XMM(tmp_xmm2)); + a.emit_jmp(Condition::None, label2); + a.emit_label(label1); + a.emit_vxorpd(tmp_xmm2, XMMOrMemory::XMM(tmp_xmm2), tmp_xmm2); + a.emit_label(label2); + a.emit_vcmpeqsd(src1, XMMOrMemory::XMM(src2), tmp_xmm3); + a.emit_vblendvpd(tmp_xmm3, XMMOrMemory::XMM(tmp_xmm2), tmp_xmm1, tmp_xmm1); + a.emit_vcmpunordsd(src1, XMMOrMemory::XMM(src2), src1); + // load float canonical nan + a.emit_mov( + Size::S64, + Location::Imm64(0x7FF8_0000_0000_0000), // Canonical NaN + Location::GPR(tmpg1), + ); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(src2)); + a.emit_vblendvpd(src1, XMMOrMemory::XMM(src2), tmp_xmm1, src1); + match ret { + Location::XMM(x) => { + a.emit_vmovapd(XMMOrMemory::XMM(src1), XMMOrMemory::XMM(x)); + } + Location::Memory(_, _) | Location::GPR(_) => { + a.emit_mov(Size::S64, Location::XMM(src1), ret); + } + _ => unreachable!(), + } + + self.machine.release_temp_gpr(tmpg2); + self.machine.release_temp_gpr(tmpg1); + self.machine.release_temp_xmm(tmp2); + self.machine.release_temp_xmm(tmp1); + } + } + Operator::F64Min => { + if !a.arch_supports_canonicalize_nan() { + Self::emit_fp_binop_avx( + a, + &mut self.machine, + &mut self.value_stack, + Assembler::emit_vminsd, + ); + } else { + let src2 = get_location_released( + a, + &mut self.machine, + self.value_stack.pop().unwrap(), + ); + let src1 = get_location_released( + a, + &mut self.machine, + self.value_stack.pop().unwrap(), + ); + let ret = self.machine.acquire_locations( + a, + &[(WpType::F64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let tmp1 = self.machine.acquire_temp_xmm().unwrap(); + let tmp2 = self.machine.acquire_temp_xmm().unwrap(); + let tmpg1 = self.machine.acquire_temp_gpr().unwrap(); + let tmpg2 = self.machine.acquire_temp_gpr().unwrap(); + + let src1 = match src1 { + Location::XMM(x) => x, + Location::GPR(_) | Location::Memory(_, _) => { + a.emit_mov(Size::S64, src1, Location::XMM(tmp1)); + tmp1 + } + Location::Imm32(_) => { + a.emit_mov(Size::S32, src1, Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::GPR(tmpg1), Location::XMM(tmp1)); + tmp1 + } + Location::Imm64(_) => { + a.emit_mov(Size::S64, src1, Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp1)); + tmp1 + } + _ => unreachable!(), + }; + let src2 = match src2 { + Location::XMM(x) => x, + Location::GPR(_) | Location::Memory(_, _) => { + a.emit_mov(Size::S64, src2, Location::XMM(tmp2)); + tmp2 + } + Location::Imm32(_) => { + a.emit_mov(Size::S32, src2, Location::GPR(tmpg1)); + a.emit_mov(Size::S32, Location::GPR(tmpg1), Location::XMM(tmp2)); + tmp2 + } + Location::Imm64(_) => { + a.emit_mov(Size::S64, src2, Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp2)); + tmp2 + } + _ => unreachable!(), + }; + + let tmp_xmm1 = XMM::XMM8; + let tmp_xmm2 = XMM::XMM9; + let tmp_xmm3 = XMM::XMM10; + + a.emit_mov(Size::S64, Location::XMM(src1), Location::GPR(tmpg1)); + a.emit_mov(Size::S64, Location::XMM(src2), Location::GPR(tmpg2)); + a.emit_cmp(Size::S64, Location::GPR(tmpg2), Location::GPR(tmpg1)); + a.emit_vminsd(src1, XMMOrMemory::XMM(src2), tmp_xmm1); + let label1 = a.get_label(); + let label2 = a.get_label(); + a.emit_jmp(Condition::NotEqual, label1); + a.emit_vmovapd(XMMOrMemory::XMM(tmp_xmm1), XMMOrMemory::XMM(tmp_xmm2)); + a.emit_jmp(Condition::None, label2); + a.emit_label(label1); + // load float -0.0 + a.emit_mov( + Size::S64, + Location::Imm64(0x8000_0000_0000_0000), // Negative zero + Location::GPR(tmpg1), + ); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(tmp_xmm2)); + a.emit_label(label2); + a.emit_vcmpeqsd(src1, XMMOrMemory::XMM(src2), tmp_xmm3); + a.emit_vblendvpd(tmp_xmm3, XMMOrMemory::XMM(tmp_xmm2), tmp_xmm1, tmp_xmm1); + a.emit_vcmpunordsd(src1, XMMOrMemory::XMM(src2), src1); + // load float canonical nan + a.emit_mov( + Size::S64, + Location::Imm64(0x7FF8_0000_0000_0000), // Canonical NaN + Location::GPR(tmpg1), + ); + a.emit_mov(Size::S64, Location::GPR(tmpg1), Location::XMM(src2)); + a.emit_vblendvpd(src1, XMMOrMemory::XMM(src2), tmp_xmm1, src1); + match ret { + Location::XMM(x) => { + a.emit_vmovaps(XMMOrMemory::XMM(src1), XMMOrMemory::XMM(x)); + } + Location::Memory(_, _) | Location::GPR(_) => { + a.emit_mov(Size::S64, Location::XMM(src1), ret); + } + _ => unreachable!(), + } + + self.machine.release_temp_gpr(tmpg2); + self.machine.release_temp_gpr(tmpg1); + self.machine.release_temp_xmm(tmp2); + self.machine.release_temp_xmm(tmp1); + } + } Operator::F64Eq => Self::emit_fp_cmpop_avx( a, &mut self.machine, @@ -2997,11 +4201,33 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S64, loc, Location::GPR(tmp)); - a.emit_btc_gpr_imm8_64(63, tmp); - a.emit_mov(Size::S64, Location::GPR(tmp), ret); - self.machine.release_temp_gpr(tmp); + if a.arch_has_fneg() { + let tmp = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp), + ); + a.arch_emit_f64_neg(tmp, tmp); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::XMM(tmp), + ret, + ); + self.machine.release_temp_xmm(tmp); + } else { + let tmp = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S64, loc, Location::GPR(tmp)); + a.emit_btc_gpr_imm8_64(63, tmp); + a.emit_mov(Size::S64, Location::GPR(tmp), ret); + self.machine.release_temp_gpr(tmp); + } } Operator::F64PromoteF32 => Self::emit_fp_unop_avx( @@ -3112,24 +4338,48 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_gpr().unwrap(); - let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - Self::emit_relaxed_binop( - a, - &mut self.machine, - Assembler::emit_mov, - Size::S32, - loc, - Location::XMM(tmp_in), - ); - Self::emit_f32_int_conv_check(a, &mut self.machine, tmp_in, -1.0, 4294967296.0); + if a.arch_has_itruncf() { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); + a.arch_emit_i32_trunc_uf32(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::GPR(tmp_out), + ret, + ); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); + Self::emit_f32_int_conv_check(a, &mut self.machine, tmp_in, -1.0, 4294967296.0); - a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); - a.emit_mov(Size::S32, Location::GPR(tmp_out), ret); + a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); + a.emit_mov(Size::S32, Location::GPR(tmp_out), ret); - self.machine.release_temp_xmm(tmp_in); - self.machine.release_temp_gpr(tmp_out); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } } Operator::I32TruncSF32 => { @@ -3141,30 +4391,55 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_gpr().unwrap(); - let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - Self::emit_relaxed_binop( - a, - &mut self.machine, - Assembler::emit_mov, - Size::S32, - loc, - Location::XMM(tmp_in), - ); - Self::emit_f32_int_conv_check( - a, - &mut self.machine, - tmp_in, - -2147483904.0, - 2147483648.0, - ); + if a.arch_has_itruncf() { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); + a.arch_emit_i32_trunc_sf32(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::GPR(tmp_out), + ret, + ); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - a.emit_cvttss2si_32(XMMOrMemory::XMM(tmp_in), tmp_out); - a.emit_mov(Size::S32, Location::GPR(tmp_out), ret); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); + Self::emit_f32_int_conv_check( + a, + &mut self.machine, + tmp_in, + -2147483904.0, + 2147483648.0, + ); - self.machine.release_temp_xmm(tmp_in); - self.machine.release_temp_gpr(tmp_out); + a.emit_cvttss2si_32(XMMOrMemory::XMM(tmp_in), tmp_out); + a.emit_mov(Size::S32, Location::GPR(tmp_out), ret); + + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } } Operator::I64TruncSF32 => { @@ -3176,47 +4451,57 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_gpr().unwrap(); - let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - Self::emit_relaxed_binop( - a, - &mut self.machine, - Assembler::emit_mov, - Size::S32, - loc, - Location::XMM(tmp_in), - ); - Self::emit_f32_int_conv_check( - a, - &mut self.machine, - tmp_in, - -9223373136366403584.0, - 9223372036854775808.0, - ); - a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); - a.emit_mov(Size::S64, Location::GPR(tmp_out), ret); + if a.arch_has_itruncf() { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); + a.arch_emit_i64_trunc_sf32(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::GPR(tmp_out), + ret, + ); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - self.machine.release_temp_xmm(tmp_in); - self.machine.release_temp_gpr(tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); + Self::emit_f32_int_conv_check( + a, + &mut self.machine, + tmp_in, + -9223373136366403584.0, + 9223372036854775808.0, + ); + a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); + a.emit_mov(Size::S64, Location::GPR(tmp_out), ret); + + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } } Operator::I64TruncUF32 => { - /* - ; movq xmm5, r15 - ; mov r15d, 1593835520u32 as i32 //float 9.22337203E+18 - ; movd xmm1, r15d - ; movd xmm2, Rd(reg as u8) - ; movd xmm3, Rd(reg as u8) - ; subss xmm2, xmm1 - ; cvttss2si Rq(reg as u8), xmm2 - ; mov r15, QWORD 0x8000000000000000u64 as i64 - ; xor r15, Rq(reg as u8) - ; cvttss2si Rq(reg as u8), xmm3 - ; ucomiss xmm3, xmm1 - ; cmovae Rq(reg as u8), r15 - ; movq r15, xmm5 - */ let loc = get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); let ret = self.machine.acquire_locations( @@ -3225,54 +4510,79 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_gpr().unwrap(); - let tmp_in = self.machine.acquire_temp_xmm().unwrap(); // xmm2 - Self::emit_relaxed_binop( - a, - &mut self.machine, - Assembler::emit_mov, - Size::S32, - loc, - Location::XMM(tmp_in), - ); - Self::emit_f32_int_conv_check( - a, - &mut self.machine, - tmp_in, - -1.0, - 18446744073709551616.0, - ); + if a.arch_has_itruncf() { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); + a.arch_emit_i64_trunc_uf32(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::GPR(tmp_out), + ret, + ); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); // xmm2 - let tmp = self.machine.acquire_temp_gpr().unwrap(); // r15 - let tmp_x1 = self.machine.acquire_temp_xmm().unwrap(); // xmm1 - let tmp_x2 = self.machine.acquire_temp_xmm().unwrap(); // xmm3 + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::XMM(tmp_in), + ); + Self::emit_f32_int_conv_check( + a, + &mut self.machine, + tmp_in, + -1.0, + 18446744073709551616.0, + ); - a.emit_mov( - Size::S32, - Location::Imm32(1593835520u32), - Location::GPR(tmp), - ); //float 9.22337203E+18 - a.emit_mov(Size::S32, Location::GPR(tmp), Location::XMM(tmp_x1)); - a.emit_mov(Size::S32, Location::XMM(tmp_in), Location::XMM(tmp_x2)); - a.emit_vsubss(tmp_in, XMMOrMemory::XMM(tmp_x1), tmp_in); - a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); - a.emit_mov( - Size::S64, - Location::Imm64(0x8000000000000000u64), - Location::GPR(tmp), - ); - a.emit_xor(Size::S64, Location::GPR(tmp_out), Location::GPR(tmp)); - a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_x2), tmp_out); - a.emit_ucomiss(XMMOrMemory::XMM(tmp_x1), tmp_x2); - a.emit_cmovae_gpr_64(tmp, tmp_out); - a.emit_mov(Size::S64, Location::GPR(tmp_out), ret); + let tmp = self.machine.acquire_temp_gpr().unwrap(); // r15 + let tmp_x1 = self.machine.acquire_temp_xmm().unwrap(); // xmm1 + let tmp_x2 = self.machine.acquire_temp_xmm().unwrap(); // xmm3 - self.machine.release_temp_xmm(tmp_x2); - self.machine.release_temp_xmm(tmp_x1); - self.machine.release_temp_gpr(tmp); - self.machine.release_temp_xmm(tmp_in); - self.machine.release_temp_gpr(tmp_out); + a.emit_mov( + Size::S32, + Location::Imm32(1593835520u32), + Location::GPR(tmp), + ); //float 9.22337203E+18 + a.emit_mov(Size::S32, Location::GPR(tmp), Location::XMM(tmp_x1)); + a.emit_mov(Size::S32, Location::XMM(tmp_in), Location::XMM(tmp_x2)); + a.emit_vsubss(tmp_in, XMMOrMemory::XMM(tmp_x1), tmp_in); + a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); + a.emit_mov( + Size::S64, + Location::Imm64(0x8000000000000000u64), + Location::GPR(tmp), + ); + a.emit_xor(Size::S64, Location::GPR(tmp_out), Location::GPR(tmp)); + a.emit_cvttss2si_64(XMMOrMemory::XMM(tmp_x2), tmp_out); + a.emit_ucomiss(XMMOrMemory::XMM(tmp_x1), tmp_x2); + a.emit_cmovae_gpr_64(tmp, tmp_out); + a.emit_mov(Size::S64, Location::GPR(tmp_out), ret); + + self.machine.release_temp_xmm(tmp_x2); + self.machine.release_temp_xmm(tmp_x1); + self.machine.release_temp_gpr(tmp); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } } Operator::I32TruncUF64 => { @@ -3284,24 +4594,49 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_gpr().unwrap(); - let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - Self::emit_relaxed_binop( - a, - &mut self.machine, - Assembler::emit_mov, - Size::S64, - loc, - Location::XMM(tmp_in), - ); - Self::emit_f64_int_conv_check(a, &mut self.machine, tmp_in, -1.0, 4294967296.0); + if a.arch_has_itruncf() { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); + a.arch_emit_i32_trunc_uf64(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::GPR(tmp_out), + ret, + ); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); - a.emit_mov(Size::S32, Location::GPR(tmp_out), ret); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); + Self::emit_f64_int_conv_check(a, &mut self.machine, tmp_in, -1.0, 4294967296.0); - self.machine.release_temp_xmm(tmp_in); - self.machine.release_temp_gpr(tmp_out); + a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); + a.emit_mov(Size::S32, Location::GPR(tmp_out), ret); + + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } } Operator::I32TruncSF64 => { @@ -3313,35 +4648,60 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_gpr().unwrap(); - let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - let real_in = match loc { - Location::Imm32(_) | Location::Imm64(_) => { - a.emit_mov(Size::S64, loc, Location::GPR(tmp_out)); - a.emit_mov(Size::S64, Location::GPR(tmp_out), Location::XMM(tmp_in)); - tmp_in - } - Location::XMM(x) => x, - _ => { - a.emit_mov(Size::S64, loc, Location::XMM(tmp_in)); - tmp_in - } - }; + if a.arch_has_itruncf() { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); + a.arch_emit_i32_trunc_sf64(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::GPR(tmp_out), + ret, + ); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - Self::emit_f64_int_conv_check( - a, - &mut self.machine, - real_in, - -2147483649.0, - 2147483648.0, - ); + let real_in = match loc { + Location::Imm32(_) | Location::Imm64(_) => { + a.emit_mov(Size::S64, loc, Location::GPR(tmp_out)); + a.emit_mov(Size::S64, Location::GPR(tmp_out), Location::XMM(tmp_in)); + tmp_in + } + Location::XMM(x) => x, + _ => { + a.emit_mov(Size::S64, loc, Location::XMM(tmp_in)); + tmp_in + } + }; - a.emit_cvttsd2si_32(XMMOrMemory::XMM(real_in), tmp_out); - a.emit_mov(Size::S32, Location::GPR(tmp_out), ret); + Self::emit_f64_int_conv_check( + a, + &mut self.machine, + real_in, + -2147483649.0, + 2147483648.0, + ); - self.machine.release_temp_xmm(tmp_in); - self.machine.release_temp_gpr(tmp_out); + a.emit_cvttsd2si_32(XMMOrMemory::XMM(real_in), tmp_out); + a.emit_mov(Size::S32, Location::GPR(tmp_out), ret); + + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } } Operator::I64TruncSF64 => { @@ -3353,30 +4713,55 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_gpr().unwrap(); - let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - Self::emit_relaxed_binop( - a, - &mut self.machine, - Assembler::emit_mov, - Size::S64, - loc, - Location::XMM(tmp_in), - ); - Self::emit_f64_int_conv_check( - a, - &mut self.machine, - tmp_in, - -9223372036854777856.0, - 9223372036854775808.0, - ); + if a.arch_has_itruncf() { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); + a.arch_emit_i64_trunc_sf64(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::GPR(tmp_out), + ret, + ); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); - a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); - a.emit_mov(Size::S64, Location::GPR(tmp_out), ret); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); + Self::emit_f64_int_conv_check( + a, + &mut self.machine, + tmp_in, + -9223372036854777856.0, + 9223372036854775808.0, + ); - self.machine.release_temp_xmm(tmp_in); - self.machine.release_temp_gpr(tmp_out); + a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); + a.emit_mov(Size::S64, Location::GPR(tmp_out), ret); + + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } } Operator::I64TruncUF64 => { @@ -3388,54 +4773,79 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_gpr().unwrap(); - let tmp_in = self.machine.acquire_temp_xmm().unwrap(); // xmm2 - Self::emit_relaxed_binop( - a, - &mut self.machine, - Assembler::emit_mov, - Size::S64, - loc, - Location::XMM(tmp_in), - ); - Self::emit_f64_int_conv_check( - a, - &mut self.machine, - tmp_in, - -1.0, - 18446744073709551616.0, - ); + if a.arch_has_itruncf() { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); + a.arch_emit_i64_trunc_uf64(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::GPR(tmp_out), + ret, + ); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_gpr().unwrap(); + let tmp_in = self.machine.acquire_temp_xmm().unwrap(); // xmm2 - let tmp = self.machine.acquire_temp_gpr().unwrap(); // r15 - let tmp_x1 = self.machine.acquire_temp_xmm().unwrap(); // xmm1 - let tmp_x2 = self.machine.acquire_temp_xmm().unwrap(); // xmm3 + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::XMM(tmp_in), + ); + Self::emit_f64_int_conv_check( + a, + &mut self.machine, + tmp_in, + -1.0, + 18446744073709551616.0, + ); - a.emit_mov( - Size::S64, - Location::Imm64(4890909195324358656u64), - Location::GPR(tmp), - ); //double 9.2233720368547758E+18 - a.emit_mov(Size::S64, Location::GPR(tmp), Location::XMM(tmp_x1)); - a.emit_mov(Size::S64, Location::XMM(tmp_in), Location::XMM(tmp_x2)); - a.emit_vsubsd(tmp_in, XMMOrMemory::XMM(tmp_x1), tmp_in); - a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); - a.emit_mov( - Size::S64, - Location::Imm64(0x8000000000000000u64), - Location::GPR(tmp), - ); - a.emit_xor(Size::S64, Location::GPR(tmp_out), Location::GPR(tmp)); - a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_x2), tmp_out); - a.emit_ucomisd(XMMOrMemory::XMM(tmp_x1), tmp_x2); - a.emit_cmovae_gpr_64(tmp, tmp_out); - a.emit_mov(Size::S64, Location::GPR(tmp_out), ret); + let tmp = self.machine.acquire_temp_gpr().unwrap(); // r15 + let tmp_x1 = self.machine.acquire_temp_xmm().unwrap(); // xmm1 + let tmp_x2 = self.machine.acquire_temp_xmm().unwrap(); // xmm3 - self.machine.release_temp_xmm(tmp_x2); - self.machine.release_temp_xmm(tmp_x1); - self.machine.release_temp_gpr(tmp); - self.machine.release_temp_xmm(tmp_in); - self.machine.release_temp_gpr(tmp_out); + a.emit_mov( + Size::S64, + Location::Imm64(4890909195324358656u64), + Location::GPR(tmp), + ); //double 9.2233720368547758E+18 + a.emit_mov(Size::S64, Location::GPR(tmp), Location::XMM(tmp_x1)); + a.emit_mov(Size::S64, Location::XMM(tmp_in), Location::XMM(tmp_x2)); + a.emit_vsubsd(tmp_in, XMMOrMemory::XMM(tmp_x1), tmp_in); + a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_in), tmp_out); + a.emit_mov( + Size::S64, + Location::Imm64(0x8000000000000000u64), + Location::GPR(tmp), + ); + a.emit_xor(Size::S64, Location::GPR(tmp_out), Location::GPR(tmp)); + a.emit_cvttsd2si_64(XMMOrMemory::XMM(tmp_x2), tmp_out); + a.emit_ucomisd(XMMOrMemory::XMM(tmp_x1), tmp_x2); + a.emit_cmovae_gpr_64(tmp, tmp_out); + a.emit_mov(Size::S64, Location::GPR(tmp_out), ret); + + self.machine.release_temp_xmm(tmp_x2); + self.machine.release_temp_xmm(tmp_x1); + self.machine.release_temp_gpr(tmp); + self.machine.release_temp_xmm(tmp_in); + self.machine.release_temp_gpr(tmp_out); + } } Operator::F32ConvertSI32 => { @@ -3447,15 +4857,40 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_xmm().unwrap(); - let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S32, loc, Location::GPR(tmp_in)); - a.emit_vcvtsi2ss_32(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_mov(Size::S32, Location::XMM(tmp_out), ret); + if a.arch_has_fconverti() { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::GPR(tmp_in), + ); + a.arch_emit_f32_convert_si32(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::XMM(tmp_out), + ret, + ); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - self.machine.release_temp_gpr(tmp_in); - self.machine.release_temp_xmm(tmp_out); + a.emit_mov(Size::S32, loc, Location::GPR(tmp_in)); + a.emit_vcvtsi2ss_32(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_mov(Size::S32, Location::XMM(tmp_out), ret); + + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } } Operator::F32ConvertUI32 => { let loc = @@ -3466,15 +4901,39 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_xmm().unwrap(); - let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + if a.arch_has_fconverti() { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::GPR(tmp_in), + ); + a.arch_emit_f32_convert_ui32(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::XMM(tmp_out), + ret, + ); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S32, loc, Location::GPR(tmp_in)); - a.emit_vcvtsi2ss_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_mov(Size::S32, Location::XMM(tmp_out), ret); + a.emit_mov(Size::S32, loc, Location::GPR(tmp_in)); + a.emit_vcvtsi2ss_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_mov(Size::S32, Location::XMM(tmp_out), ret); - self.machine.release_temp_gpr(tmp_in); - self.machine.release_temp_xmm(tmp_out); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } } Operator::F32ConvertSI64 => { let loc = @@ -3485,15 +4944,39 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_xmm().unwrap(); - let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + if a.arch_has_fconverti() { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::GPR(tmp_in), + ); + a.arch_emit_f32_convert_si64(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::XMM(tmp_out), + ret, + ); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S64, loc, Location::GPR(tmp_in)); - a.emit_vcvtsi2ss_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_mov(Size::S32, Location::XMM(tmp_out), ret); + a.emit_mov(Size::S64, loc, Location::GPR(tmp_in)); + a.emit_vcvtsi2ss_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_mov(Size::S32, Location::XMM(tmp_out), ret); - self.machine.release_temp_gpr(tmp_in); - self.machine.release_temp_xmm(tmp_out); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } } Operator::F32ConvertUI64 => { let loc = @@ -3504,31 +4987,55 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_xmm().unwrap(); - let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - let tmp = self.machine.acquire_temp_gpr().unwrap(); + if a.arch_has_fconverti() { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::GPR(tmp_in), + ); + a.arch_emit_f32_convert_ui64(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + Location::XMM(tmp_out), + ret, + ); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + let tmp = self.machine.acquire_temp_gpr().unwrap(); - let do_convert = a.get_label(); - let end_convert = a.get_label(); + let do_convert = a.get_label(); + let end_convert = a.get_label(); - a.emit_mov(Size::S64, loc, Location::GPR(tmp_in)); - a.emit_test_gpr_64(tmp_in); - a.emit_jmp(Condition::Signed, do_convert); - a.emit_vcvtsi2ss_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_jmp(Condition::None, end_convert); - a.emit_label(do_convert); - a.emit_mov(Size::S64, Location::GPR(tmp_in), Location::GPR(tmp)); - a.emit_and(Size::S64, Location::Imm32(1), Location::GPR(tmp)); - a.emit_shr(Size::S64, Location::Imm8(1), Location::GPR(tmp_in)); - a.emit_or(Size::S64, Location::GPR(tmp), Location::GPR(tmp_in)); - a.emit_vcvtsi2ss_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_vaddss(tmp_out, XMMOrMemory::XMM(tmp_out), tmp_out); - a.emit_label(end_convert); - a.emit_mov(Size::S32, Location::XMM(tmp_out), ret); + a.emit_mov(Size::S64, loc, Location::GPR(tmp_in)); + a.emit_test_gpr_64(tmp_in); + a.emit_jmp(Condition::Signed, do_convert); + a.emit_vcvtsi2ss_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_jmp(Condition::None, end_convert); + a.emit_label(do_convert); + a.emit_mov(Size::S64, Location::GPR(tmp_in), Location::GPR(tmp)); + a.emit_and(Size::S64, Location::Imm32(1), Location::GPR(tmp)); + a.emit_shr(Size::S64, Location::Imm8(1), Location::GPR(tmp_in)); + a.emit_or(Size::S64, Location::GPR(tmp), Location::GPR(tmp_in)); + a.emit_vcvtsi2ss_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_vaddss(tmp_out, XMMOrMemory::XMM(tmp_out), tmp_out); + a.emit_label(end_convert); + a.emit_mov(Size::S32, Location::XMM(tmp_out), ret); - self.machine.release_temp_gpr(tmp); - self.machine.release_temp_gpr(tmp_in); - self.machine.release_temp_xmm(tmp_out); + self.machine.release_temp_gpr(tmp); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } } Operator::F64ConvertSI32 => { @@ -3540,15 +5047,40 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_xmm().unwrap(); - let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S32, loc, Location::GPR(tmp_in)); - a.emit_vcvtsi2sd_32(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_mov(Size::S64, Location::XMM(tmp_out), ret); + if a.arch_has_fconverti() { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::GPR(tmp_in), + ); + a.arch_emit_f64_convert_si32(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::XMM(tmp_out), + ret, + ); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - self.machine.release_temp_gpr(tmp_in); - self.machine.release_temp_xmm(tmp_out); + a.emit_mov(Size::S32, loc, Location::GPR(tmp_in)); + a.emit_vcvtsi2sd_32(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_mov(Size::S64, Location::XMM(tmp_out), ret); + + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } } Operator::F64ConvertUI32 => { let loc = @@ -3559,15 +5091,40 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_xmm().unwrap(); - let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S32, loc, Location::GPR(tmp_in)); - a.emit_vcvtsi2sd_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_mov(Size::S64, Location::XMM(tmp_out), ret); + if a.arch_has_fconverti() { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S32, + loc, + Location::GPR(tmp_in), + ); + a.arch_emit_f64_convert_ui32(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::XMM(tmp_out), + ret, + ); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - self.machine.release_temp_gpr(tmp_in); - self.machine.release_temp_xmm(tmp_out); + a.emit_mov(Size::S32, loc, Location::GPR(tmp_in)); + a.emit_vcvtsi2sd_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_mov(Size::S64, Location::XMM(tmp_out), ret); + + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } } Operator::F64ConvertSI64 => { let loc = @@ -3578,15 +5135,40 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_xmm().unwrap(); - let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S64, loc, Location::GPR(tmp_in)); - a.emit_vcvtsi2sd_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_mov(Size::S64, Location::XMM(tmp_out), ret); + if a.arch_has_fconverti() { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::GPR(tmp_in), + ); + a.arch_emit_f64_convert_si64(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::XMM(tmp_out), + ret, + ); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - self.machine.release_temp_gpr(tmp_in); - self.machine.release_temp_xmm(tmp_out); + a.emit_mov(Size::S64, loc, Location::GPR(tmp_in)); + a.emit_vcvtsi2sd_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_mov(Size::S64, Location::XMM(tmp_out), ret); + + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } } Operator::F64ConvertUI64 => { let loc = @@ -3597,31 +5179,56 @@ impl FunctionCodeGenerator for X64FunctionCode { false, )[0]; self.value_stack.push(ret); - let tmp_out = self.machine.acquire_temp_xmm().unwrap(); - let tmp_in = self.machine.acquire_temp_gpr().unwrap(); - let tmp = self.machine.acquire_temp_gpr().unwrap(); - let do_convert = a.get_label(); - let end_convert = a.get_label(); + if a.arch_has_fconverti() { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + loc, + Location::GPR(tmp_in), + ); + a.arch_emit_f64_convert_ui64(tmp_in, tmp_out); + Self::emit_relaxed_binop( + a, + &mut self.machine, + Assembler::emit_mov, + Size::S64, + Location::XMM(tmp_out), + ret, + ); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } else { + let tmp_out = self.machine.acquire_temp_xmm().unwrap(); + let tmp_in = self.machine.acquire_temp_gpr().unwrap(); + let tmp = self.machine.acquire_temp_gpr().unwrap(); - a.emit_mov(Size::S64, loc, Location::GPR(tmp_in)); - a.emit_test_gpr_64(tmp_in); - a.emit_jmp(Condition::Signed, do_convert); - a.emit_vcvtsi2sd_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_jmp(Condition::None, end_convert); - a.emit_label(do_convert); - a.emit_mov(Size::S64, Location::GPR(tmp_in), Location::GPR(tmp)); - a.emit_and(Size::S64, Location::Imm32(1), Location::GPR(tmp)); - a.emit_shr(Size::S64, Location::Imm8(1), Location::GPR(tmp_in)); - a.emit_or(Size::S64, Location::GPR(tmp), Location::GPR(tmp_in)); - a.emit_vcvtsi2sd_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); - a.emit_vaddsd(tmp_out, XMMOrMemory::XMM(tmp_out), tmp_out); - a.emit_label(end_convert); - a.emit_mov(Size::S64, Location::XMM(tmp_out), ret); + let do_convert = a.get_label(); + let end_convert = a.get_label(); - self.machine.release_temp_gpr(tmp); - self.machine.release_temp_gpr(tmp_in); - self.machine.release_temp_xmm(tmp_out); + a.emit_mov(Size::S64, loc, Location::GPR(tmp_in)); + a.emit_test_gpr_64(tmp_in); + a.emit_jmp(Condition::Signed, do_convert); + a.emit_vcvtsi2sd_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_jmp(Condition::None, end_convert); + a.emit_label(do_convert); + a.emit_mov(Size::S64, Location::GPR(tmp_in), Location::GPR(tmp)); + a.emit_and(Size::S64, Location::Imm32(1), Location::GPR(tmp)); + a.emit_shr(Size::S64, Location::Imm8(1), Location::GPR(tmp_in)); + a.emit_or(Size::S64, Location::GPR(tmp), Location::GPR(tmp_in)); + a.emit_vcvtsi2sd_64(tmp_out, GPROrMemory::GPR(tmp_in), tmp_out); + a.emit_vaddsd(tmp_out, XMMOrMemory::XMM(tmp_out), tmp_out); + a.emit_label(end_convert); + a.emit_mov(Size::S64, Location::XMM(tmp_out), ret); + + self.machine.release_temp_gpr(tmp); + self.machine.release_temp_gpr(tmp_in); + self.machine.release_temp_xmm(tmp_out); + } } Operator::Call { function_index } => { @@ -3768,10 +5375,17 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, |a| { - a.emit_call_location(Location::Memory( - GPR::RAX, - (vm::Anyfunc::offset_func() as usize) as i32, - )); + if a.arch_requires_indirect_call_trampoline() { + a.arch_emit_indirect_call_with_trampoline(Location::Memory( + GPR::RAX, + (vm::Anyfunc::offset_func() as usize) as i32, + )); + } else { + a.emit_call_location(Location::Memory( + GPR::RAX, + (vm::Anyfunc::offset_func() as usize) as i32, + )); + } }, params.iter().map(|x| *x), Some((&mut self.fsm, &mut self.control_stack)), @@ -3806,7 +5420,11 @@ impl FunctionCodeGenerator for X64FunctionCode { returns: match ty { WpTypeOrFuncType::Type(WpType::EmptyBlockType) => smallvec![], WpTypeOrFuncType::Type(inner_ty) => smallvec![inner_ty], - _ => panic!("multi-value returns not yet implemented"), + _ => { + return Err(CodegenError { + message: format!("multi-value returns not yet implemented"), + }) + } }, value_stack_depth: self.value_stack.len(), state: self.machine.state.clone(), @@ -3913,7 +5531,11 @@ impl FunctionCodeGenerator for X64FunctionCode { returns: match ty { WpTypeOrFuncType::Type(WpType::EmptyBlockType) => smallvec![], WpTypeOrFuncType::Type(inner_ty) => smallvec![inner_ty], - _ => panic!("multi-value returns not yet implemented"), + _ => { + return Err(CodegenError { + message: format!("multi-value returns not yet implemented"), + }) + } }, value_stack_depth: self.value_stack.len(), state: self.machine.state.clone(), @@ -3938,7 +5560,11 @@ impl FunctionCodeGenerator for X64FunctionCode { returns: match ty { WpTypeOrFuncType::Type(WpType::EmptyBlockType) => smallvec![], WpTypeOrFuncType::Type(inner_ty) => smallvec![inner_ty], - _ => panic!("multi-value returns not yet implemented"), + _ => { + return Err(CodegenError { + message: format!("multi-value returns not yet implemented"), + }) + } }, value_stack_depth: self.value_stack.len(), state: self.machine.state.clone(), @@ -3958,6 +5584,7 @@ impl FunctionCodeGenerator for X64FunctionCode { self.fsm.loop_offsets.insert( a.get_offset().0, OffsetInfo { + end_offset: a.get_offset().0 + 1, activate_offset, diff_id: state_diff_id, }, @@ -3992,9 +5619,15 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, |a| { - a.emit_call_location(Location::GPR(GPR::RAX)); + let label = a.get_label(); + let after = a.get_label(); + a.emit_jmp(Condition::None, after); + a.emit_label(label); + a.emit_host_redirection(GPR::RAX); + a.emit_label(after); + a.emit_call_label(label); }, - ::std::iter::once(Location::Imm32(memory_index.index() as u32)), + iter::once(Location::Imm32(memory_index.index() as u32)), None, ); let ret = self.machine.acquire_locations( @@ -4031,10 +5664,16 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, |a| { - a.emit_call_location(Location::GPR(GPR::RAX)); + let label = a.get_label(); + let after = a.get_label(); + a.emit_jmp(Condition::None, after); + a.emit_label(label); + a.emit_host_redirection(GPR::RAX); + a.emit_label(after); + a.emit_call_label(label); }, - ::std::iter::once(Location::Imm32(memory_index.index() as u32)) - .chain(::std::iter::once(param_pages)), + iter::once(Location::Imm32(memory_index.index() as u32)) + .chain(iter::once(param_pages)), None, ); @@ -4064,7 +5703,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 4, |a, m, addr| { Self::emit_relaxed_binop( @@ -4094,7 +5734,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 4, |a, m, addr| { Self::emit_relaxed_binop( @@ -4124,7 +5765,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 1, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4155,7 +5797,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 1, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4186,7 +5829,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 2, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4217,7 +5861,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 2, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4244,7 +5889,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 4, |a, m, addr| { Self::emit_relaxed_binop( @@ -4270,7 +5916,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 4, |a, m, addr| { Self::emit_relaxed_binop( @@ -4296,7 +5943,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 1, |a, m, addr| { Self::emit_relaxed_binop( @@ -4322,7 +5970,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 2, |a, m, addr| { Self::emit_relaxed_binop( @@ -4352,7 +6001,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 8, |a, m, addr| { Self::emit_relaxed_binop( @@ -4382,7 +6032,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 8, |a, m, addr| { Self::emit_relaxed_binop( @@ -4412,7 +6063,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 1, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4443,7 +6095,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 1, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4474,7 +6127,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 2, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4505,7 +6159,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 2, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4536,7 +6191,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 4, |a, m, addr| { match ret { @@ -4577,7 +6233,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target, - memarg.offset as usize, + memarg, + false, 4, |a, m, addr| { Self::emit_relaxed_zx_sx( @@ -4604,7 +6261,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 8, |a, m, addr| { Self::emit_relaxed_binop( @@ -4630,7 +6288,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 8, |a, m, addr| { Self::emit_relaxed_binop( @@ -4656,7 +6315,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 1, |a, m, addr| { Self::emit_relaxed_binop( @@ -4682,7 +6342,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 2, |a, m, addr| { Self::emit_relaxed_binop( @@ -4708,7 +6369,8 @@ impl FunctionCodeGenerator for X64FunctionCode { a, &mut self.machine, target_addr, - memarg.offset as usize, + memarg, + false, 4, |a, m, addr| { Self::emit_relaxed_binop( @@ -4805,7 +6467,9 @@ impl FunctionCodeGenerator for X64FunctionCode { a.emit_lea_label(table_label, Location::GPR(GPR::RCX)); a.emit_mov(Size::S32, cond, Location::GPR(GPR::RDX)); - a.emit_imul_imm32_gpr64(5, GPR::RDX); + + let instr_size = a.get_jmp_instr_size(); + a.emit_imul_imm32_gpr64(instr_size as _, GPR::RDX); a.emit_add(Size::S64, Location::GPR(GPR::RCX), Location::GPR(GPR::RDX)); a.emit_jmp_location(Location::GPR(GPR::RDX)); @@ -4897,8 +6561,2029 @@ impl FunctionCodeGenerator for X64FunctionCode { } } } + Operator::Fence { flags: _ } => { + // Fence is a nop. + // + // Fence was added to preserve information about fences from + // source languages. If in the future Wasm extends the memory + // model, and if we hadn't recorded what fences used to be there, + // it would lead to data races that weren't present in the + // original source language. + } + Operator::I32AtomicLoad { ref memarg } => { + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 4, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_mov, + Size::S32, + Location::Memory(addr, 0), + ret, + ); + }, + ); + } + Operator::I32AtomicLoad8U { ref memarg } => { + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, m, addr| { + Self::emit_relaxed_zx_sx( + a, + m, + Assembler::emit_movzx, + Size::S8, + Location::Memory(addr, 0), + Size::S32, + ret, + ); + }, + ); + } + Operator::I32AtomicLoad16U { ref memarg } => { + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, m, addr| { + Self::emit_relaxed_zx_sx( + a, + m, + Assembler::emit_movzx, + Size::S16, + Location::Memory(addr, 0), + Size::S32, + ret, + ); + }, + ); + } + Operator::I32AtomicStore { ref memarg } => { + let target_value = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target_addr = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target_addr, + memarg, + true, + 4, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_xchg, + Size::S32, + target_value, + Location::Memory(addr, 0), + ); + }, + ); + } + Operator::I32AtomicStore8 { ref memarg } => { + let target_value = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target_addr = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target_addr, + memarg, + true, + 1, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_xchg, + Size::S8, + target_value, + Location::Memory(addr, 0), + ); + }, + ); + } + Operator::I32AtomicStore16 { ref memarg } => { + let target_value = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target_addr = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target_addr, + memarg, + true, + 2, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_xchg, + Size::S16, + target_value, + Location::Memory(addr, 0), + ); + }, + ); + } + Operator::I64AtomicLoad { ref memarg } => { + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 8, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_mov, + Size::S64, + Location::Memory(addr, 0), + ret, + ); + }, + ); + } + Operator::I64AtomicLoad8U { ref memarg } => { + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, m, addr| { + Self::emit_relaxed_zx_sx( + a, + m, + Assembler::emit_movzx, + Size::S8, + Location::Memory(addr, 0), + Size::S64, + ret, + ); + }, + ); + } + Operator::I64AtomicLoad16U { ref memarg } => { + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, m, addr| { + Self::emit_relaxed_zx_sx( + a, + m, + Assembler::emit_movzx, + Size::S16, + Location::Memory(addr, 0), + Size::S64, + ret, + ); + }, + ); + } + Operator::I64AtomicLoad32U { ref memarg } => { + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 4, + |a, m, addr| { + match ret { + Location::GPR(_) => {} + Location::Memory(base, offset) => { + a.emit_mov( + Size::S32, + Location::Imm32(0), + Location::Memory(base, offset + 4), + ); // clear upper bits + } + _ => unreachable!(), + } + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_mov, + Size::S32, + Location::Memory(addr, 0), + ret, + ); + }, + ); + } + Operator::I64AtomicStore { ref memarg } => { + let target_value = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target_addr = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target_addr, + memarg, + true, + 8, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_xchg, + Size::S64, + target_value, + Location::Memory(addr, 0), + ); + }, + ); + } + Operator::I64AtomicStore8 { ref memarg } => { + let target_value = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target_addr = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target_addr, + memarg, + true, + 1, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_xchg, + Size::S8, + target_value, + Location::Memory(addr, 0), + ); + }, + ); + } + Operator::I64AtomicStore16 { ref memarg } => { + let target_value = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target_addr = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target_addr, + memarg, + true, + 2, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_xchg, + Size::S16, + target_value, + Location::Memory(addr, 0), + ); + }, + ); + } + Operator::I64AtomicStore32 { ref memarg } => { + let target_value = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target_addr = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target_addr, + memarg, + true, + 4, + |a, m, addr| { + Self::emit_relaxed_binop( + a, + m, + Assembler::emit_xchg, + Size::S32, + target_value, + Location::Memory(addr, 0), + ); + }, + ); + } + Operator::I32AtomicRmwAdd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 4, + |a, _m, addr| { + a.emit_lock_xadd(Size::S32, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmwAdd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S64, loc, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 8, + |a, _m, addr| { + a.emit_lock_xadd(Size::S64, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmw8UAdd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S8, loc, Size::S32, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_xadd(Size::S8, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmw16UAdd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S16, loc, Size::S32, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, _m, addr| { + a.emit_lock_xadd(Size::S16, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw8UAdd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S8, loc, Size::S64, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_xadd(Size::S8, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw16UAdd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S16, loc, Size::S64, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, _m, addr| { + a.emit_lock_xadd(Size::S16, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw32UAdd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 4, + |a, _m, addr| { + a.emit_lock_xadd(Size::S32, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmwSub { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(value)); + a.emit_neg(Size::S32, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 4, + |a, _m, addr| { + a.emit_lock_xadd(Size::S32, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmwSub { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S64, loc, Location::GPR(value)); + a.emit_neg(Size::S64, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 8, + |a, _m, addr| { + a.emit_lock_xadd(Size::S64, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmw8USub { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S8, loc, Size::S32, Location::GPR(value)); + a.emit_neg(Size::S8, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_xadd(Size::S8, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmw16USub { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S16, loc, Size::S32, Location::GPR(value)); + a.emit_neg(Size::S16, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, _m, addr| { + a.emit_lock_xadd(Size::S16, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw8USub { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S8, loc, Size::S64, Location::GPR(value)); + a.emit_neg(Size::S8, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_xadd(Size::S8, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw16USub { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S16, loc, Size::S64, Location::GPR(value)); + a.emit_neg(Size::S16, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, _m, addr| { + a.emit_lock_xadd(Size::S16, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw32USub { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(value)); + a.emit_neg(Size::S32, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, _m, addr| { + a.emit_lock_xadd(Size::S32, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmwAnd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 4, + Size::S32, + Size::S32, + |a, _m, src, dst| { + a.emit_and(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmwAnd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 8, + Size::S64, + Size::S64, + |a, _m, src, dst| { + a.emit_and(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmw8UAnd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S8, + Size::S32, + |a, _m, src, dst| { + a.emit_and(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmw16UAnd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S16, + Size::S32, + |a, _m, src, dst| { + a.emit_and(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw8UAnd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S8, + Size::S64, + |a, _m, src, dst| { + a.emit_and(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw16UAnd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S16, + Size::S64, + |a, _m, src, dst| { + a.emit_and(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw32UAnd { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S32, + Size::S64, + |a, _m, src, dst| { + a.emit_and(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmwOr { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 4, + Size::S32, + Size::S32, + |a, _m, src, dst| { + a.emit_or(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmwOr { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 8, + Size::S64, + Size::S64, + |a, _m, src, dst| { + a.emit_or(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmw8UOr { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S8, + Size::S32, + |a, _m, src, dst| { + a.emit_or(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmw16UOr { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S16, + Size::S32, + |a, _m, src, dst| { + a.emit_or(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw8UOr { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S8, + Size::S64, + |a, _m, src, dst| { + a.emit_or(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw16UOr { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S16, + Size::S64, + |a, _m, src, dst| { + a.emit_or(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw32UOr { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S32, + Size::S64, + |a, _m, src, dst| { + a.emit_or(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmwXor { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 4, + Size::S32, + Size::S32, + |a, _m, src, dst| { + a.emit_xor(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmwXor { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 8, + Size::S64, + Size::S64, + |a, _m, src, dst| { + a.emit_xor(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmw8UXor { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S8, + Size::S32, + |a, _m, src, dst| { + a.emit_xor(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmw16UXor { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S16, + Size::S32, + |a, _m, src, dst| { + a.emit_xor(Size::S32, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw8UXor { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S8, + Size::S64, + |a, _m, src, dst| { + a.emit_xor(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw16UXor { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S16, + Size::S64, + |a, _m, src, dst| { + a.emit_xor(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I64AtomicRmw32UXor { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + Self::emit_compare_and_swap( + module_info, + &self.config, + a, + &mut self.machine, + loc, + target, + ret, + memarg, + 1, + Size::S32, + Size::S64, + |a, _m, src, dst| { + a.emit_xor(Size::S64, Location::GPR(src), Location::GPR(dst)); + }, + ); + } + Operator::I32AtomicRmwXchg { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 4, + |a, _m, addr| { + a.emit_xchg(Size::S32, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmwXchg { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S64, loc, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 8, + |a, _m, addr| { + a.emit_xchg(Size::S64, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmw8UXchg { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S8, loc, Size::S32, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_xchg(Size::S8, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmw16UXchg { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S16, loc, Size::S32, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, _m, addr| { + a.emit_xchg(Size::S16, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S32, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw8UXchg { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S8, loc, Size::S64, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_xchg(Size::S8, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw16UXchg { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_movzx(Size::S16, loc, Size::S64, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 2, + |a, _m, addr| { + a.emit_xchg(Size::S16, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I64AtomicRmw32UXchg { ref memarg } => { + let loc = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let value = self.machine.acquire_temp_gpr().unwrap(); + a.emit_mov(Size::S32, loc, Location::GPR(value)); + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 4, + |a, _m, addr| { + a.emit_xchg(Size::S32, Location::GPR(value), Location::Memory(addr, 0)) + }, + ); + a.emit_mov(Size::S64, Location::GPR(value), ret); + self.machine.release_temp_gpr(value); + } + Operator::I32AtomicRmwCmpxchg { ref memarg } => { + let new = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let cmp = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let compare = self.machine.reserve_unused_temp_gpr(GPR::RAX); + let value = if cmp == Location::GPR(GPR::R14) { + if new == Location::GPR(GPR::R13) { + GPR::R12 + } else { + GPR::R13 + } + } else { + GPR::R14 + }; + a.emit_push(Size::S64, Location::GPR(value)); + a.emit_mov(Size::S32, cmp, Location::GPR(compare)); + a.emit_mov(Size::S32, new, Location::GPR(value)); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 4, + |a, _m, addr| { + a.emit_lock_cmpxchg( + Size::S32, + Location::GPR(value), + Location::Memory(addr, 0), + ); + a.emit_mov(Size::S32, Location::GPR(compare), ret); + }, + ); + a.emit_pop(Size::S64, Location::GPR(value)); + self.machine.release_temp_gpr(compare); + } + Operator::I64AtomicRmwCmpxchg { ref memarg } => { + let new = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let cmp = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let compare = self.machine.reserve_unused_temp_gpr(GPR::RAX); + let value = if cmp == Location::GPR(GPR::R14) { + if new == Location::GPR(GPR::R13) { + GPR::R12 + } else { + GPR::R13 + } + } else { + GPR::R14 + }; + a.emit_push(Size::S64, Location::GPR(value)); + a.emit_mov(Size::S64, cmp, Location::GPR(compare)); + a.emit_mov(Size::S64, new, Location::GPR(value)); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 8, + |a, _m, addr| { + a.emit_lock_cmpxchg( + Size::S64, + Location::GPR(value), + Location::Memory(addr, 0), + ); + a.emit_mov(Size::S64, Location::GPR(compare), ret); + }, + ); + a.emit_pop(Size::S64, Location::GPR(value)); + self.machine.release_temp_gpr(compare); + } + Operator::I32AtomicRmw8UCmpxchg { ref memarg } => { + let new = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let cmp = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let compare = self.machine.reserve_unused_temp_gpr(GPR::RAX); + let value = if cmp == Location::GPR(GPR::R14) { + if new == Location::GPR(GPR::R13) { + GPR::R12 + } else { + GPR::R13 + } + } else { + GPR::R14 + }; + a.emit_push(Size::S64, Location::GPR(value)); + a.emit_mov(Size::S32, cmp, Location::GPR(compare)); + a.emit_mov(Size::S32, new, Location::GPR(value)); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_cmpxchg( + Size::S8, + Location::GPR(value), + Location::Memory(addr, 0), + ); + a.emit_movzx(Size::S8, Location::GPR(compare), Size::S32, ret); + }, + ); + a.emit_pop(Size::S64, Location::GPR(value)); + self.machine.release_temp_gpr(compare); + } + Operator::I32AtomicRmw16UCmpxchg { ref memarg } => { + let new = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let cmp = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I32, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let compare = self.machine.reserve_unused_temp_gpr(GPR::RAX); + let value = if cmp == Location::GPR(GPR::R14) { + if new == Location::GPR(GPR::R13) { + GPR::R12 + } else { + GPR::R13 + } + } else { + GPR::R14 + }; + a.emit_push(Size::S64, Location::GPR(value)); + a.emit_mov(Size::S32, cmp, Location::GPR(compare)); + a.emit_mov(Size::S32, new, Location::GPR(value)); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_cmpxchg( + Size::S16, + Location::GPR(value), + Location::Memory(addr, 0), + ); + a.emit_movzx(Size::S16, Location::GPR(compare), Size::S32, ret); + }, + ); + a.emit_pop(Size::S64, Location::GPR(value)); + self.machine.release_temp_gpr(compare); + } + Operator::I64AtomicRmw8UCmpxchg { ref memarg } => { + let new = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let cmp = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let compare = self.machine.reserve_unused_temp_gpr(GPR::RAX); + let value = if cmp == Location::GPR(GPR::R14) { + if new == Location::GPR(GPR::R13) { + GPR::R12 + } else { + GPR::R13 + } + } else { + GPR::R14 + }; + a.emit_push(Size::S64, Location::GPR(value)); + a.emit_mov(Size::S64, cmp, Location::GPR(compare)); + a.emit_mov(Size::S64, new, Location::GPR(value)); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_cmpxchg( + Size::S8, + Location::GPR(value), + Location::Memory(addr, 0), + ); + a.emit_movzx(Size::S8, Location::GPR(compare), Size::S64, ret); + }, + ); + a.emit_pop(Size::S64, Location::GPR(value)); + self.machine.release_temp_gpr(compare); + } + Operator::I64AtomicRmw16UCmpxchg { ref memarg } => { + let new = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let cmp = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let compare = self.machine.reserve_unused_temp_gpr(GPR::RAX); + let value = if cmp == Location::GPR(GPR::R14) { + if new == Location::GPR(GPR::R13) { + GPR::R12 + } else { + GPR::R13 + } + } else { + GPR::R14 + }; + a.emit_push(Size::S64, Location::GPR(value)); + a.emit_mov(Size::S64, cmp, Location::GPR(compare)); + a.emit_mov(Size::S64, new, Location::GPR(value)); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_cmpxchg( + Size::S16, + Location::GPR(value), + Location::Memory(addr, 0), + ); + a.emit_movzx(Size::S16, Location::GPR(compare), Size::S64, ret); + }, + ); + a.emit_pop(Size::S64, Location::GPR(value)); + self.machine.release_temp_gpr(compare); + } + Operator::I64AtomicRmw32UCmpxchg { ref memarg } => { + let new = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let cmp = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let target = + get_location_released(a, &mut self.machine, self.value_stack.pop().unwrap()); + let ret = self.machine.acquire_locations( + a, + &[(WpType::I64, MachineValue::WasmStack(self.value_stack.len()))], + false, + )[0]; + self.value_stack.push(ret); + + let compare = self.machine.reserve_unused_temp_gpr(GPR::RAX); + let value = if cmp == Location::GPR(GPR::R14) { + if new == Location::GPR(GPR::R13) { + GPR::R12 + } else { + GPR::R13 + } + } else { + GPR::R14 + }; + a.emit_push(Size::S64, Location::GPR(value)); + a.emit_mov(Size::S64, cmp, Location::GPR(compare)); + a.emit_mov(Size::S64, new, Location::GPR(value)); + + Self::emit_memory_op( + module_info, + &self.config, + a, + &mut self.machine, + target, + memarg, + true, + 1, + |a, _m, addr| { + a.emit_lock_cmpxchg( + Size::S32, + Location::GPR(value), + Location::Memory(addr, 0), + ); + a.emit_mov(Size::S32, Location::GPR(compare), ret); + }, + ); + a.emit_pop(Size::S64, Location::GPR(value)); + self.machine.release_temp_gpr(compare); + } _ => { - panic!("not yet implemented: {:?}", op); + return Err(CodegenError { + message: format!("not yet implemented: {:?}", op), + }); } } diff --git a/lib/singlepass-backend/src/emitter_x64.rs b/lib/singlepass-backend/src/emitter_x64.rs index 3476bd289..0bf6fc2b0 100644 --- a/lib/singlepass-backend/src/emitter_x64.rs +++ b/lib/singlepass-backend/src/emitter_x64.rs @@ -1,5 +1,13 @@ use dynasmrt::{x64::Assembler, AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi}; -pub use wasmer_runtime_core::state::x64::{GPR, XMM}; +use wasmer_runtime_core::backend::InlineBreakpointType; +pub use wasmer_runtime_core::state::x64_decl::{GPR, XMM}; + +fn _dummy(_a: &Assembler) { + dynasm!( + _a + ; .arch x64 + ); +} #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] pub enum Location { @@ -28,7 +36,7 @@ pub enum Condition { Signed, } -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd)] pub enum Size { S8, S16, @@ -36,7 +44,7 @@ pub enum Size { S64, } -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[allow(dead_code)] pub enum XMMOrMemory { XMM(XMM), @@ -56,11 +64,14 @@ pub trait Emitter { fn get_label(&mut self) -> Self::Label; fn get_offset(&self) -> Self::Offset; + fn get_jmp_instr_size(&self) -> u8; fn emit_u64(&mut self, x: u64); fn emit_label(&mut self, label: Self::Label); + fn emit_nop(&mut self); + fn emit_mov(&mut self, sz: Size, src: Location, dst: Location); fn emit_lea(&mut self, sz: Size, src: Location, dst: Location); fn emit_lea_label(&mut self, label: Self::Label, dst: Location); @@ -76,6 +87,7 @@ pub trait Emitter { fn emit_cmp(&mut self, sz: Size, left: Location, right: Location); fn emit_add(&mut self, sz: Size, src: Location, dst: Location); fn emit_sub(&mut self, sz: Size, src: Location, dst: Location); + fn emit_neg(&mut self, sz: Size, value: Location); fn emit_imul(&mut self, sz: Size, src: Location, dst: Location); fn emit_imul_imm32_gpr64(&mut self, src: u32, dst: GPR); fn emit_div(&mut self, sz: Size, divisor: Location); @@ -87,11 +99,14 @@ pub trait Emitter { fn emit_ror(&mut self, sz: Size, src: Location, dst: Location); fn emit_and(&mut self, sz: Size, src: Location, dst: Location); fn emit_or(&mut self, sz: Size, src: Location, dst: Location); - fn emit_lzcnt(&mut self, sz: Size, src: Location, dst: Location); - fn emit_tzcnt(&mut self, sz: Size, src: Location, dst: Location); + fn emit_bsr(&mut self, sz: Size, src: Location, dst: Location); + fn emit_bsf(&mut self, sz: Size, src: Location, dst: Location); fn emit_popcnt(&mut self, sz: Size, src: Location, dst: Location); fn emit_movzx(&mut self, sz_src: Size, src: Location, sz_dst: Size, dst: Location); fn emit_movsx(&mut self, sz_src: Size, src: Location, sz_dst: Size, dst: Location); + fn emit_xchg(&mut self, sz: Size, src: Location, dst: Location); + fn emit_lock_xadd(&mut self, sz: Size, src: Location, dst: Location); + fn emit_lock_cmpxchg(&mut self, sz: Size, src: Location, dst: Location); fn emit_btc_gpr_imm8_32(&mut self, src: u8, dst: GPR); fn emit_btc_gpr_imm8_64(&mut self, src: u8, dst: GPR); @@ -99,6 +114,11 @@ pub trait Emitter { fn emit_cmovae_gpr_32(&mut self, src: GPR, dst: GPR); fn emit_cmovae_gpr_64(&mut self, src: GPR, dst: GPR); + fn emit_vmovaps(&mut self, src: XMMOrMemory, dst: XMMOrMemory); + fn emit_vmovapd(&mut self, src: XMMOrMemory, dst: XMMOrMemory); + fn emit_vxorps(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); + fn emit_vxorpd(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); + fn emit_vaddss(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); fn emit_vaddsd(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); fn emit_vsubss(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); @@ -130,6 +150,12 @@ pub trait Emitter { fn emit_vcmpgess(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); fn emit_vcmpgesd(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); + fn emit_vcmpunordss(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); + fn emit_vcmpunordsd(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); + + fn emit_vcmpordss(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); + fn emit_vcmpordsd(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); + fn emit_vsqrtss(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); fn emit_vsqrtsd(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM); @@ -158,6 +184,9 @@ pub trait Emitter { fn emit_vcvtsi2sd_32(&mut self, src1: XMM, src2: GPROrMemory, dst: XMM); fn emit_vcvtsi2sd_64(&mut self, src1: XMM, src2: GPROrMemory, dst: XMM); + fn emit_vblendvps(&mut self, src1: XMM, src2: XMMOrMemory, mask: XMM, dst: XMM); + fn emit_vblendvpd(&mut self, src1: XMM, src2: XMMOrMemory, mask: XMM, dst: XMM); + fn emit_test_gpr_64(&mut self, reg: GPR); fn emit_ud2(&mut self); @@ -166,6 +195,100 @@ pub trait Emitter { fn emit_call_location(&mut self, loc: Location); fn emit_bkpt(&mut self); + + fn emit_host_redirection(&mut self, target: GPR); + fn emit_inline_breakpoint(&mut self, ty: InlineBreakpointType); + + fn arch_has_itruncf(&self) -> bool { + false + } + fn arch_emit_i32_trunc_sf32(&mut self, _src: XMM, _dst: GPR) { + unimplemented!() + } + fn arch_emit_i32_trunc_sf64(&mut self, _src: XMM, _dst: GPR) { + unimplemented!() + } + fn arch_emit_i32_trunc_uf32(&mut self, _src: XMM, _dst: GPR) { + unimplemented!() + } + fn arch_emit_i32_trunc_uf64(&mut self, _src: XMM, _dst: GPR) { + unimplemented!() + } + fn arch_emit_i64_trunc_sf32(&mut self, _src: XMM, _dst: GPR) { + unimplemented!() + } + fn arch_emit_i64_trunc_sf64(&mut self, _src: XMM, _dst: GPR) { + unimplemented!() + } + fn arch_emit_i64_trunc_uf32(&mut self, _src: XMM, _dst: GPR) { + unimplemented!() + } + fn arch_emit_i64_trunc_uf64(&mut self, _src: XMM, _dst: GPR) { + unimplemented!() + } + + fn arch_has_fconverti(&self) -> bool { + false + } + fn arch_emit_f32_convert_si32(&mut self, _src: GPR, _dst: XMM) { + unimplemented!() + } + fn arch_emit_f32_convert_si64(&mut self, _src: GPR, _dst: XMM) { + unimplemented!() + } + fn arch_emit_f32_convert_ui32(&mut self, _src: GPR, _dst: XMM) { + unimplemented!() + } + fn arch_emit_f32_convert_ui64(&mut self, _src: GPR, _dst: XMM) { + unimplemented!() + } + fn arch_emit_f64_convert_si32(&mut self, _src: GPR, _dst: XMM) { + unimplemented!() + } + fn arch_emit_f64_convert_si64(&mut self, _src: GPR, _dst: XMM) { + unimplemented!() + } + fn arch_emit_f64_convert_ui32(&mut self, _src: GPR, _dst: XMM) { + unimplemented!() + } + fn arch_emit_f64_convert_ui64(&mut self, _src: GPR, _dst: XMM) { + unimplemented!() + } + + fn arch_has_fneg(&self) -> bool { + false + } + fn arch_emit_f32_neg(&mut self, _src: XMM, _dst: XMM) { + unimplemented!() + } + fn arch_emit_f64_neg(&mut self, _src: XMM, _dst: XMM) { + unimplemented!() + } + + fn arch_has_xzcnt(&self) -> bool { + false + } + fn arch_emit_lzcnt(&mut self, _sz: Size, _src: Location, _dst: Location) { + unimplemented!() + } + fn arch_emit_tzcnt(&mut self, _sz: Size, _src: Location, _dst: Location) { + unimplemented!() + } + + fn arch_supports_canonicalize_nan(&self) -> bool { + true + } + + fn arch_requires_indirect_call_trampoline(&self) -> bool { + false + } + + fn arch_emit_indirect_call_with_trampoline(&mut self, _loc: Location) { + unimplemented!() + } + + // Emits entry trampoline just before the real function. + fn arch_emit_entry_trampoline(&mut self) {} } macro_rules! unop_gpr { @@ -363,6 +486,14 @@ macro_rules! avx_fn { XMM::XMM5 => dynasm!(self ; $ins Rx((dst as u8)), xmm5, Rx((x as u8))), XMM::XMM6 => dynasm!(self ; $ins Rx((dst as u8)), xmm6, Rx((x as u8))), XMM::XMM7 => dynasm!(self ; $ins Rx((dst as u8)), xmm7, Rx((x as u8))), + XMM::XMM8 => dynasm!(self ; $ins Rx((dst as u8)), xmm8, Rx((x as u8))), + XMM::XMM9 => dynasm!(self ; $ins Rx((dst as u8)), xmm9, Rx((x as u8))), + XMM::XMM10 => dynasm!(self ; $ins Rx((dst as u8)), xmm10, Rx((x as u8))), + XMM::XMM11 => dynasm!(self ; $ins Rx((dst as u8)), xmm11, Rx((x as u8))), + XMM::XMM12 => dynasm!(self ; $ins Rx((dst as u8)), xmm12, Rx((x as u8))), + XMM::XMM13 => dynasm!(self ; $ins Rx((dst as u8)), xmm13, Rx((x as u8))), + XMM::XMM14 => dynasm!(self ; $ins Rx((dst as u8)), xmm14, Rx((x as u8))), + XMM::XMM15 => dynasm!(self ; $ins Rx((dst as u8)), xmm15, Rx((x as u8))), }, XMMOrMemory::Memory(base, disp) => match src1 { XMM::XMM0 => dynasm!(self ; $ins Rx((dst as u8)), xmm0, [Rq((base as u8)) + disp]), @@ -373,6 +504,14 @@ macro_rules! avx_fn { XMM::XMM5 => dynasm!(self ; $ins Rx((dst as u8)), xmm5, [Rq((base as u8)) + disp]), XMM::XMM6 => dynasm!(self ; $ins Rx((dst as u8)), xmm6, [Rq((base as u8)) + disp]), XMM::XMM7 => dynasm!(self ; $ins Rx((dst as u8)), xmm7, [Rq((base as u8)) + disp]), + XMM::XMM8 => dynasm!(self ; $ins Rx((dst as u8)), xmm8, [Rq((base as u8)) + disp]), + XMM::XMM9 => dynasm!(self ; $ins Rx((dst as u8)), xmm9, [Rq((base as u8)) + disp]), + XMM::XMM10 => dynasm!(self ; $ins Rx((dst as u8)), xmm10, [Rq((base as u8)) + disp]), + XMM::XMM11 => dynasm!(self ; $ins Rx((dst as u8)), xmm11, [Rq((base as u8)) + disp]), + XMM::XMM12 => dynasm!(self ; $ins Rx((dst as u8)), xmm12, [Rq((base as u8)) + disp]), + XMM::XMM13 => dynasm!(self ; $ins Rx((dst as u8)), xmm13, [Rq((base as u8)) + disp]), + XMM::XMM14 => dynasm!(self ; $ins Rx((dst as u8)), xmm14, [Rq((base as u8)) + disp]), + XMM::XMM15 => dynasm!(self ; $ins Rx((dst as u8)), xmm15, [Rq((base as u8)) + disp]), }, } } @@ -392,6 +531,14 @@ macro_rules! avx_i2f_64_fn { XMM::XMM5 => dynasm!(self ; $ins Rx((dst as u8)), xmm5, Rq((x as u8))), XMM::XMM6 => dynasm!(self ; $ins Rx((dst as u8)), xmm6, Rq((x as u8))), XMM::XMM7 => dynasm!(self ; $ins Rx((dst as u8)), xmm7, Rq((x as u8))), + XMM::XMM8 => dynasm!(self ; $ins Rx((dst as u8)), xmm8, Rq((x as u8))), + XMM::XMM9 => dynasm!(self ; $ins Rx((dst as u8)), xmm9, Rq((x as u8))), + XMM::XMM10 => dynasm!(self ; $ins Rx((dst as u8)), xmm10, Rq((x as u8))), + XMM::XMM11 => dynasm!(self ; $ins Rx((dst as u8)), xmm11, Rq((x as u8))), + XMM::XMM12 => dynasm!(self ; $ins Rx((dst as u8)), xmm12, Rq((x as u8))), + XMM::XMM13 => dynasm!(self ; $ins Rx((dst as u8)), xmm13, Rq((x as u8))), + XMM::XMM14 => dynasm!(self ; $ins Rx((dst as u8)), xmm14, Rq((x as u8))), + XMM::XMM15 => dynasm!(self ; $ins Rx((dst as u8)), xmm15, Rq((x as u8))), }, GPROrMemory::Memory(base, disp) => match src1 { XMM::XMM0 => dynasm!(self ; $ins Rx((dst as u8)), xmm0, QWORD [Rq((base as u8)) + disp]), @@ -402,6 +549,14 @@ macro_rules! avx_i2f_64_fn { XMM::XMM5 => dynasm!(self ; $ins Rx((dst as u8)), xmm5, QWORD [Rq((base as u8)) + disp]), XMM::XMM6 => dynasm!(self ; $ins Rx((dst as u8)), xmm6, QWORD [Rq((base as u8)) + disp]), XMM::XMM7 => dynasm!(self ; $ins Rx((dst as u8)), xmm7, QWORD [Rq((base as u8)) + disp]), + XMM::XMM8 => dynasm!(self ; $ins Rx((dst as u8)), xmm8, QWORD [Rq((base as u8)) + disp]), + XMM::XMM9 => dynasm!(self ; $ins Rx((dst as u8)), xmm9, QWORD [Rq((base as u8)) + disp]), + XMM::XMM10 => dynasm!(self ; $ins Rx((dst as u8)), xmm10, QWORD [Rq((base as u8)) + disp]), + XMM::XMM11 => dynasm!(self ; $ins Rx((dst as u8)), xmm11, QWORD [Rq((base as u8)) + disp]), + XMM::XMM12 => dynasm!(self ; $ins Rx((dst as u8)), xmm12, QWORD [Rq((base as u8)) + disp]), + XMM::XMM13 => dynasm!(self ; $ins Rx((dst as u8)), xmm13, QWORD [Rq((base as u8)) + disp]), + XMM::XMM14 => dynasm!(self ; $ins Rx((dst as u8)), xmm14, QWORD [Rq((base as u8)) + disp]), + XMM::XMM15 => dynasm!(self ; $ins Rx((dst as u8)), xmm15, QWORD [Rq((base as u8)) + disp]), }, } } @@ -421,6 +576,14 @@ macro_rules! avx_i2f_32_fn { XMM::XMM5 => dynasm!(self ; $ins Rx((dst as u8)), xmm5, Rd((x as u8))), XMM::XMM6 => dynasm!(self ; $ins Rx((dst as u8)), xmm6, Rd((x as u8))), XMM::XMM7 => dynasm!(self ; $ins Rx((dst as u8)), xmm7, Rd((x as u8))), + XMM::XMM8 => dynasm!(self ; $ins Rx((dst as u8)), xmm8, Rd((x as u8))), + XMM::XMM9 => dynasm!(self ; $ins Rx((dst as u8)), xmm9, Rd((x as u8))), + XMM::XMM10 => dynasm!(self ; $ins Rx((dst as u8)), xmm10, Rd((x as u8))), + XMM::XMM11 => dynasm!(self ; $ins Rx((dst as u8)), xmm11, Rd((x as u8))), + XMM::XMM12 => dynasm!(self ; $ins Rx((dst as u8)), xmm12, Rd((x as u8))), + XMM::XMM13 => dynasm!(self ; $ins Rx((dst as u8)), xmm13, Rd((x as u8))), + XMM::XMM14 => dynasm!(self ; $ins Rx((dst as u8)), xmm14, Rd((x as u8))), + XMM::XMM15 => dynasm!(self ; $ins Rx((dst as u8)), xmm15, Rd((x as u8))), }, GPROrMemory::Memory(base, disp) => match src1 { XMM::XMM0 => dynasm!(self ; $ins Rx((dst as u8)), xmm0, DWORD [Rq((base as u8)) + disp]), @@ -431,6 +594,14 @@ macro_rules! avx_i2f_32_fn { XMM::XMM5 => dynasm!(self ; $ins Rx((dst as u8)), xmm5, DWORD [Rq((base as u8)) + disp]), XMM::XMM6 => dynasm!(self ; $ins Rx((dst as u8)), xmm6, DWORD [Rq((base as u8)) + disp]), XMM::XMM7 => dynasm!(self ; $ins Rx((dst as u8)), xmm7, DWORD [Rq((base as u8)) + disp]), + XMM::XMM8 => dynasm!(self ; $ins Rx((dst as u8)), xmm8, DWORD [Rq((base as u8)) + disp]), + XMM::XMM9 => dynasm!(self ; $ins Rx((dst as u8)), xmm9, DWORD [Rq((base as u8)) + disp]), + XMM::XMM10 => dynasm!(self ; $ins Rx((dst as u8)), xmm10, DWORD [Rq((base as u8)) + disp]), + XMM::XMM11 => dynasm!(self ; $ins Rx((dst as u8)), xmm11, DWORD [Rq((base as u8)) + disp]), + XMM::XMM12 => dynasm!(self ; $ins Rx((dst as u8)), xmm12, DWORD [Rq((base as u8)) + disp]), + XMM::XMM13 => dynasm!(self ; $ins Rx((dst as u8)), xmm13, DWORD [Rq((base as u8)) + disp]), + XMM::XMM14 => dynasm!(self ; $ins Rx((dst as u8)), xmm14, DWORD [Rq((base as u8)) + disp]), + XMM::XMM15 => dynasm!(self ; $ins Rx((dst as u8)), xmm15, DWORD [Rq((base as u8)) + disp]), }, } } @@ -460,6 +631,10 @@ impl Emitter for Assembler { self.offset() } + fn get_jmp_instr_size(&self) -> u8 { + 5 + } + fn emit_u64(&mut self, x: u64) { self.push_u64(x); } @@ -468,6 +643,10 @@ impl Emitter for Assembler { dynasm!(self ; => label); } + fn emit_nop(&mut self) { + dynasm!(self ; nop); + } + fn emit_mov(&mut self, sz: Size, src: Location, dst: Location) { binop_all_nofp!(mov, self, sz, src, dst, { binop_imm64_gpr!(mov, self, sz, src, dst, { @@ -543,7 +722,7 @@ impl Emitter for Assembler { dynasm!(self ; movq Rx(dst as u8), Rx(src as u8)); } - _ => panic!("MOV {:?} {:?} {:?}", sz, src, dst), + _ => panic!("singlepass can't emit MOV {:?} {:?} {:?}", sz, src, dst), } }) }); @@ -556,7 +735,7 @@ impl Emitter for Assembler { (Size::S64, Location::Memory(src, disp), Location::GPR(dst)) => { dynasm!(self ; lea Rq(dst as u8), [Rq(src as u8) + disp]); } - _ => unreachable!(), + _ => panic!("singlepass can't emit LEA {:?} {:?} {:?}", sz, src, dst), } } fn emit_lea_label(&mut self, label: Self::Label, dst: Location) { @@ -564,7 +743,7 @@ impl Emitter for Assembler { Location::GPR(x) => { dynasm!(self ; lea Rq(x as u8), [=>label]); } - _ => unreachable!(), + _ => panic!("singlepass can't emit LEA label={:?} {:?}", label, dst), } } fn emit_cdq(&mut self) { @@ -574,7 +753,9 @@ impl Emitter for Assembler { dynasm!(self ; cqo); } fn emit_xor(&mut self, sz: Size, src: Location, dst: Location) { - binop_all_nofp!(xor, self, sz, src, dst, { unreachable!() }); + binop_all_nofp!(xor, self, sz, src, dst, { + panic!("singlepass can't emit XOR {:?} {:?} {:?}", sz, src, dst) + }); } fn emit_jmp(&mut self, condition: Condition, label: Self::Label) { match condition { @@ -596,7 +777,7 @@ impl Emitter for Assembler { match loc { Location::GPR(x) => dynasm!(self ; jmp Rq(x as u8)), Location::Memory(base, disp) => dynasm!(self ; jmp QWORD [Rq(base as u8) + disp]), - _ => unreachable!(), + _ => panic!("singlepass can't emit JMP {:?}", loc), } } fn emit_conditional_trap(&mut self, condition: Condition) { @@ -628,87 +809,126 @@ impl Emitter for Assembler { Condition::Equal => dynasm!(self ; sete Rb(dst as u8)), Condition::NotEqual => dynasm!(self ; setne Rb(dst as u8)), Condition::Signed => dynasm!(self ; sets Rb(dst as u8)), - _ => unreachable!(), + _ => panic!("singlepass can't emit SET {:?} {:?}", condition, dst), } } fn emit_push(&mut self, sz: Size, src: Location) { match (sz, src) { (Size::S64, Location::Imm32(src)) => dynasm!(self ; push src as i32), (Size::S64, Location::GPR(src)) => dynasm!(self ; push Rq(src as u8)), - (Size::S64, Location::Memory(src, disp)) => { - dynasm!(self ; push QWORD [Rq(src as u8) + disp]) - } - _ => panic!("push {:?} {:?}", sz, src), + (Size::S64, Location::Memory(src, disp)) => dynasm!(self ; push QWORD [Rq(src as u8) + disp]), + _ => panic!("singlepass can't emit PUSH {:?} {:?}", sz, src), } } fn emit_pop(&mut self, sz: Size, dst: Location) { match (sz, dst) { (Size::S64, Location::GPR(dst)) => dynasm!(self ; pop Rq(dst as u8)), - (Size::S64, Location::Memory(dst, disp)) => { - dynasm!(self ; pop QWORD [Rq(dst as u8) + disp]) - } - _ => panic!("pop {:?} {:?}", sz, dst), + (Size::S64, Location::Memory(dst, disp)) => dynasm!(self ; pop QWORD [Rq(dst as u8) + disp]), + _ => panic!("singlepass can't emit POP {:?} {:?}", sz, dst), } } fn emit_cmp(&mut self, sz: Size, left: Location, right: Location) { binop_all_nofp!(cmp, self, sz, left, right, { - panic!("{:?} {:?} {:?}", sz, left, right); + panic!("singlepass can't emit CMP {:?} {:?} {:?}", sz, left, right); }); } fn emit_add(&mut self, sz: Size, src: Location, dst: Location) { - binop_all_nofp!(add, self, sz, src, dst, { unreachable!() }); + binop_all_nofp!(add, self, sz, src, dst, { + panic!("singlepass can't emit ADD {:?} {:?} {:?}", sz, src, dst) + }); } fn emit_sub(&mut self, sz: Size, src: Location, dst: Location) { - binop_all_nofp!(sub, self, sz, src, dst, { unreachable!() }); + binop_all_nofp!(sub, self, sz, src, dst, { + panic!("singlepass can't emit SUB {:?} {:?} {:?}", sz, src, dst) + }); + } + fn emit_neg(&mut self, sz: Size, value: Location) { + match (sz, value) { + (Size::S8, Location::GPR(value)) => dynasm!(self ; neg Rb(value as u8)), + (Size::S8, Location::Memory(value, disp)) => dynasm!(self ; neg [Rq(value as u8) + disp]), + (Size::S16, Location::GPR(value)) => dynasm!(self ; neg Rw(value as u8)), + (Size::S16, Location::Memory(value, disp)) => dynasm!(self ; neg [Rq(value as u8) + disp]), + (Size::S32, Location::GPR(value)) => dynasm!(self ; neg Rd(value as u8)), + (Size::S32, Location::Memory(value, disp)) => dynasm!(self ; neg [Rq(value as u8) + disp]), + (Size::S64, Location::GPR(value)) => dynasm!(self ; neg Rq(value as u8)), + (Size::S64, Location::Memory(value, disp)) => dynasm!(self ; neg [Rq(value as u8) + disp]), + _ => panic!("singlepass can't emit NEG {:?} {:?}", sz, value), + } } fn emit_imul(&mut self, sz: Size, src: Location, dst: Location) { binop_gpr_gpr!(imul, self, sz, src, dst, { - binop_mem_gpr!(imul, self, sz, src, dst, { unreachable!() }) + binop_mem_gpr!(imul, self, sz, src, dst, { + panic!("singlepass can't emit IMUL {:?} {:?} {:?}", sz, src, dst) + }) }); } fn emit_imul_imm32_gpr64(&mut self, src: u32, dst: GPR) { dynasm!(self ; imul Rq(dst as u8), Rq(dst as u8), src as i32); } fn emit_div(&mut self, sz: Size, divisor: Location) { - unop_gpr_or_mem!(div, self, sz, divisor, { unreachable!() }); - } - fn emit_idiv(&mut self, sz: Size, divisor: Location) { - unop_gpr_or_mem!(idiv, self, sz, divisor, { unreachable!() }); - } - fn emit_shl(&mut self, sz: Size, src: Location, dst: Location) { - binop_shift!(shl, self, sz, src, dst, { unreachable!() }); - } - fn emit_shr(&mut self, sz: Size, src: Location, dst: Location) { - binop_shift!(shr, self, sz, src, dst, { unreachable!() }); - } - fn emit_sar(&mut self, sz: Size, src: Location, dst: Location) { - binop_shift!(sar, self, sz, src, dst, { unreachable!() }); - } - fn emit_rol(&mut self, sz: Size, src: Location, dst: Location) { - binop_shift!(rol, self, sz, src, dst, { unreachable!() }); - } - fn emit_ror(&mut self, sz: Size, src: Location, dst: Location) { - binop_shift!(ror, self, sz, src, dst, { unreachable!() }); - } - fn emit_and(&mut self, sz: Size, src: Location, dst: Location) { - binop_all_nofp!(and, self, sz, src, dst, { unreachable!() }); - } - fn emit_or(&mut self, sz: Size, src: Location, dst: Location) { - binop_all_nofp!(or, self, sz, src, dst, { unreachable!() }); - } - fn emit_lzcnt(&mut self, sz: Size, src: Location, dst: Location) { - binop_gpr_gpr!(lzcnt, self, sz, src, dst, { - binop_mem_gpr!(lzcnt, self, sz, src, dst, { unreachable!() }) + unop_gpr_or_mem!(div, self, sz, divisor, { + panic!("singlepass can't emit DIV {:?} {:?}", sz, divisor) }); } - fn emit_tzcnt(&mut self, sz: Size, src: Location, dst: Location) { - binop_gpr_gpr!(tzcnt, self, sz, src, dst, { - binop_mem_gpr!(tzcnt, self, sz, src, dst, { unreachable!() }) + fn emit_idiv(&mut self, sz: Size, divisor: Location) { + unop_gpr_or_mem!(idiv, self, sz, divisor, { + panic!("singlepass can't emit IDIV {:?} {:?}", sz, divisor) + }); + } + fn emit_shl(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(shl, self, sz, src, dst, { + panic!("singlepass can't emit SHL {:?} {:?} {:?}", sz, src, dst) + }); + } + fn emit_shr(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(shr, self, sz, src, dst, { + panic!("singlepass can't emit SHR {:?} {:?} {:?}", sz, src, dst) + }); + } + fn emit_sar(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(sar, self, sz, src, dst, { + panic!("singlepass can't emit SAR {:?} {:?} {:?}", sz, src, dst) + }); + } + fn emit_rol(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(rol, self, sz, src, dst, { + panic!("singlepass can't emit ROL {:?} {:?} {:?}", sz, src, dst) + }); + } + fn emit_ror(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(ror, self, sz, src, dst, { + panic!("singlepass can't emit ROR {:?} {:?} {:?}", sz, src, dst) + }); + } + fn emit_and(&mut self, sz: Size, src: Location, dst: Location) { + binop_all_nofp!(and, self, sz, src, dst, { + panic!("singlepass can't emit AND {:?} {:?} {:?}", sz, src, dst) + }); + } + fn emit_or(&mut self, sz: Size, src: Location, dst: Location) { + binop_all_nofp!(or, self, sz, src, dst, { + panic!("singlepass can't emit OR {:?} {:?} {:?}", sz, src, dst) + }); + } + fn emit_bsr(&mut self, sz: Size, src: Location, dst: Location) { + binop_gpr_gpr!(bsr, self, sz, src, dst, { + binop_mem_gpr!(bsr, self, sz, src, dst, { + panic!("singlepass can't emit BSR {:?} {:?} {:?}", sz, src, dst) + }) + }); + } + fn emit_bsf(&mut self, sz: Size, src: Location, dst: Location) { + binop_gpr_gpr!(bsf, self, sz, src, dst, { + binop_mem_gpr!(bsf, self, sz, src, dst, { + panic!("singlepass can't emit BSF {:?} {:?} {:?}", sz, src, dst) + }) }); } fn emit_popcnt(&mut self, sz: Size, src: Location, dst: Location) { binop_gpr_gpr!(popcnt, self, sz, src, dst, { - binop_mem_gpr!(popcnt, self, sz, src, dst, { unreachable!() }) + binop_mem_gpr!(popcnt, self, sz, src, dst, { + panic!("singlepass can't emit POPCNT {:?} {:?} {:?}", sz, src, dst) + }) }); } fn emit_movzx(&mut self, sz_src: Size, src: Location, sz_dst: Size, dst: Location) { @@ -737,7 +957,10 @@ impl Emitter for Assembler { (Size::S16, Location::Memory(src, disp), Size::S64, Location::GPR(dst)) => { dynasm!(self ; movzx Rq(dst as u8), WORD [Rq(src as u8) + disp]); } - _ => unreachable!(), + _ => panic!( + "singlepass can't emit MOVZX {:?} {:?} {:?} {:?}", + sz_src, src, sz_dst, dst + ), } } fn emit_movsx(&mut self, sz_src: Size, src: Location, sz_dst: Size, dst: Location) { @@ -772,7 +995,94 @@ impl Emitter for Assembler { (Size::S32, Location::Memory(src, disp), Size::S64, Location::GPR(dst)) => { dynasm!(self ; movsx Rq(dst as u8), DWORD [Rq(src as u8) + disp]); } - _ => unreachable!(), + _ => panic!( + "singlepass can't emit MOVSX {:?} {:?} {:?} {:?}", + sz_src, src, sz_dst, dst + ), + } + } + + fn emit_xchg(&mut self, sz: Size, src: Location, dst: Location) { + match (sz, src, dst) { + (Size::S8, Location::GPR(src), Location::GPR(dst)) => { + dynasm!(self ; xchg Rb(dst as u8), Rb(src as u8)); + } + (Size::S16, Location::GPR(src), Location::GPR(dst)) => { + dynasm!(self ; xchg Rw(dst as u8), Rw(src as u8)); + } + (Size::S32, Location::GPR(src), Location::GPR(dst)) => { + dynasm!(self ; xchg Rd(dst as u8), Rd(src as u8)); + } + (Size::S64, Location::GPR(src), Location::GPR(dst)) => { + dynasm!(self ; xchg Rq(dst as u8), Rq(src as u8)); + } + (Size::S8, Location::Memory(src, disp), Location::GPR(dst)) => { + dynasm!(self ; xchg Rb(dst as u8), [Rq(src as u8) + disp]); + } + (Size::S8, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; xchg [Rq(dst as u8) + disp], Rb(src as u8)); + } + (Size::S16, Location::Memory(src, disp), Location::GPR(dst)) => { + dynasm!(self ; xchg Rw(dst as u8), [Rq(src as u8) + disp]); + } + (Size::S16, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; xchg [Rq(dst as u8) + disp], Rw(src as u8)); + } + (Size::S32, Location::Memory(src, disp), Location::GPR(dst)) => { + dynasm!(self ; xchg Rd(dst as u8), [Rq(src as u8) + disp]); + } + (Size::S32, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; xchg [Rq(dst as u8) + disp], Rd(src as u8)); + } + (Size::S64, Location::Memory(src, disp), Location::GPR(dst)) => { + dynasm!(self ; xchg Rq(dst as u8), [Rq(src as u8) + disp]); + } + (Size::S64, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; xchg [Rq(dst as u8) + disp], Rq(src as u8)); + } + _ => panic!("singlepass can't emit XCHG {:?} {:?} {:?}", sz, src, dst), + } + } + + fn emit_lock_xadd(&mut self, sz: Size, src: Location, dst: Location) { + match (sz, src, dst) { + (Size::S8, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; lock xadd [Rq(dst as u8) + disp], Rb(src as u8)); + } + (Size::S16, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; lock xadd [Rq(dst as u8) + disp], Rw(src as u8)); + } + (Size::S32, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; lock xadd [Rq(dst as u8) + disp], Rd(src as u8)); + } + (Size::S64, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; lock xadd [Rq(dst as u8) + disp], Rq(src as u8)); + } + _ => panic!( + "singlepass can't emit LOCK XADD {:?} {:?} {:?}", + sz, src, dst + ), + } + } + + fn emit_lock_cmpxchg(&mut self, sz: Size, src: Location, dst: Location) { + match (sz, src, dst) { + (Size::S8, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; lock cmpxchg [Rq(dst as u8) + disp], Rb(src as u8)); + } + (Size::S16, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; lock cmpxchg [Rq(dst as u8) + disp], Rw(src as u8)); + } + (Size::S32, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; lock cmpxchg [Rq(dst as u8) + disp], Rd(src as u8)); + } + (Size::S64, Location::GPR(src), Location::Memory(dst, disp)) => { + dynasm!(self ; lock cmpxchg [Rq(dst as u8) + disp], Rq(src as u8)); + } + _ => panic!( + "singlepass can't emit LOCK CMPXCHG {:?} {:?} {:?}", + sz, src, dst + ), } } @@ -792,6 +1102,27 @@ impl Emitter for Assembler { dynasm!(self ; cmovae Rq(dst as u8), Rq(src as u8)); } + fn emit_vmovaps(&mut self, src: XMMOrMemory, dst: XMMOrMemory) { + match (src, dst) { + (XMMOrMemory::XMM(src), XMMOrMemory::XMM(dst)) => dynasm!(self ; movaps Rx(dst as u8), Rx(src as u8)), + (XMMOrMemory::Memory(base, disp), XMMOrMemory::XMM(dst)) => dynasm!(self ; movaps Rx(dst as u8), [Rq(base as u8) + disp]), + (XMMOrMemory::XMM(src), XMMOrMemory::Memory(base, disp)) => dynasm!(self ; movaps [Rq(base as u8) + disp], Rx(src as u8)), + _ => panic!("singlepass can't emit VMOVAPS {:?} {:?}", src, dst), + }; + } + + fn emit_vmovapd(&mut self, src: XMMOrMemory, dst: XMMOrMemory) { + match (src, dst) { + (XMMOrMemory::XMM(src), XMMOrMemory::XMM(dst)) => dynasm!(self ; movapd Rx(dst as u8), Rx(src as u8)), + (XMMOrMemory::Memory(base, disp), XMMOrMemory::XMM(dst)) => dynasm!(self ; movapd Rx(dst as u8), [Rq(base as u8) + disp]), + (XMMOrMemory::XMM(src), XMMOrMemory::Memory(base, disp)) => dynasm!(self ; movapd [Rq(base as u8) + disp], Rx(src as u8)), + _ => panic!("singlepass can't emit VMOVAPD {:?} {:?}", src, dst), + }; + } + + avx_fn!(vxorps, emit_vxorps); + avx_fn!(vxorpd, emit_vxorpd); + avx_fn!(vaddss, emit_vaddss); avx_fn!(vaddsd, emit_vaddsd); @@ -828,6 +1159,12 @@ impl Emitter for Assembler { avx_fn!(vcmpgess, emit_vcmpgess); avx_fn!(vcmpgesd, emit_vcmpgesd); + avx_fn!(vcmpunordss, emit_vcmpunordss); + avx_fn!(vcmpunordsd, emit_vcmpunordsd); + + avx_fn!(vcmpordss, emit_vcmpordss); + avx_fn!(vcmpordsd, emit_vcmpordsd); + avx_fn!(vsqrtss, emit_vsqrtss); avx_fn!(vsqrtsd, emit_vsqrtsd); @@ -848,57 +1185,59 @@ impl Emitter for Assembler { avx_i2f_64_fn!(vcvtsi2ss, emit_vcvtsi2ss_64); avx_i2f_64_fn!(vcvtsi2sd, emit_vcvtsi2sd_64); + fn emit_vblendvps(&mut self, src1: XMM, src2: XMMOrMemory, mask: XMM, dst: XMM) { + match src2 { + XMMOrMemory::XMM(src2) => dynasm!(self ; vblendvps Rx(dst as u8), Rx(mask as u8), Rx(src2 as u8), Rx(src1 as u8)), + XMMOrMemory::Memory(base, disp) => dynasm!(self ; vblendvps Rx(dst as u8), Rx(mask as u8), [Rq(base as u8) + disp], Rx(src1 as u8)), + } + } + + fn emit_vblendvpd(&mut self, src1: XMM, src2: XMMOrMemory, mask: XMM, dst: XMM) { + match src2 { + XMMOrMemory::XMM(src2) => dynasm!(self ; vblendvpd Rx(dst as u8), Rx(mask as u8), Rx(src2 as u8), Rx(src1 as u8)), + XMMOrMemory::Memory(base, disp) => dynasm!(self ; vblendvpd Rx(dst as u8), Rx(mask as u8), [Rq(base as u8) + disp], Rx(src1 as u8)), + } + } + fn emit_ucomiss(&mut self, src: XMMOrMemory, dst: XMM) { match src { XMMOrMemory::XMM(x) => dynasm!(self ; ucomiss Rx(dst as u8), Rx(x as u8)), - XMMOrMemory::Memory(base, disp) => { - dynasm!(self ; ucomiss Rx(dst as u8), [Rq(base as u8) + disp]) - } + XMMOrMemory::Memory(base, disp) => dynasm!(self ; ucomiss Rx(dst as u8), [Rq(base as u8) + disp]), } } fn emit_ucomisd(&mut self, src: XMMOrMemory, dst: XMM) { match src { XMMOrMemory::XMM(x) => dynasm!(self ; ucomisd Rx(dst as u8), Rx(x as u8)), - XMMOrMemory::Memory(base, disp) => { - dynasm!(self ; ucomisd Rx(dst as u8), [Rq(base as u8) + disp]) - } + XMMOrMemory::Memory(base, disp) => dynasm!(self ; ucomisd Rx(dst as u8), [Rq(base as u8) + disp]), } } fn emit_cvttss2si_32(&mut self, src: XMMOrMemory, dst: GPR) { match src { XMMOrMemory::XMM(x) => dynasm!(self ; cvttss2si Rd(dst as u8), Rx(x as u8)), - XMMOrMemory::Memory(base, disp) => { - dynasm!(self ; cvttss2si Rd(dst as u8), [Rq(base as u8) + disp]) - } + XMMOrMemory::Memory(base, disp) => dynasm!(self ; cvttss2si Rd(dst as u8), [Rq(base as u8) + disp]), } } fn emit_cvttss2si_64(&mut self, src: XMMOrMemory, dst: GPR) { match src { XMMOrMemory::XMM(x) => dynasm!(self ; cvttss2si Rq(dst as u8), Rx(x as u8)), - XMMOrMemory::Memory(base, disp) => { - dynasm!(self ; cvttss2si Rq(dst as u8), [Rq(base as u8) + disp]) - } + XMMOrMemory::Memory(base, disp) => dynasm!(self ; cvttss2si Rq(dst as u8), [Rq(base as u8) + disp]), } } fn emit_cvttsd2si_32(&mut self, src: XMMOrMemory, dst: GPR) { match src { XMMOrMemory::XMM(x) => dynasm!(self ; cvttsd2si Rd(dst as u8), Rx(x as u8)), - XMMOrMemory::Memory(base, disp) => { - dynasm!(self ; cvttsd2si Rd(dst as u8), [Rq(base as u8) + disp]) - } + XMMOrMemory::Memory(base, disp) => dynasm!(self ; cvttsd2si Rd(dst as u8), [Rq(base as u8) + disp]), } } fn emit_cvttsd2si_64(&mut self, src: XMMOrMemory, dst: GPR) { match src { XMMOrMemory::XMM(x) => dynasm!(self ; cvttsd2si Rq(dst as u8), Rx(x as u8)), - XMMOrMemory::Memory(base, disp) => { - dynasm!(self ; cvttsd2si Rq(dst as u8), [Rq(base as u8) + disp]) - } + XMMOrMemory::Memory(base, disp) => dynasm!(self ; cvttsd2si Rq(dst as u8), [Rq(base as u8) + disp]), } } @@ -920,11 +1259,24 @@ impl Emitter for Assembler { match loc { Location::GPR(x) => dynasm!(self ; call Rq(x as u8)), Location::Memory(base, disp) => dynasm!(self ; call QWORD [Rq(base as u8) + disp]), - _ => unreachable!(), + _ => panic!("singlepass can't emit CALL {:?}", loc), } } fn emit_bkpt(&mut self) { dynasm!(self ; int 0x3); } + + fn emit_host_redirection(&mut self, target: GPR) { + self.emit_jmp_location(Location::GPR(target)); + } + + fn emit_inline_breakpoint(&mut self, ty: InlineBreakpointType) { + dynasm!(self + ; ud2 + ; .byte 0x0f ; .byte 0xb9u8 as i8 // ud + ; int -1 + ; .byte ty as u8 as i8 + ); + } } diff --git a/lib/singlepass-backend/src/lib.rs b/lib/singlepass-backend/src/lib.rs index e749e5a22..5f3607b45 100644 --- a/lib/singlepass-backend/src/lib.rs +++ b/lib/singlepass-backend/src/lib.rs @@ -8,15 +8,23 @@ unreachable_patterns )] #![feature(proc_macro_hygiene)] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] #[cfg(not(any( all(target_os = "macos", target_arch = "x86_64"), all(target_os = "linux", target_arch = "x86_64"), + all(target_os = "linux", target_arch = "aarch64"), )))] compile_error!("This crate doesn't yet support compiling on operating systems other than linux and macos and architectures other than x86_64"); extern crate dynasmrt; +extern crate serde; + +#[macro_use] +extern crate serde_derive; + #[macro_use] extern crate dynasm; @@ -31,6 +39,8 @@ mod codegen_x64; mod emitter_x64; mod machine; pub mod protect_unix; +#[cfg(target_arch = "aarch64")] +mod translator_aarch64; pub use codegen_x64::X64FunctionCode as FunctionCodeGenerator; pub use codegen_x64::X64ModuleCodeGenerator as ModuleCodeGenerator; diff --git a/lib/singlepass-backend/src/machine.rs b/lib/singlepass-backend/src/machine.rs index b09d0bf85..017ba1270 100644 --- a/lib/singlepass-backend/src/machine.rs +++ b/lib/singlepass-backend/src/machine.rs @@ -1,9 +1,10 @@ use crate::emitter_x64::*; use smallvec::SmallVec; use std::collections::HashSet; -use wasmer_runtime_core::state::x64::X64Register; -use wasmer_runtime_core::state::*; -use wasmparser::Type as WpType; +use wasmer_runtime_core::{ + state::{x64::X64Register, *}, + wasmparser::Type as WpType, +}; struct MachineStackOffset(usize); @@ -83,7 +84,14 @@ impl Machine { /// Releases a temporary GPR. pub fn release_temp_gpr(&mut self, gpr: GPR) { - assert_eq!(self.used_gprs.remove(&gpr), true); + assert!(self.used_gprs.remove(&gpr)); + } + + /// Specify that a given register is in use. + pub fn reserve_unused_temp_gpr(&mut self, gpr: GPR) -> GPR { + assert!(!self.used_gprs.contains(&gpr)); + self.used_gprs.insert(gpr); + gpr } /// Picks an unused XMM register. @@ -157,12 +165,12 @@ impl Machine { }; if let Location::GPR(x) = loc { self.used_gprs.insert(x); - self.state.register_values[X64Register::GPR(x).to_index().0] = *mv; + self.state.register_values[X64Register::GPR(x).to_index().0] = mv.clone(); } else if let Location::XMM(x) = loc { self.used_xmms.insert(x); - self.state.register_values[X64Register::XMM(x).to_index().0] = *mv; + self.state.register_values[X64Register::XMM(x).to_index().0] = mv.clone(); } else { - self.state.stack_values.push(*mv); + self.state.stack_values.push(mv.clone()); } self.state.wasm_stack.push(WasmAbstractValue::Runtime); ret.push(loc); @@ -494,11 +502,12 @@ mod test { let mut assembler = Assembler::new().unwrap(); let locs = machine.acquire_locations( &mut assembler, - &[(WpType::I32, MachineValue::Undefined); 10], + &(0..10) + .map(|_| (WpType::I32, MachineValue::Undefined)) + .collect::>(), false, ); machine.release_locations_keep_state(&mut assembler, &locs); } - } diff --git a/lib/singlepass-backend/src/translator_aarch64.rs b/lib/singlepass-backend/src/translator_aarch64.rs new file mode 100644 index 000000000..0c2b7a6ec --- /dev/null +++ b/lib/singlepass-backend/src/translator_aarch64.rs @@ -0,0 +1,1869 @@ +#![allow(dead_code)] + +use crate::emitter_x64::*; +use dynasmrt::{aarch64::Assembler, AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi}; +use wasmer_runtime_core::backend::InlineBreakpointType; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct AX(pub u32); + +impl AX { + pub fn x(&self) -> u32 { + self.0 + } +} + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct AV(pub u32); + +impl AV { + pub fn v(&self) -> u32 { + self.0 + } +} + +/* +#[repr(u8)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum GPR { + RAX, + RCX, + RDX, + RBX, + RSP, + RBP, + RSI, + RDI, + R8, + R9, + R10, + R11, + R12, + R13, + R14, + R15, +} + +#[repr(u8)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +pub enum XMM { + XMM0, + XMM1, + XMM2, + XMM3, + XMM4, + XMM5, + XMM6, + XMM7, +} +*/ + +pub fn map_gpr(gpr: GPR) -> AX { + use GPR::*; + + match gpr { + RAX => AX(0), + RCX => AX(1), + RDX => AX(2), + RBX => AX(3), + RSP => AX(28), + RBP => AX(5), + RSI => AX(6), + RDI => AX(7), + R8 => AX(8), + R9 => AX(9), + R10 => AX(10), + R11 => AX(11), + R12 => AX(12), + R13 => AX(13), + R14 => AX(14), + R15 => AX(15), + } +} + +pub fn map_xmm(xmm: XMM) -> AV { + use XMM::*; + + match xmm { + XMM0 => AV(0), + XMM1 => AV(1), + XMM2 => AV(2), + XMM3 => AV(3), + XMM4 => AV(4), + XMM5 => AV(5), + XMM6 => AV(6), + XMM7 => AV(7), + XMM8 => AV(8), + XMM9 => AV(9), + XMM10 => AV(10), + XMM11 => AV(11), + XMM12 => AV(12), + XMM13 => AV(13), + XMM14 => AV(14), + XMM15 => AV(15), + } +} + +pub fn get_aarch64_assembler() -> Assembler { + let a = Assembler::new().unwrap(); + dynasm!( + a + ; .arch aarch64 + ; .alias x_rsp, x28 + ; .alias x_tmp1, x27 + ; .alias w_tmp1, w27 + ; .alias x_tmp2, x26 + ; .alias w_tmp2, w26 + ; .alias x_tmp3, x25 + ; .alias w_tmp3, w25 + ; .alias d_tmp1, d28 + ; .alias d_tmp2, d27 + ; .alias v_tmp1, v28 + ; .alias v_tmp2, v27 + ); + a +} + +const X_TMP1: u32 = 27; +const X_TMP2: u32 = 26; +const X_TMP3: u32 = 25; +const V_TMP1: u32 = 28; +const V_TMP2: u32 = 27; + +macro_rules! binop_imm32_gpr { + ($ins:ident, $assembler:tt, $sz:expr, $src:expr, $dst:expr, $otherwise:block) => { + match ($sz, $src, $dst) { + (Size::S32, Location::Imm32(src), Location::GPR(dst)) => { + dynasm!($assembler + ; b >after + ; data: + ; .dword src as i32 + ; after: + ; ldr w_tmp1, { + dynasm!($assembler + ; b >after + ; data: + ; .qword src as i64 + ; after: + ; ldr x_tmp1, $otherwise + } + }; +} + +macro_rules! binop_imm32_mem { + ($ins:ident, $assembler:tt, $sz:expr, $src:expr, $dst:expr, $otherwise:block) => { + match ($sz, $src, $dst) { + (Size::S32, Location::Imm32(src), Location::Memory(dst, disp)) => { + if disp >= 0 { + dynasm!($assembler ; add x_tmp3, X(map_gpr(dst).x()), disp as u32); + } else { + dynasm!($assembler ; sub x_tmp3, X(map_gpr(dst).x()), (-disp) as u32); + } + dynasm!($assembler + ; b >after + ; data: + ; .dword src as i32 + ; after: + ; ldr w_tmp1, { + if disp >= 0 { + dynasm!($assembler ; add x_tmp3, X(map_gpr(dst).x()), disp as u32); + } else { + dynasm!($assembler ; sub x_tmp3, X(map_gpr(dst).x()), (-disp) as u32); + } + dynasm!($assembler + ; b >after + ; data: + ; .qword src as i64 + ; after: + ; ldr x_tmp1, $otherwise + } + }; +} + +macro_rules! binop_gpr_gpr { + ($ins:ident, $assembler:tt, $sz:expr, $src:expr, $dst:expr, $otherwise:block) => { + match ($sz, $src, $dst) { + (Size::S32, Location::GPR(src), Location::GPR(dst)) => { + dynasm!($assembler + ; $ins W(map_gpr(dst).x()), W(map_gpr(dst).x()), W(map_gpr(src).x()) + ); + }, + (Size::S64, Location::GPR(src), Location::GPR(dst)) => { + dynasm!($assembler + ; $ins X(map_gpr(dst).x()), X(map_gpr(dst).x()), X(map_gpr(src).x()) + ); + }, + _ => $otherwise + } + }; +} + +macro_rules! binop_gpr_mem { + ($ins:ident, $assembler:tt, $sz:expr, $src:expr, $dst:expr, $otherwise:block) => { + match ($sz, $src, $dst) { + (Size::S32, Location::GPR(src), Location::Memory(base, disp)) => { + if disp >= 0 { + dynasm!($assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!($assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, $otherwise + } + }; +} + +macro_rules! binop_mem_gpr { + ($ins:ident, $assembler:tt, $sz:expr, $src:expr, $dst:expr, $otherwise:block) => { + match ($sz, $src, $dst) { + (Size::S32, Location::Memory(base, disp), Location::GPR(dst)) => { + if disp >= 0 { + dynasm!($assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!($assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, $otherwise + } + }; +} + +macro_rules! binop_all_nofp { + ($ins:ident, $assembler:tt, $sz:expr, $src:expr, $dst:expr, $otherwise:block) => { + binop_imm32_gpr!($ins, $assembler, $sz, $src, $dst, { + binop_imm32_mem!($ins, $assembler, $sz, $src, $dst, { + binop_gpr_gpr!($ins, $assembler, $sz, $src, $dst, { + binop_gpr_mem!($ins, $assembler, $sz, $src, $dst, { + binop_mem_gpr!($ins, $assembler, $sz, $src, $dst, $otherwise) + }) + }) + }) + }) + }; +} + +macro_rules! binop_shift { + ($ins:ident, $assembler:tt, $sz:expr, $src:expr, $dst:expr, $otherwise:block) => { + match ($sz, $src, $dst) { + (Size::S32, Location::Imm8(imm), Location::GPR(dst)) => { + assert!(imm < 32); + dynasm!($assembler ; $ins W(map_gpr(dst).x()), W(map_gpr(dst).x()), imm as u32); + }, + (Size::S32, Location::Imm8(imm), Location::Memory(base, disp)) => { + assert!(imm < 32); + if disp >= 0 { + dynasm!($assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + dynasm!($assembler ; $ins W(map_gpr(dst).x()), W(map_gpr(dst).x()), W(map_gpr(GPR::RCX).x())); + }, + (Size::S32, Location::GPR(GPR::RCX), Location::Memory(base, disp)) => { + if disp >= 0 { + dynasm!($assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + assert!(imm < 32); + dynasm!($assembler ; $ins X(map_gpr(dst).x()), X(map_gpr(dst).x()), imm as u32); + }, + (Size::S64, Location::Imm8(imm), Location::Memory(base, disp)) => { + assert!(imm < 32); + if disp >= 0 { + dynasm!($assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + dynasm!($assembler ; $ins X(map_gpr(dst).x()), X(map_gpr(dst).x()), X(map_gpr(GPR::RCX).x())); + }, + (Size::S64, Location::GPR(GPR::RCX), Location::Memory(base, disp)) => { + if disp >= 0 { + dynasm!($assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, $otherwise + } + } +} + +macro_rules! avx_fn { + ($ins:ident, $width:ident, $width_int:ident, $name:ident) => { + fn $name(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM) { + match src2 { + XMMOrMemory::XMM(src2) => dynasm!(self ; $ins $width(map_xmm(dst).v()), $width(map_xmm(src1).v()), $width(map_xmm(src2).v())), + XMMOrMemory::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + fn $name(&mut self, src1: XMM, src2: XMMOrMemory, dst: XMM) { + match src2 { + XMMOrMemory::XMM(src2) => { + dynasm!( + self + ; fcmpe $width(map_xmm(src1).v()), $width(map_xmm(src2).v()) + ; cset w_tmp1, $cmpty + ; mov V(map_xmm(dst).v()).$width[0], $width_int(X_TMP1) + ); + }, + XMMOrMemory::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + fn $name(&mut self, src1: XMM, _src2: XMMOrMemory, dst: XMM) { + dynasm!(self ; $ins $width(map_xmm(dst).v()), $width(map_xmm(src1).v())); + } + } +} + +macro_rules! avx_fn_cvt { + ($ins:ident, $width_src:ident, $width_dst:ident, $name:ident) => { + fn $name(&mut self, src1: XMM, _src2: XMMOrMemory, dst: XMM) { + dynasm!(self ; $ins $width_dst(map_xmm(dst).v()), $width_src(map_xmm(src1).v())); + } + } +} + +impl Emitter for Assembler { + type Label = DynamicLabel; + type Offset = AssemblyOffset; + + fn get_label(&mut self) -> DynamicLabel { + self.new_dynamic_label() + } + + fn get_offset(&self) -> AssemblyOffset { + self.offset() + } + + fn get_jmp_instr_size(&self) -> u8 { + 4 + } + + fn emit_u64(&mut self, x: u64) { + self.push_u64(x); + } + + fn emit_label(&mut self, label: Self::Label) { + dynasm!(self ; => label); + } + + fn emit_nop(&mut self) { + dynasm!(self ; nop); + } + + fn emit_mov(&mut self, sz: Size, src: Location, dst: Location) { + match (sz, src, dst) { + (Size::S32, Location::GPR(src), Location::GPR(dst)) => { + dynasm!(self ; mov W(map_gpr(dst).x()), W(map_gpr(src).x())); + } + (Size::S32, Location::Memory(base, disp), Location::GPR(dst)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, after; data: ; .dword x as i32; after: ; ldr w_tmp1, { + dynasm!(self ; b >after; data: ; .dword x as i32; after: ; ldr W(map_gpr(dst).x()), { + dynasm!(self ; b >after; data: ; .dword x as i32; after: ; ldr W(map_gpr(dst).x()), { + dynasm!(self ; mov X(map_gpr(dst).x()), X(map_gpr(src).x())); + } + (Size::S64, Location::Memory(base, disp), Location::GPR(dst)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, after; data: ; .qword x as i64; after: ; ldr x_tmp1, { + dynasm!(self ; b >after; data: ; .qword x as i64; after: ; ldr X(map_gpr(dst).x()), { + dynasm!(self ; b >after; data: ; .qword x as i64; after: ; ldr X(map_gpr(dst).x()), { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, after; data: ; .dword x as i32; after: ; ldr w_tmp1, { + dynasm!(self ; b >after; data: ; .dword x as u8 as i32; after: ; ldr W(map_gpr(dst).x()), { + dynasm!(self ; b >after; data: ; .dword x as u8 as i32; after: ; ldr W(map_gpr(dst).x()), { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, after; data: ; .dword x as i32; after: ; ldr w_tmp1, { + dynasm!(self ; b >after; data: ; .dword x as u16 as i32; after: ; ldr W(map_gpr(dst).x()), { + dynasm!(self ; b >after; data: ; .dword x as u16 as i32; after: ; ldr W(map_gpr(dst).x()), { + dynasm!(self ; fmov S(map_xmm(dst).v()), S(map_xmm(src).v())); + } + (Size::S32, Location::XMM(src), Location::GPR(dst)) => { + dynasm!(self ; fmov W(map_gpr(dst).x()), S(map_xmm(src).v())); + } + (Size::S32, Location::GPR(src), Location::XMM(dst)) => { + dynasm!(self ; fmov S(map_xmm(dst).v()), W(map_gpr(src).x())); + } + (Size::S32, Location::Memory(base, disp), Location::XMM(dst)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + dynasm!(self ; fmov D(map_xmm(dst).v()), D(map_xmm(src).v())); + } + (Size::S64, Location::XMM(src), Location::GPR(dst)) => { + dynasm!(self ; fmov X(map_gpr(dst).x()), D(map_xmm(src).v())); + } + (Size::S64, Location::GPR(src), Location::XMM(dst)) => { + dynasm!(self ; fmov D(map_xmm(dst).v()), X(map_gpr(src).x())); + } + (Size::S64, Location::Memory(base, disp), Location::XMM(dst)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, panic!("NOT IMPL: {:?} {:?} {:?}", sz, src, dst), + } + } + + fn emit_lea(&mut self, sz: Size, src: Location, dst: Location) { + match (sz, src, dst) { + (Size::S32, Location::Memory(src, disp), Location::GPR(dst)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + } + fn emit_lea_label(&mut self, label: Self::Label, dst: Location) { + match dst { + Location::GPR(dst) => { + dynasm!(self ; adr X(map_gpr(dst).x()), =>label); + } + _ => unreachable!(), + } + } + + fn emit_cdq(&mut self) { + dynasm!( + self + ; b >after + ; bit_tester: + ; .dword 0x80000000u32 as i32 + ; all_ones: + ; .dword 0xffffffffu32 as i32 + ; after: + ; ldr w_tmp1, zero + ; not_zero: + ; ldr W(map_gpr(GPR::RDX).x()), after + ; zero: + ; mov W(map_gpr(GPR::RDX).x()), wzr + ; after: + ); + } + fn emit_cqo(&mut self) { + dynasm!( + self + ; b >after + ; bit_tester: + ; .qword 0x8000000000000000u64 as i64 + ; all_ones: + ; .qword 0xffffffffffffffffu64 as i64 + ; after: + ; ldr x_tmp1, zero + ; not_zero: + ; ldr X(map_gpr(GPR::RDX).x()), after + ; zero: + ; mov X(map_gpr(GPR::RDX).x()), xzr + ; after: + ); + } + fn emit_xor(&mut self, sz: Size, src: Location, dst: Location) { + binop_all_nofp!(eor, self, sz, src, dst, { unreachable!("xor") }); + } + fn emit_jmp(&mut self, condition: Condition, label: Self::Label) { + use Condition::*; + + match condition { + None => dynasm!(self ; b =>label), + Above => dynasm!(self ; b.hi =>label), + AboveEqual => dynasm!(self ; b.hs =>label), + Below => dynasm!(self ; b.lo =>label), + BelowEqual => dynasm!(self ; b.ls =>label), + Greater => dynasm!(self ; b.gt =>label), + GreaterEqual => dynasm!(self ; b.ge =>label), + Less => dynasm!(self ; b.lt =>label), + LessEqual => dynasm!(self ; b.le =>label), + Equal => dynasm!(self ; b.eq =>label), + NotEqual => dynasm!(self ; b.ne =>label), + Signed => dynasm!(self ; b.vs =>label), // TODO: Review this + } + } + + fn emit_jmp_location(&mut self, loc: Location) { + match loc { + Location::GPR(x) => dynasm!(self ; br X(map_gpr(x).x())), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + } + + fn emit_conditional_trap(&mut self, condition: Condition) { + use Condition::*; + + match condition { + None => dynasm!(self ; b >fail), + Above => dynasm!(self ; b.hi >fail), + AboveEqual => dynasm!(self ; b.hs >fail), + Below => dynasm!(self ; b.lo >fail), + BelowEqual => dynasm!(self ; b.ls >fail), + Greater => dynasm!(self ; b.gt >fail), + GreaterEqual => dynasm!(self ; b.ge >fail), + Less => dynasm!(self ; b.lt >fail), + LessEqual => dynasm!(self ; b.le >fail), + Equal => dynasm!(self ; b.eq >fail), + NotEqual => dynasm!(self ; b.ne >fail), + Signed => dynasm!(self ; b.vs >fail), // TODO: Review this + } + dynasm!( + self + ; b >ok + ; fail: + ; .dword 0 ; .dword 0 + ; ok: + ); + } + + fn emit_set(&mut self, condition: Condition, dst: GPR) { + use Condition::*; + + match condition { + None => dynasm!(self ; b >set), + Above => dynasm!(self ; b.hi >set), + AboveEqual => dynasm!(self ; b.hs >set), + Below => dynasm!(self ; b.lo >set), + BelowEqual => dynasm!(self ; b.ls >set), + Greater => dynasm!(self ; b.gt >set), + GreaterEqual => dynasm!(self ; b.ge >set), + Less => dynasm!(self ; b.lt >set), + LessEqual => dynasm!(self ; b.le >set), + Equal => dynasm!(self ; b.eq >set), + NotEqual => dynasm!(self ; b.ne >set), + Signed => dynasm!(self ; b.vs >set), // TODO: Review this + } + dynasm!( + self + ; mov W(map_gpr(dst).x()), wzr + ; b >ok + ; set: + ; mov W(map_gpr(dst).x()), 1 + ; ok: + ); + } + + fn emit_push(&mut self, sz: Size, src: Location) { + match (sz, src) { + (Size::S64, Location::Imm32(src)) => dynasm!(self + ; b >after + ; data: + ; .dword src as i32 + ; after: + ; ldr w_tmp1, dynasm!(self + ; sub x_rsp, x_rsp, 8 + ; str X(map_gpr(src).x()), [x_rsp] + ), + (Size::S64, Location::Memory(base, disp)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, panic!("push {:?} {:?}", sz, src), + } + } + fn emit_pop(&mut self, sz: Size, dst: Location) { + match (sz, dst) { + (Size::S64, Location::GPR(dst)) => dynasm!(self + ; ldr X(map_gpr(dst).x()), [x_rsp] + ; add x_rsp, x_rsp, 8 + ), + (Size::S64, Location::Memory(base, disp)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, panic!("pop {:?} {:?}", sz, dst), + } + } + fn emit_cmp(&mut self, sz: Size, left: Location, right: Location) { + match (sz, left, right) { + (Size::S32, Location::Imm32(left), Location::GPR(right)) => { + dynasm!(self + ; b >after + ; data: + ; .dword left as i32 + ; after: + ; ldr w_tmp1, { + dynasm!(self + ; b >after + ; data: + ; .qword left as i64 + ; after: + ; ldr x_tmp1, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, after + ; data: + ; .dword left as i32 + ; after: + ; ldr w_tmp1, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, after + ; data: + ; .qword left as i64 + ; after: + ; ldr x_tmp1, dynasm!( + self + ; cmp W(map_gpr(right).x()), W(map_gpr(left).x()) + ), + (Size::S64, Location::GPR(left), Location::GPR(right)) => dynasm!( + self + ; cmp X(map_gpr(right).x()), X(map_gpr(left).x()) + ), + (Size::S32, Location::GPR(left), Location::Memory(base, disp)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + } + fn emit_add(&mut self, sz: Size, src: Location, dst: Location) { + binop_all_nofp!(add, self, sz, src, dst, { unreachable!("add") }); + } + fn emit_sub(&mut self, sz: Size, src: Location, dst: Location) { + binop_all_nofp!(sub, self, sz, src, dst, { unreachable!("sub") }); + } + + fn emit_imul(&mut self, sz: Size, src: Location, dst: Location) { + binop_gpr_gpr!(mul, self, sz, src, dst, { + binop_mem_gpr!(mul, self, sz, src, dst, { unreachable!() }) + }); + } + fn emit_imul_imm32_gpr64(&mut self, src: u32, dst: GPR) { + dynasm!( + self + ; b >after + ; data: + ; .dword src as i32 + ; after: + ; ldr w_tmp1, { + match divisor { + Location::GPR(x) => dynasm!( + self + ; mov w_tmp1, W(map_gpr(x).x()) + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + dynasm!( + self + ; mov w_tmp2, W(map_gpr(GPR::RAX).x()) + ; udiv W(map_gpr(GPR::RAX).x()), w_tmp2, w_tmp1 + ; msub W(map_gpr(GPR::RDX).x()), W(map_gpr(GPR::RAX).x()), w_tmp1, w_tmp2 + ) + } + Size::S64 => { + match divisor { + Location::GPR(x) => dynasm!( + self + ; mov x_tmp1, X(map_gpr(x).x()) + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + dynasm!( + self + ; mov x_tmp2, X(map_gpr(GPR::RAX).x()) + ; udiv X(map_gpr(GPR::RAX).x()), x_tmp2, x_tmp1 + ; msub X(map_gpr(GPR::RDX).x()), X(map_gpr(GPR::RAX).x()), x_tmp1, x_tmp2 + ) + } + _ => unreachable!(), + } + } + fn emit_idiv(&mut self, sz: Size, divisor: Location) { + match sz { + Size::S32 => { + match divisor { + Location::GPR(x) => dynasm!( + self + ; mov w_tmp1, W(map_gpr(x).x()) + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + dynasm!( + self + ; mov w_tmp2, W(map_gpr(GPR::RAX).x()) + ; sdiv W(map_gpr(GPR::RAX).x()), w_tmp2, w_tmp1 + ; msub W(map_gpr(GPR::RDX).x()), W(map_gpr(GPR::RAX).x()), w_tmp1, w_tmp2 + ) + } + Size::S64 => { + match divisor { + Location::GPR(x) => dynasm!( + self + ; mov x_tmp1, X(map_gpr(x).x()) + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + dynasm!( + self + ; mov x_tmp2, X(map_gpr(GPR::RAX).x()) + ; sdiv X(map_gpr(GPR::RAX).x()), x_tmp2, x_tmp1 + ; msub X(map_gpr(GPR::RDX).x()), X(map_gpr(GPR::RAX).x()), x_tmp1, x_tmp2 + ) + } + _ => unreachable!(), + } + } + fn emit_shl(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(lsl, self, sz, src, dst, { unreachable!("shl") }); + } + fn emit_shr(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(lsr, self, sz, src, dst, { unreachable!("shr") }); + } + fn emit_sar(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(asr, self, sz, src, dst, { unreachable!("sar") }); + } + fn emit_rol(&mut self, sz: Size, src: Location, dst: Location) { + // TODO: We are changing content of `src` (possibly RCX) here. Will this break any assumptions? + match sz { + Size::S32 => match src { + Location::Imm8(x) => { + assert!(x < 32); + binop_shift!(ror, self, sz, Location::Imm8(32 - x), dst, { + unreachable!("rol") + }); + } + Location::GPR(GPR::RCX) => { + dynasm!( + self + ; mov w_tmp1, 32 + ; sub W(map_gpr(GPR::RCX).x()), w_tmp1, W(map_gpr(GPR::RCX).x()) + ); + binop_shift!(ror, self, sz, src, dst, { unreachable!("rol") }); + } + _ => unreachable!(), + }, + Size::S64 => match src { + Location::Imm8(x) => { + assert!(x < 64); + binop_shift!(ror, self, sz, Location::Imm8(64 - x), dst, { + unreachable!("rol") + }); + } + Location::GPR(GPR::RCX) => { + dynasm!( + self + ; mov x_tmp1, 64 + ; sub X(map_gpr(GPR::RCX).x()), x_tmp1, X(map_gpr(GPR::RCX).x()) + ); + binop_shift!(ror, self, sz, src, dst, { unreachable!("rol") }); + } + _ => unreachable!(), + }, + _ => unreachable!(), + } + } + fn emit_ror(&mut self, sz: Size, src: Location, dst: Location) { + binop_shift!(ror, self, sz, src, dst, { unreachable!("ror") }); + } + fn emit_and(&mut self, sz: Size, src: Location, dst: Location) { + binop_all_nofp!(and, self, sz, src, dst, { unreachable!("and") }); + } + fn emit_or(&mut self, sz: Size, src: Location, dst: Location) { + binop_all_nofp!(orr, self, sz, src, dst, { unreachable!("or") }); + } + fn emit_bsr(&mut self, _sz: Size, _src: Location, _dst: Location) { + unimplemented!("aarch64: bsr"); + } + fn emit_bsf(&mut self, _sz: Size, _src: Location, _dst: Location) { + unimplemented!("aarch64: bsf"); + } + fn arch_has_xzcnt(&self) -> bool { + true + } + fn arch_emit_lzcnt(&mut self, sz: Size, src: Location, dst: Location) { + emit_clz_variant(self, sz, &src, &dst, false); + } + fn arch_emit_tzcnt(&mut self, sz: Size, src: Location, dst: Location) { + emit_clz_variant(self, sz, &src, &dst, true); + } + fn emit_neg(&mut self, _sz: Size, _value: Location) { + unimplemented!("aarch64: neg"); + } + fn emit_popcnt(&mut self, sz: Size, src: Location, dst: Location) { + match sz { + Size::S32 => { + match src { + Location::GPR(src) => dynasm!( + self + ; mov w_tmp1, W(map_gpr(src).x()) + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + match dst { + Location::GPR(dst) => { + dynasm!( + self + ; mov v_tmp1.S[0], w_tmp1 + ; cnt v_tmp1.B16, v_tmp1.B16 + ; mov w_tmp1, v_tmp1.S[0] + ; mov W(map_gpr(dst).x()), w_tmp1 + ; add W(map_gpr(dst).x()), W(map_gpr(dst).x()), w_tmp1, lsr 8 + ; add W(map_gpr(dst).x()), W(map_gpr(dst).x()), w_tmp1, lsr 16 + ; add W(map_gpr(dst).x()), W(map_gpr(dst).x()), w_tmp1, lsr 24 + ; and W(map_gpr(dst).x()), W(map_gpr(dst).x()), 255 + ); + } + _ => unreachable!(), + } + } + Size::S64 => { + match src { + Location::GPR(src) => dynasm!( + self + ; mov x_tmp1, X(map_gpr(src).x()) + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + match dst { + Location::GPR(dst) => { + dynasm!( + self + ; mov v_tmp1.D[0], x_tmp1 + ; cnt v_tmp1.B16, v_tmp1.B16 + ; mov x_tmp1, v_tmp1.D[0] + ; mov X(map_gpr(dst).x()), x_tmp1 + ; add X(map_gpr(dst).x()), X(map_gpr(dst).x()), x_tmp1, lsr 8 + ; add X(map_gpr(dst).x()), X(map_gpr(dst).x()), x_tmp1, lsr 16 + ; add X(map_gpr(dst).x()), X(map_gpr(dst).x()), x_tmp1, lsr 24 + ; add X(map_gpr(dst).x()), X(map_gpr(dst).x()), x_tmp1, lsr 32 + ; add X(map_gpr(dst).x()), X(map_gpr(dst).x()), x_tmp1, lsr 40 + ; add X(map_gpr(dst).x()), X(map_gpr(dst).x()), x_tmp1, lsr 48 + ; add X(map_gpr(dst).x()), X(map_gpr(dst).x()), x_tmp1, lsr 56 + ; and X(map_gpr(dst).x()), X(map_gpr(dst).x()), 255 + ); + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } + } + fn emit_movzx(&mut self, sz_src: Size, src: Location, _sz_dst: Size, dst: Location) { + match (sz_src, src, dst) { + (Size::S8, Location::GPR(src), Location::GPR(dst)) => { + dynasm!(self ; uxtb W(map_gpr(dst).x()), W(map_gpr(src).x())); + } + (Size::S16, Location::GPR(src), Location::GPR(dst)) => { + dynasm!(self ; uxth W(map_gpr(dst).x()), W(map_gpr(src).x())); + } + (Size::S8, Location::Memory(base, disp), Location::GPR(dst)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + } + fn emit_movsx(&mut self, sz_src: Size, src: Location, sz_dst: Size, dst: Location) { + match (sz_src, src, sz_dst, dst) { + (Size::S8, Location::GPR(src), Size::S32, Location::GPR(dst)) => { + dynasm!(self ; sxtb W(map_gpr(dst).x()), W(map_gpr(src).x())); + } + (Size::S16, Location::GPR(src), Size::S32, Location::GPR(dst)) => { + dynasm!(self ; sxth W(map_gpr(dst).x()), W(map_gpr(src).x())); + } + (Size::S8, Location::Memory(base, disp), Size::S32, Location::GPR(dst)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + dynasm!(self ; sxtb X(map_gpr(dst).x()), W(map_gpr(src).x())); + } + (Size::S16, Location::GPR(src), Size::S64, Location::GPR(dst)) => { + dynasm!(self ; sxth X(map_gpr(dst).x()), W(map_gpr(src).x())); + } + (Size::S32, Location::GPR(src), Size::S64, Location::GPR(dst)) => { + dynasm!(self ; sxtw X(map_gpr(dst).x()), W(map_gpr(src).x())); + } + (Size::S8, Location::Memory(base, disp), Size::S64, Location::GPR(dst)) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + } + + fn emit_xchg(&mut self, _sz: Size, _src: Location, _dst: Location) { + unimplemented!("aarch64: xchg") + } + fn emit_lock_xadd(&mut self, _sz: Size, _src: Location, _dst: Location) { + unimplemented!("aarch64: xadd") + } + fn emit_lock_cmpxchg(&mut self, _sz: Size, _src: Location, _dst: Location) { + unimplemented!("aarch64: cmpxchg") + } + fn emit_vmovaps(&mut self, _src: XMMOrMemory, _dst: XMMOrMemory) { + unimplemented!("aarch64: vmovaps") + } + fn emit_vmovapd(&mut self, _src: XMMOrMemory, _dst: XMMOrMemory) { + unimplemented!("aarch64: vmovapd") + } + fn emit_vxorps(&mut self, _src1: XMM, _src2: XMMOrMemory, _dst: XMM) { + unimplemented!("aarch64: vxorps") + } + fn emit_vxorpd(&mut self, _src1: XMM, _src2: XMMOrMemory, _dst: XMM) { + unimplemented!("aarch64: vxorpd") + } + fn emit_vcmpunordss(&mut self, _src1: XMM, _src2: XMMOrMemory, _dst: XMM) { + unimplemented!("aarch64: vcmpunordss") + } + fn emit_vcmpunordsd(&mut self, _src1: XMM, _src2: XMMOrMemory, _dst: XMM) { + unimplemented!("aarch64: vcmpunordsd") + } + + fn emit_vcmpordss(&mut self, _src1: XMM, _src2: XMMOrMemory, _dst: XMM) { + unimplemented!("aarch64: vcmpordss") + } + fn emit_vcmpordsd(&mut self, _src1: XMM, _src2: XMMOrMemory, _dst: XMM) { + unimplemented!("aarch64: vcmpordsd") + } + + fn emit_vblendvps(&mut self, _src1: XMM, _src2: XMMOrMemory, _mask: XMM, _dst: XMM) { + unimplemented!("aarch64: vblendvps") + } + fn emit_vblendvpd(&mut self, _src1: XMM, _src2: XMMOrMemory, _mask: XMM, _dst: XMM) { + unimplemented!("aarch64: vblendvpd") + } + + avx_fn!(fadd, S, W, emit_vaddss); + avx_fn!(fsub, S, W, emit_vsubss); + avx_fn!(fmul, S, W, emit_vmulss); + avx_fn!(fdiv, S, W, emit_vdivss); + avx_fn!(fmax, S, W, emit_vmaxss); + avx_fn!(fmin, S, W, emit_vminss); + avx_cmp!(gt, S, W, emit_vcmpgtss); + avx_cmp!(ge, S, W, emit_vcmpgess); + avx_cmp!(mi, S, W, emit_vcmpltss); + avx_cmp!(ls, S, W, emit_vcmpless); + avx_cmp!(eq, S, W, emit_vcmpeqss); + avx_cmp!(ne, S, W, emit_vcmpneqss); + avx_fn_unop!(fsqrt, S, emit_vsqrtss); + avx_fn_unop!(frintn, S, emit_vroundss_nearest); // to nearest with ties to even + avx_fn_unop!(frintm, S, emit_vroundss_floor); // toward minus infinity + avx_fn_unop!(frintp, S, emit_vroundss_ceil); // toward positive infinity + avx_fn_unop!(frintz, S, emit_vroundss_trunc); // toward zero + avx_fn_cvt!(fcvt, S, D, emit_vcvtss2sd); + + avx_fn!(fadd, D, X, emit_vaddsd); + avx_fn!(fsub, D, X, emit_vsubsd); + avx_fn!(fmul, D, X, emit_vmulsd); + avx_fn!(fdiv, D, X, emit_vdivsd); + avx_fn!(fmax, D, X, emit_vmaxsd); + avx_fn!(fmin, D, X, emit_vminsd); + avx_cmp!(gt, D, X, emit_vcmpgtsd); + avx_cmp!(ge, D, X, emit_vcmpgesd); + avx_cmp!(mi, D, X, emit_vcmpltsd); + avx_cmp!(ls, D, X, emit_vcmplesd); + avx_cmp!(eq, D, X, emit_vcmpeqsd); + avx_cmp!(ne, D, X, emit_vcmpneqsd); + avx_fn_unop!(fsqrt, D, emit_vsqrtsd); + avx_fn_unop!(frintn, D, emit_vroundsd_nearest); // to nearest with ties to even + avx_fn_unop!(frintm, D, emit_vroundsd_floor); // toward minus infinity + avx_fn_unop!(frintp, D, emit_vroundsd_ceil); // toward positive infinity + avx_fn_unop!(frintz, D, emit_vroundsd_trunc); // toward zero + avx_fn_cvt!(fcvt, D, S, emit_vcvtsd2ss); + + fn arch_has_itruncf(&self) -> bool { + true + } + fn arch_emit_i32_trunc_sf32(&mut self, src: XMM, dst: GPR) { + dynasm!(self ; fcvtzs W(map_gpr(dst).x()), S(map_xmm(src).v())); + } + fn arch_emit_i32_trunc_sf64(&mut self, src: XMM, dst: GPR) { + dynasm!(self ; fcvtzs W(map_gpr(dst).x()), D(map_xmm(src).v())); + } + fn arch_emit_i32_trunc_uf32(&mut self, src: XMM, dst: GPR) { + dynasm!(self ; fcvtzu W(map_gpr(dst).x()), S(map_xmm(src).v())); + } + fn arch_emit_i32_trunc_uf64(&mut self, src: XMM, dst: GPR) { + dynasm!(self ; fcvtzu W(map_gpr(dst).x()), D(map_xmm(src).v())); + } + fn arch_emit_i64_trunc_sf32(&mut self, src: XMM, dst: GPR) { + dynasm!(self ; fcvtzs X(map_gpr(dst).x()), S(map_xmm(src).v())); + } + fn arch_emit_i64_trunc_sf64(&mut self, src: XMM, dst: GPR) { + dynasm!(self ; fcvtzs X(map_gpr(dst).x()), D(map_xmm(src).v())); + } + fn arch_emit_i64_trunc_uf32(&mut self, src: XMM, dst: GPR) { + dynasm!(self ; fcvtzu X(map_gpr(dst).x()), S(map_xmm(src).v())); + } + fn arch_emit_i64_trunc_uf64(&mut self, src: XMM, dst: GPR) { + dynasm!(self ; fcvtzu X(map_gpr(dst).x()), D(map_xmm(src).v())); + } + + fn arch_has_fconverti(&self) -> bool { + true + } + fn arch_emit_f32_convert_si32(&mut self, src: GPR, dst: XMM) { + dynasm!(self ; scvtf S(map_xmm(dst).v()), W(map_gpr(src).x())); + } + fn arch_emit_f32_convert_si64(&mut self, src: GPR, dst: XMM) { + dynasm!(self ; scvtf S(map_xmm(dst).v()), X(map_gpr(src).x())); + } + fn arch_emit_f32_convert_ui32(&mut self, src: GPR, dst: XMM) { + dynasm!(self ; ucvtf S(map_xmm(dst).v()), W(map_gpr(src).x())); + } + fn arch_emit_f32_convert_ui64(&mut self, src: GPR, dst: XMM) { + dynasm!(self ; ucvtf S(map_xmm(dst).v()), X(map_gpr(src).x())); + } + fn arch_emit_f64_convert_si32(&mut self, src: GPR, dst: XMM) { + dynasm!(self ; scvtf D(map_xmm(dst).v()), W(map_gpr(src).x())); + } + fn arch_emit_f64_convert_si64(&mut self, src: GPR, dst: XMM) { + dynasm!(self ; scvtf D(map_xmm(dst).v()), X(map_gpr(src).x())); + } + fn arch_emit_f64_convert_ui32(&mut self, src: GPR, dst: XMM) { + dynasm!(self ; ucvtf D(map_xmm(dst).v()), W(map_gpr(src).x())); + } + fn arch_emit_f64_convert_ui64(&mut self, src: GPR, dst: XMM) { + dynasm!(self ; ucvtf D(map_xmm(dst).v()), X(map_gpr(src).x())); + } + + fn arch_has_fneg(&self) -> bool { + true + } + fn arch_emit_f32_neg(&mut self, src: XMM, dst: XMM) { + dynasm!(self ; fneg S(map_xmm(dst).v()), S(map_xmm(src).v())); + } + fn arch_emit_f64_neg(&mut self, src: XMM, dst: XMM) { + dynasm!(self ; fneg D(map_xmm(dst).v()), D(map_xmm(src).v())); + } + + fn emit_btc_gpr_imm8_32(&mut self, _src: u8, _dst: GPR) { + unimplemented!(); + } + fn emit_btc_gpr_imm8_64(&mut self, _src: u8, _dst: GPR) { + unimplemented!(); + } + fn emit_cmovae_gpr_32(&mut self, _src: GPR, _dst: GPR) { + unimplemented!(); + } + fn emit_cmovae_gpr_64(&mut self, _src: GPR, _dst: GPR) { + unimplemented!(); + } + fn emit_ucomiss(&mut self, _src: XMMOrMemory, _dst: XMM) { + unimplemented!(); + } + fn emit_ucomisd(&mut self, _src: XMMOrMemory, _dst: XMM) { + unimplemented!(); + } + fn emit_cvttss2si_32(&mut self, _src: XMMOrMemory, _dst: GPR) { + unimplemented!(); + } + fn emit_cvttss2si_64(&mut self, _src: XMMOrMemory, _dst: GPR) { + unimplemented!(); + } + fn emit_cvttsd2si_32(&mut self, _src: XMMOrMemory, _dst: GPR) { + unimplemented!(); + } + fn emit_cvttsd2si_64(&mut self, _src: XMMOrMemory, _dst: GPR) { + unimplemented!(); + } + fn emit_vcvtsi2ss_32(&mut self, _src1: XMM, _src2: GPROrMemory, _dst: XMM) { + unimplemented!(); + } + fn emit_vcvtsi2ss_64(&mut self, _src1: XMM, _src2: GPROrMemory, _dst: XMM) { + unimplemented!(); + } + fn emit_vcvtsi2sd_32(&mut self, _src1: XMM, _src2: GPROrMemory, _dst: XMM) { + unimplemented!(); + } + fn emit_vcvtsi2sd_64(&mut self, _src1: XMM, _src2: GPROrMemory, _dst: XMM) { + unimplemented!(); + } + fn emit_test_gpr_64(&mut self, _reg: GPR) { + unimplemented!(); + } + + fn emit_ud2(&mut self) { + dynasm!(self ; .dword 0 ; .dword 2) + } + fn emit_ret(&mut self) { + dynasm!(self + ; ldr x_tmp1, [x_rsp] + ; add x_rsp, x_rsp, 8 + ; br x_tmp1 + ); + } + fn emit_call_label(&mut self, label: Self::Label) { + dynasm!(self + ; b >after + ; addr: + ; .qword =>label // Is this the offset? + ; after: + + // Calculate the target address. + ; ldr x_tmp1, done + ; str x_tmp2, [x_rsp] + + // Jump. + ; br x_tmp1 + ; done: + ); + } + fn emit_call_location(&mut self, loc: Location) { + match loc { + Location::GPR(x) => dynasm!(self + // Push return address. + ; sub x_rsp, x_rsp, 8 + ; adr x_tmp1, >done + ; str x_tmp1, [x_rsp] + + // Jump. + ; br X(map_gpr(x).x()) + ; done: + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, done + ; str x_tmp1, [x_rsp] + + // Read memory. + ; ldr x_tmp1, [x_tmp3] + + // Jump. + ; br x_tmp1 + ; done: + ); + } + _ => unreachable!(), + } + } + + fn emit_bkpt(&mut self) { + dynasm!(self ; .dword 0 ; .dword 1) + } + + fn emit_host_redirection(&mut self, target: GPR) { + let target = map_gpr(target); + dynasm!( + self + ; sub sp, sp, 80 + ; str x30, [sp, 0] // LR + ; str X(target.x()), [sp, 8] + // Save callee-saved registers as required by x86-64 conventions. + ; str X(map_gpr(GPR::RBX).x()), [sp, 16] + ; str X(map_gpr(GPR::R12).x()), [sp, 24] + ; str X(map_gpr(GPR::R13).x()), [sp, 32] + ; str X(map_gpr(GPR::R14).x()), [sp, 40] + ; str X(map_gpr(GPR::R15).x()), [sp, 48] + ; str X(map_gpr(GPR::RBP).x()), [sp, 56] + ; str X(map_gpr(GPR::RSP).x()), [sp, 64] + ; adr x30, >after + + // Put parameters in correct order + ; sub sp, sp, 64 + ; str X(map_gpr(GPR::RDI).x()), [sp, 0] + ; str X(map_gpr(GPR::RSI).x()), [sp, 8] + ; str X(map_gpr(GPR::RDX).x()), [sp, 16] + ; str X(map_gpr(GPR::RCX).x()), [sp, 24] + ; str X(map_gpr(GPR::R8).x()), [sp, 32] + ; str X(map_gpr(GPR::R9).x()), [sp, 40] + ; ldr x0, [sp, 0] + ; ldr x1, [sp, 8] + ; ldr x2, [sp, 16] + ; ldr x3, [sp, 24] + ; ldr x4, [sp, 32] + ; ldr x5, [sp, 40] + ; add sp, sp, 64 + + // Branch to saved target + ; ldr x8, [sp, 8] + ; br x8 + + ; after: + ; ldr x30, [sp, 0] // LR + ; ldr X(map_gpr(GPR::RBX).x()), [sp, 16] + ; ldr X(map_gpr(GPR::R12).x()), [sp, 24] + ; ldr X(map_gpr(GPR::R13).x()), [sp, 32] + ; ldr X(map_gpr(GPR::R14).x()), [sp, 40] + ; ldr X(map_gpr(GPR::R15).x()), [sp, 48] + ; ldr X(map_gpr(GPR::RBP).x()), [sp, 56] + ; ldr X(map_gpr(GPR::RSP).x()), [sp, 64] + ; add sp, sp, 80 + + ; ldr x_tmp1, [x_rsp] + ; add x_rsp, x_rsp, 8 + ; br x_tmp1 + ); + } + + fn emit_inline_breakpoint(&mut self, ty: InlineBreakpointType) { + dynasm!(self + ; .dword 0 + ; .dword -1 + ; .dword (ty as u8 as i32) + ); + } + + fn arch_supports_canonicalize_nan(&self) -> bool { + false + } + + fn arch_requires_indirect_call_trampoline(&self) -> bool { + true + } + + fn arch_emit_indirect_call_with_trampoline(&mut self, loc: Location) { + match loc { + Location::GPR(x) => { + dynasm!(self + // Push return address. + ; sub x_rsp, x_rsp, 8 + ; adr x_tmp1, >done + ; str x_tmp1, [x_rsp] + ); + self.emit_host_redirection(x); + dynasm!(self ; done: ); + } + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(self ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, done + ; str x_tmp1, [x_rsp] + + // Read memory. + ; ldr X(map_gpr(GPR::RAX).x()), [x_tmp3] + ); + self.emit_host_redirection(GPR::RAX); + dynasm!(self ; done: ); + } + _ => unreachable!(), + } + } + + fn arch_emit_entry_trampoline(&mut self) { + dynasm!( + self + ; mov x18, x28 + ; mov x28, sp // WASM stack pointer + ; ldr x9, >v_65536 + ; sub sp, sp, x9 // Pre-allocate the WASM stack + ; sub x28, x28, 16 // for the last two arguments + + // Fixup param locations. + ; str x0, [sp, 0] + ; str x1, [sp, 8] + ; str x2, [sp, 16] + ; str x3, [sp, 24] + ; str x4, [sp, 32] + ; str x5, [sp, 40] + ; str x6, [x28, 0] + ; str x7, [x28, 8] + ; ldr X(map_gpr(GPR::RDI).x()), [sp, 0] + ; ldr X(map_gpr(GPR::RSI).x()), [sp, 8] + ; ldr X(map_gpr(GPR::RDX).x()), [sp, 16] + ; ldr X(map_gpr(GPR::RCX).x()), [sp, 24] + ; ldr X(map_gpr(GPR::R8).x()), [sp, 32] + ; ldr X(map_gpr(GPR::R9).x()), [sp, 40] + + ; str x19, [sp, 0] + ; str x20, [sp, 8] + ; str x21, [sp, 16] + ; str x22, [sp, 24] + ; str x23, [sp, 32] + ; str x24, [sp, 40] + ; str x25, [sp, 48] + ; str x26, [sp, 56] + ; str x27, [sp, 64] + ; str x18, [sp, 72] // previously x28 + ; str x29, [sp, 80] + ; str x30, [sp, 88] + + // return address + ; adr x20, >done + ; sub x28, x28, 8 + ; str x20, [x28] // Keep this consistent with RSP mapping in translator_aarch64 + + // Jump to target function! + ; b >real_entry + + ; done: + ; ldr x19, [sp, 0] + ; ldr x20, [sp, 8] + ; ldr x21, [sp, 16] + ; ldr x22, [sp, 24] + ; ldr x23, [sp, 32] + ; ldr x24, [sp, 40] + ; ldr x25, [sp, 48] + ; ldr x26, [sp, 56] + ; ldr x27, [sp, 64] + ; ldr x28, [sp, 72] + ; ldr x29, [sp, 80] + ; ldr x30, [sp, 88] + ; ldr x9, >v_65536 + ; add sp, sp, x9 // Resume stack pointer + ; br x30 // LR + + ; v_65536: + ; .qword 524288 + + ; real_entry: + ) + } +} + +fn emit_clz_variant( + assembler: &mut Assembler, + sz: Size, + src: &Location, + dst: &Location, + reversed: bool, +) { + match sz { + Size::S32 => { + match *src { + Location::GPR(src) => dynasm!( + assembler + ; mov w_tmp1, W(map_gpr(src).x()) + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + match *dst { + Location::GPR(dst) => { + if reversed { + dynasm!(assembler ; rbit w_tmp1, w_tmp1); + } + dynasm!( + assembler + ; clz W(map_gpr(dst).x()), w_tmp1 + ); + } + _ => unreachable!(), + } + } + Size::S64 => { + match *src { + Location::GPR(src) => dynasm!( + assembler + ; mov x_tmp1, X(map_gpr(src).x()) + ), + Location::Memory(base, disp) => { + if disp >= 0 { + dynasm!(assembler ; b >after ; disp: ; .dword disp ; after: ; ldr w_tmp3, after ; disp: ; .dword -disp ; after: ; ldr w_tmp3, unreachable!(), + } + match *dst { + Location::GPR(dst) => { + if reversed { + dynasm!(assembler ; rbit x_tmp1, x_tmp1) + } + dynasm!( + assembler + ; clz X(map_gpr(dst).x()), x_tmp1 + ); + } + _ => unreachable!(), + } + } + _ => unreachable!(), + } +} diff --git a/lib/spectests/Cargo.toml b/lib/spectests/Cargo.toml index 2bac32872..81bd57669 100644 --- a/lib/spectests/Cargo.toml +++ b/lib/spectests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmer-spectests" -version = "0.6.0" +version = "0.11.0" description = "Wasmer spectests library" license = "MIT" authors = ["The Wasmer Engineering Team "] @@ -8,11 +8,11 @@ repository = "https://github.com/wasmerio/wasmer" edition = "2018" [dependencies] -glob = "0.3.0" -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } -wasmer-clif-backend = { path = "../clif-backend", version = "0.6.0" } -wasmer-llvm-backend = { path = "../llvm-backend", version = "0.6.0", optional = true } -wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.6.0", optional = true } +glob = "0.3" +wasmer-runtime = { path = "../runtime", version = "0.11.0", default-features = false} +wasmer-clif-backend = { path = "../clif-backend", version = "0.11.0", optional = true} +wasmer-llvm-backend = { path = "../llvm-backend", version = "0.11.0", features = ["test"], optional = true } +wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.11.0", optional = true } [build-dependencies] wabt = "0.9.1" @@ -23,6 +23,6 @@ wabt = "0.9.1" [features] default = ["fast-tests"] fast-tests = [] -clif = [] -llvm = ["wasmer-llvm-backend"] -singlepass = ["wasmer-singlepass-backend"] +clif = ["wasmer-clif-backend", "wasmer-runtime/default-backend-cranelift"] +singlepass = ["wasmer-singlepass-backend", "wasmer-runtime/default-backend-singlepass"] +llvm = ["wasmer-llvm-backend", "wasmer-runtime/default-backend-llvm"] diff --git a/lib/spectests/README.md b/lib/spectests/README.md index 439b95887..da05683ea 100644 --- a/lib/spectests/README.md +++ b/lib/spectests/README.md @@ -1,21 +1,21 @@

- Wasmer logo + Wasmer logo

- - Build Status + + Build Status - License + License Join the Wasmer Community - Number of downloads from crates.io + Number of downloads from crates.io Read our API documentation diff --git a/lib/spectests/examples/simple/main.rs b/lib/spectests/examples/simple/main.rs index 357adb5f7..6ef870479 100644 --- a/lib/spectests/examples/simple/main.rs +++ b/lib/spectests/examples/simple/main.rs @@ -1,53 +1,20 @@ use wabt::wat2wasm; -use wasmer_runtime_core::{ - backend::Compiler, - error, - global::Global, - memory::Memory, - prelude::*, - table::Table, +use wasmer_runtime::{ + compile, error, func, imports, types::{ElementType, MemoryDescriptor, TableDescriptor, Value}, units::Pages, + Ctx, Global, Memory, Table, }; -#[cfg(feature = "clif")] -fn get_compiler() -> impl Compiler { - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() -} - -#[cfg(feature = "llvm")] -fn get_compiler() -> impl Compiler { - use wasmer_llvm_backend::LLVMCompiler; - LLVMCompiler::new() -} - -#[cfg(feature = "singlepass")] -fn get_compiler() -> impl Compiler { - use wasmer_singlepass_backend::SinglePassCompiler; - SinglePassCompiler::new() -} - -#[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] -fn get_compiler() -> impl Compiler { - panic!("compiler not specified, activate a compiler via features"); - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() -} - static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm"); fn main() -> error::Result<()> { let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed"); - let inner_module = wasmer_runtime_core::compile_with(&wasm_binary, &get_compiler())?; + let inner_module = compile(&wasm_binary)?; - let memory = Memory::new(MemoryDescriptor { - minimum: Pages(1), - maximum: Some(Pages(1)), - shared: false, - }) - .unwrap(); + let memory_desc = MemoryDescriptor::new(Pages(1), Some(Pages(1)), false).unwrap(); + let memory = Memory::new(memory_desc).unwrap(); let global = Global::new(Value::I32(42)); @@ -75,7 +42,7 @@ fn main() -> error::Result<()> { "env" => inner_instance, }; - let outer_module = wasmer_runtime_core::compile_with(EXAMPLE_WASM, &get_compiler())?; + let outer_module = compile(EXAMPLE_WASM)?; let outer_instance = outer_module.instantiate(&outer_imports)?; let ret = outer_instance.call("main", &[Value::I32(42)])?; println!("ret: {:?}", ret); @@ -83,7 +50,7 @@ fn main() -> error::Result<()> { Ok(()) } -fn print_num(ctx: &mut vm::Ctx, n: i32) -> Result { +fn print_num(ctx: &mut Ctx, n: i32) -> Result { println!("print_num({})", n); let memory: &Memory = ctx.memory(0); diff --git a/lib/spectests/examples/test.rs b/lib/spectests/examples/test.rs index 8ce199695..eff7793b9 100644 --- a/lib/spectests/examples/test.rs +++ b/lib/spectests/examples/test.rs @@ -1,6 +1,5 @@ use wabt::wat2wasm; -use wasmer_clif_backend::CraneliftCompiler; -use wasmer_runtime_core::{backend::Compiler, import::ImportObject, Instance}; +use wasmer_runtime::{compile, ImportObject, Instance}; fn main() { let instance = create_module_1(); @@ -24,38 +23,12 @@ fn create_module_1() -> Instance { (elem (;1;) (i32.const 9) 1)) "#; let wasm_binary = wat2wasm(module_str.as_bytes()).expect("WAST not valid or malformed"); - let module = wasmer_runtime_core::compile_with(&wasm_binary[..], &get_compiler()) - .expect("WASM can't be compiled"); + let module = compile(&wasm_binary[..]).expect("WASM can't be compiled"); module .instantiate(&generate_imports()) .expect("WASM can't be instantiated") } -#[cfg(feature = "clif")] -fn get_compiler() -> impl Compiler { - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() -} - -#[cfg(feature = "llvm")] -fn get_compiler() -> impl Compiler { - use wasmer_llvm_backend::LLVMCompiler; - LLVMCompiler::new() -} - -#[cfg(feature = "singlepass")] -fn get_compiler() -> impl Compiler { - use wasmer_singlepass_backend::SinglePassCompiler; - SinglePassCompiler::new() -} - -#[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] -fn get_compiler() -> impl Compiler { - panic!("compiler not specified, activate a compiler via features"); - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() -} - static IMPORT_MODULE: &str = r#" (module (type $t0 (func (param i32))) @@ -69,8 +42,7 @@ static IMPORT_MODULE: &str = r#" pub fn generate_imports() -> ImportObject { let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed"); - let module = wasmer_runtime_core::compile_with(&wasm_binary[..], &get_compiler()) - .expect("WASM can't be compiled"); + let module = compile(&wasm_binary[..]).expect("WASM can't be compiled"); let instance = module .instantiate(&ImportObject::new()) .expect("WASM can't be instantiated"); diff --git a/lib/spectests/spectests/LICENSE b/lib/spectests/spectests/LICENSE new file mode 100644 index 000000000..8f71f43fe --- /dev/null +++ b/lib/spectests/spectests/LICENSE @@ -0,0 +1,202 @@ + 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. + diff --git a/lib/spectests/spectests/atomic.wast b/lib/spectests/spectests/atomic.wast index de6b3ade5..5455e0969 100644 --- a/lib/spectests/spectests/atomic.wast +++ b/lib/spectests/spectests/atomic.wast @@ -1,5 +1,4 @@ ;; atomic.wast from WebAssembly test suite. -;; https://github.com/WebAssembly/testsuite/blob/master/LICENSE ;; ;; Modified by Wasmer to parse with the wabt spec test parser, replacing ;; '_u.add' with '.add_u' and similarly for 'sub', 'and', 'or', 'xor', 'xchg' diff --git a/lib/spectests/spectests/conversions.wast b/lib/spectests/spectests/conversions.wast index c0ae54a53..8022a1a60 100644 --- a/lib/spectests/spectests/conversions.wast +++ b/lib/spectests/spectests/conversions.wast @@ -1,6 +1,11 @@ (module (func (export "i64.extend_i32_s") (param $x i32) (result i64) (i64.extend_i32_s (local.get $x))) (func (export "i64.extend_i32_u") (param $x i32) (result i64) (i64.extend_i32_u (local.get $x))) + (func (export "i64.extend32_s") (param $x i64) (result i64) (i64.extend32_s (local.get $x))) + (func (export "i64.extend16_s") (param $x i64) (result i64) (i64.extend16_s (local.get $x))) + (func (export "i64.extend8_s") (param $x i64) (result i64) (i64.extend8_s (local.get $x))) + (func (export "i32.extend16_s") (param $x i32) (result i32) (i32.extend16_s (local.get $x))) + (func (export "i32.extend8_s") (param $x i32) (result i32) (i32.extend8_s (local.get $x))) (func (export "i32.wrap_i64") (param $x i64) (result i32) (i32.wrap_i64 (local.get $x))) (func (export "i32.trunc_f32_s") (param $x f32) (result i32) (i32.trunc_f32_s (local.get $x))) (func (export "i32.trunc_f32_u") (param $x f32) (result i32) (i32.trunc_f32_u (local.get $x))) @@ -497,3 +502,46 @@ (assert_invalid (module (func (result f64) (f64.convert_i64_u (i32.const 0)))) "type mismatch") (assert_invalid (module (func (result f64) (f64.promote_f32 (i32.const 0)))) "type mismatch") (assert_invalid (module (func (result f64) (f64.reinterpret_i64 (i32.const 0)))) "type mismatch") + +(assert_return (invoke "i32.extend8_s" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "i32.extend8_s" (i32.const 0x7f)) (i32.const 127)) +(assert_return (invoke "i32.extend8_s" (i32.const 0x80)) (i32.const -128)) +(assert_return (invoke "i32.extend8_s" (i32.const 0xff)) (i32.const -1)) +(assert_return (invoke "i32.extend8_s" (i32.const 0x012345_00)) (i32.const 0)) +(assert_return (invoke "i32.extend8_s" (i32.const 0xfedcba_80)) (i32.const -0x80)) +(assert_return (invoke "i32.extend8_s" (i32.const -1)) (i32.const -1)) + +(assert_return (invoke "i32.extend16_s" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "i32.extend16_s" (i32.const 0x7fff)) (i32.const 32767)) +(assert_return (invoke "i32.extend16_s" (i32.const 0x8000)) (i32.const -32768)) +(assert_return (invoke "i32.extend16_s" (i32.const 0xffff)) (i32.const -1)) +(assert_return (invoke "i32.extend16_s" (i32.const 0x0123_0000)) (i32.const 0)) +(assert_return (invoke "i32.extend16_s" (i32.const 0xfedc_8000)) (i32.const -0x8000)) +(assert_return (invoke "i32.extend16_s" (i32.const -1)) (i32.const -1)) + +(assert_return (invoke "i64.extend8_s" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "i64.extend8_s" (i64.const 0x7f)) (i64.const 127)) +(assert_return (invoke "i64.extend8_s" (i64.const 0x80)) (i64.const -128)) +(assert_return (invoke "i64.extend8_s" (i64.const 0xff)) (i64.const -1)) +(assert_return (invoke "i64.extend8_s" (i64.const 0x01234567_89abcd_00)) (i64.const 0)) +(assert_return (invoke "i64.extend8_s" (i64.const 0xfedcba98_765432_80)) (i64.const -0x80)) +(assert_return (invoke "i64.extend8_s" (i64.const -1)) (i64.const -1)) + +(assert_return (invoke "i64.extend16_s" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "i64.extend16_s" (i64.const 0x7fff)) (i64.const 32767)) +(assert_return (invoke "i64.extend16_s" (i64.const 0x8000)) (i64.const -32768)) +(assert_return (invoke "i64.extend16_s" (i64.const 0xffff)) (i64.const -1)) +(assert_return (invoke "i64.extend16_s" (i64.const 0x12345678_9abc_0000)) (i64.const 0)) +(assert_return (invoke "i64.extend16_s" (i64.const 0xfedcba98_7654_8000)) (i64.const -0x8000)) +(assert_return (invoke "i64.extend16_s" (i64.const -1)) (i64.const -1)) + +(assert_return (invoke "i64.extend32_s" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "i64.extend32_s" (i64.const 0x7fff)) (i64.const 32767)) +(assert_return (invoke "i64.extend32_s" (i64.const 0x8000)) (i64.const 32768)) +(assert_return (invoke "i64.extend32_s" (i64.const 0xffff)) (i64.const 65535)) +(assert_return (invoke "i64.extend32_s" (i64.const 0x7fffffff)) (i64.const 0x7fffffff)) +(assert_return (invoke "i64.extend32_s" (i64.const 0x80000000)) (i64.const -0x80000000)) +(assert_return (invoke "i64.extend32_s" (i64.const 0xffffffff)) (i64.const -1)) +(assert_return (invoke "i64.extend32_s" (i64.const 0x01234567_00000000)) (i64.const 0)) +(assert_return (invoke "i64.extend32_s" (i64.const 0xfedcba98_80000000)) (i64.const -0x80000000)) +(assert_return (invoke "i64.extend32_s" (i64.const -1)) (i64.const -1)) \ No newline at end of file diff --git a/lib/spectests/spectests/simd.wast b/lib/spectests/spectests/simd.wast index c59e087c0..b17debb9f 100644 --- a/lib/spectests/spectests/simd.wast +++ b/lib/spectests/spectests/simd.wast @@ -1,9 +1,6 @@ ;; Code tests taken from ;; https://github.com/WAVM/WAVM/blob/2b919c20a02624af9758e9ddd0b9b5726c973e4f/Test/simd.wast ;; -;; WAVM test spec license: Apache 2.0 -;; https://github.com/WAVM/WAVM/blob/2b919c20a02624af9758e9ddd0b9b5726c973e4f/Test/spec/LICENSE -;; ;; Modified by Wasmer to parse with the wabt spec tests parser and to pass with ;; Wasmer. diff --git a/lib/spectests/spectests/simd_binaryen.wast b/lib/spectests/spectests/simd_binaryen.wast index e9dd54a45..b838c11b1 100644 --- a/lib/spectests/spectests/simd_binaryen.wast +++ b/lib/spectests/spectests/simd_binaryen.wast @@ -1,9 +1,6 @@ ;; ;; https://github.com/WebAssembly/binaryen/blob/master/test/spec/simd.wast ;; -;; Distributed under the Apache License -;; https://github.com/WebAssembly/binaryen/blob/master/test/spec/LICENSE -;; ;; Modified by wasmer to work with the wabt parser and to pass with wasmer. ;; * replaced result negative nans with positive nans ;; * disabled min and max tests pending an update to LLVM diff --git a/lib/spectests/tests/excludes.txt b/lib/spectests/tests/excludes.txt index 5402157ed..a2742b0dc 100644 --- a/lib/spectests/tests/excludes.txt +++ b/lib/spectests/tests/excludes.txt @@ -1,3 +1,4 @@ + # Comment lines begin with # are ignored, empty lines are ignored # Exclude lines follow the format: # ::: @@ -11,10 +12,12 @@ # Star line allows skipping an entire wast file # clif:skip:simd.wast:* # -# Excludes can also contain platform +# Excludes can also contain target family # clif:skip:data.wast:172:windows # clif:skip:data.wast:172:unix # +# Or target arch +# singlepass:skip:atomic.wast:*:*:aarch64 # Cranelift clif:skip:atomic.wast:* # Threads not implemented @@ -23,7 +26,6 @@ clif:skip:simd_binaryen.wast:* # SIMD not implemented # linking.wast:387,388 appear to be related to WABT issue: https://github.com/pepyakin/wabt-rs/issues/51 -clif:fail:globals.wast:243 # AssertInvalid - Should be invalid clif:fail:linking.wast:137 # AssertTrap - expected trap, got Runtime:Error "unknown trap at 0x106883062 - illegal instruction" clif:fail:linking.wast:139 # AssertTrap - expected trap, got Runtime:Error "unknown trap at 0x106883062 - illegal instruction" clif:fail:linking.wast:142 # AssertTrap - expected trap, got Runtime:Error "unknown trap at 0x106883062 - illegal instruction" @@ -32,7 +34,6 @@ clif:fail:linking.wast:147 # AssertTrap - expected trap, got Runtime:Error "unkn clif:fail:linking.wast:149 # AssertTrap - expected trap, got Runtime:Error "unknown trap at 0x106883037 - illegal instruction" clif:fail:linking.wast:185 # AssertTrap - expected trap, got Runtime:Error "unknown trap at 0x106883062 - illegal instruction" clif:fail:linking.wast:187 # AssertTrap - expected trap, got Runtime:Error "unknown trap at 0x106883062 - illegal instruction" -clif:fail:linking.wast:387 # AssertReturn - result I32(0) ("0x0") does not match expected I32(104) ("0x68") clif:fail:linking.wast:388 # AssertReturn - Call failed RuntimeError: WebAssembly trap occurred during runtime: `call_indirect` out-of-bounds # clif:skip:skip-stack-guard-page.wast:2 # Slow test @@ -83,6 +84,7 @@ clif:fail:data.wast:227:windows # AssertUnlinkable - caught panic Any clif:fail:data.wast:258:windows # AssertUnlinkable - caught panic Any clif:fail:data.wast:273:windows # AssertUnlinkable - caught panic Any clif:fail:start.wast:92:windows # Module - caught panic Any +clif:skip:start.wast:98:windows clif:fail:align.wast:3:windows # Module - caught panic Any clif:fail:align.wast:4:windows # Module - caught panic Any @@ -264,577 +266,12 @@ clif:fail:data.wast:266:windows # AssertUnlinkable - caught panic Any clif:fail:data.wast:186:windows # AssertUnlinkable - caught panic Any clif:fail:data.wast:194:windows # AssertUnlinkable - caught panic Any -# LLVM bug with min/max over NaNs -llvm:skip:f32.wast:1651 -llvm:skip:f32.wast:1652 -llvm:skip:f32.wast:1653 -llvm:skip:f32.wast:1654 -llvm:skip:f32.wast:1655 -llvm:skip:f32.wast:1656 -llvm:skip:f32.wast:1657 -llvm:skip:f32.wast:1658 -llvm:skip:f32.wast:1691 -llvm:skip:f32.wast:1692 -llvm:skip:f32.wast:1693 -llvm:skip:f32.wast:1694 -llvm:skip:f32.wast:1695 -llvm:skip:f32.wast:1696 -llvm:skip:f32.wast:1697 -llvm:skip:f32.wast:1698 -llvm:skip:f32.wast:1731 -llvm:skip:f32.wast:1732 -llvm:skip:f32.wast:1733 -llvm:skip:f32.wast:1734 -llvm:skip:f32.wast:1735 -llvm:skip:f32.wast:1736 -llvm:skip:f32.wast:1737 -llvm:skip:f32.wast:1738 -llvm:skip:f32.wast:1771 -llvm:skip:f32.wast:1772 -llvm:skip:f32.wast:1773 -llvm:skip:f32.wast:1774 -llvm:skip:f32.wast:1775 -llvm:skip:f32.wast:1776 -llvm:skip:f32.wast:1777 -llvm:skip:f32.wast:1778 -llvm:skip:f32.wast:1811 -llvm:skip:f32.wast:1812 -llvm:skip:f32.wast:1813 -llvm:skip:f32.wast:1814 -llvm:skip:f32.wast:1815 -llvm:skip:f32.wast:1816 -llvm:skip:f32.wast:1817 -llvm:skip:f32.wast:1818 -llvm:skip:f32.wast:1851 -llvm:skip:f32.wast:1852 -llvm:skip:f32.wast:1853 -llvm:skip:f32.wast:1854 -llvm:skip:f32.wast:1855 -llvm:skip:f32.wast:1856 -llvm:skip:f32.wast:1857 -llvm:skip:f32.wast:1858 -llvm:skip:f32.wast:1891 -llvm:skip:f32.wast:1892 -llvm:skip:f32.wast:1893 -llvm:skip:f32.wast:1894 -llvm:skip:f32.wast:1895 -llvm:skip:f32.wast:1896 -llvm:skip:f32.wast:1897 -llvm:skip:f32.wast:1898 -llvm:skip:f32.wast:1931 -llvm:skip:f32.wast:1932 -llvm:skip:f32.wast:1933 -llvm:skip:f32.wast:1934 -llvm:skip:f32.wast:1935 -llvm:skip:f32.wast:1936 -llvm:skip:f32.wast:1937 -llvm:skip:f32.wast:1938 -llvm:skip:f32.wast:1939 -llvm:skip:f32.wast:1940 -llvm:skip:f32.wast:1941 -llvm:skip:f32.wast:1942 -llvm:skip:f32.wast:1943 -llvm:skip:f32.wast:1944 -llvm:skip:f32.wast:1945 -llvm:skip:f32.wast:1946 -llvm:skip:f32.wast:1947 -llvm:skip:f32.wast:1948 -llvm:skip:f32.wast:1949 -llvm:skip:f32.wast:1950 -llvm:skip:f32.wast:1951 -llvm:skip:f32.wast:1952 -llvm:skip:f32.wast:1953 -llvm:skip:f32.wast:1954 -llvm:skip:f32.wast:1955 -llvm:skip:f32.wast:1956 -llvm:skip:f32.wast:1957 -llvm:skip:f32.wast:1958 -llvm:skip:f32.wast:1959 -llvm:skip:f32.wast:1960 -llvm:skip:f32.wast:1961 -llvm:skip:f32.wast:1962 -llvm:skip:f32.wast:1963 -llvm:skip:f32.wast:1964 -llvm:skip:f32.wast:1965 -llvm:skip:f32.wast:1966 -llvm:skip:f32.wast:1967 -llvm:skip:f32.wast:1968 -llvm:skip:f32.wast:1969 -llvm:skip:f32.wast:1970 -llvm:skip:f32.wast:1971 -llvm:skip:f32.wast:1972 -llvm:skip:f32.wast:1973 -llvm:skip:f32.wast:1974 -llvm:skip:f32.wast:1975 -llvm:skip:f32.wast:1976 -llvm:skip:f32.wast:1977 -llvm:skip:f32.wast:1978 -llvm:skip:f32.wast:1979 -llvm:skip:f32.wast:1980 -llvm:skip:f32.wast:1981 -llvm:skip:f32.wast:1982 -llvm:skip:f32.wast:1983 -llvm:skip:f32.wast:1984 -llvm:skip:f32.wast:1985 -llvm:skip:f32.wast:1986 -llvm:skip:f32.wast:1987 -llvm:skip:f32.wast:1988 -llvm:skip:f32.wast:1989 -llvm:skip:f32.wast:1990 -llvm:skip:f32.wast:1991 -llvm:skip:f32.wast:1992 -llvm:skip:f32.wast:1993 -llvm:skip:f32.wast:1994 -llvm:skip:f32.wast:1995 -llvm:skip:f32.wast:1996 -llvm:skip:f32.wast:1997 -llvm:skip:f32.wast:1998 -llvm:skip:f32.wast:1999 -llvm:skip:f32.wast:2000 -llvm:skip:f32.wast:2001 -llvm:skip:f32.wast:2002 -llvm:skip:f32.wast:2005 -llvm:skip:f32.wast:2006 -llvm:skip:f32.wast:2009 -llvm:skip:f32.wast:2010 -llvm:skip:f32.wast:2013 -llvm:skip:f32.wast:2014 -llvm:skip:f32.wast:2017 -llvm:skip:f32.wast:2018 -llvm:skip:f32.wast:2051 -llvm:skip:f32.wast:2052 -llvm:skip:f32.wast:2053 -llvm:skip:f32.wast:2054 -llvm:skip:f32.wast:2055 -llvm:skip:f32.wast:2056 -llvm:skip:f32.wast:2057 -llvm:skip:f32.wast:2058 -llvm:skip:f32.wast:2091 -llvm:skip:f32.wast:2092 -llvm:skip:f32.wast:2093 -llvm:skip:f32.wast:2094 -llvm:skip:f32.wast:2095 -llvm:skip:f32.wast:2096 -llvm:skip:f32.wast:2097 -llvm:skip:f32.wast:2098 -llvm:skip:f32.wast:2131 -llvm:skip:f32.wast:2132 -llvm:skip:f32.wast:2133 -llvm:skip:f32.wast:2134 -llvm:skip:f32.wast:2135 -llvm:skip:f32.wast:2136 -llvm:skip:f32.wast:2137 -llvm:skip:f32.wast:2138 -llvm:skip:f32.wast:2171 -llvm:skip:f32.wast:2172 -llvm:skip:f32.wast:2173 -llvm:skip:f32.wast:2174 -llvm:skip:f32.wast:2175 -llvm:skip:f32.wast:2176 -llvm:skip:f32.wast:2177 -llvm:skip:f32.wast:2178 -llvm:skip:f32.wast:2211 -llvm:skip:f32.wast:2212 -llvm:skip:f32.wast:2213 -llvm:skip:f32.wast:2214 -llvm:skip:f32.wast:2215 -llvm:skip:f32.wast:2216 -llvm:skip:f32.wast:2217 -llvm:skip:f32.wast:2218 -llvm:skip:f32.wast:2251 -llvm:skip:f32.wast:2252 -llvm:skip:f32.wast:2253 -llvm:skip:f32.wast:2254 -llvm:skip:f32.wast:2255 -llvm:skip:f32.wast:2256 -llvm:skip:f32.wast:2257 -llvm:skip:f32.wast:2258 -llvm:skip:f32.wast:2291 -llvm:skip:f32.wast:2292 -llvm:skip:f32.wast:2293 -llvm:skip:f32.wast:2294 -llvm:skip:f32.wast:2295 -llvm:skip:f32.wast:2296 -llvm:skip:f32.wast:2297 -llvm:skip:f32.wast:2298 -llvm:skip:f32.wast:2331 -llvm:skip:f32.wast:2332 -llvm:skip:f32.wast:2333 -llvm:skip:f32.wast:2334 -llvm:skip:f32.wast:2335 -llvm:skip:f32.wast:2336 -llvm:skip:f32.wast:2337 -llvm:skip:f32.wast:2338 -llvm:skip:f32.wast:2339 -llvm:skip:f32.wast:2340 -llvm:skip:f32.wast:2341 -llvm:skip:f32.wast:2342 -llvm:skip:f32.wast:2343 -llvm:skip:f32.wast:2344 -llvm:skip:f32.wast:2345 -llvm:skip:f32.wast:2346 -llvm:skip:f32.wast:2347 -llvm:skip:f32.wast:2348 -llvm:skip:f32.wast:2349 -llvm:skip:f32.wast:2350 -llvm:skip:f32.wast:2351 -llvm:skip:f32.wast:2352 -llvm:skip:f32.wast:2353 -llvm:skip:f32.wast:2354 -llvm:skip:f32.wast:2355 -llvm:skip:f32.wast:2356 -llvm:skip:f32.wast:2357 -llvm:skip:f32.wast:2358 -llvm:skip:f32.wast:2359 -llvm:skip:f32.wast:2360 -llvm:skip:f32.wast:2361 -llvm:skip:f32.wast:2362 -llvm:skip:f32.wast:2363 -llvm:skip:f32.wast:2364 -llvm:skip:f32.wast:2365 -llvm:skip:f32.wast:2366 -llvm:skip:f32.wast:2367 -llvm:skip:f32.wast:2368 -llvm:skip:f32.wast:2369 -llvm:skip:f32.wast:2370 -llvm:skip:f32.wast:2371 -llvm:skip:f32.wast:2372 -llvm:skip:f32.wast:2373 -llvm:skip:f32.wast:2374 -llvm:skip:f32.wast:2375 -llvm:skip:f32.wast:2376 -llvm:skip:f32.wast:2377 -llvm:skip:f32.wast:2378 -llvm:skip:f32.wast:2379 -llvm:skip:f32.wast:2380 -llvm:skip:f32.wast:2381 -llvm:skip:f32.wast:2382 -llvm:skip:f32.wast:2383 -llvm:skip:f32.wast:2384 -llvm:skip:f32.wast:2385 -llvm:skip:f32.wast:2386 -llvm:skip:f32.wast:2387 -llvm:skip:f32.wast:2388 -llvm:skip:f32.wast:2389 -llvm:skip:f32.wast:2390 -llvm:skip:f32.wast:2391 -llvm:skip:f32.wast:2392 -llvm:skip:f32.wast:2393 -llvm:skip:f32.wast:2394 -llvm:skip:f32.wast:2395 -llvm:skip:f32.wast:2396 -llvm:skip:f32.wast:2397 -llvm:skip:f32.wast:2398 -llvm:skip:f32.wast:2399 -llvm:skip:f32.wast:2400 -llvm:skip:f32.wast:2401 -llvm:skip:f32.wast:2402 -llvm:skip:f32.wast:2403 -llvm:skip:f32.wast:2404 -llvm:skip:f32.wast:2405 -llvm:skip:f32.wast:2406 -llvm:skip:f32.wast:2409 -llvm:skip:f32.wast:2410 -llvm:skip:f32.wast:2413 -llvm:skip:f32.wast:2414 -llvm:skip:f32.wast:2417 -llvm:skip:f32.wast:2418 -llvm:skip:f64.wast:1651 -llvm:skip:f64.wast:1652 -llvm:skip:f64.wast:1653 -llvm:skip:f64.wast:1654 -llvm:skip:f64.wast:1655 -llvm:skip:f64.wast:1656 -llvm:skip:f64.wast:1657 -llvm:skip:f64.wast:1658 -llvm:skip:f64.wast:1691 -llvm:skip:f64.wast:1692 -llvm:skip:f64.wast:1693 -llvm:skip:f64.wast:1694 -llvm:skip:f64.wast:1695 -llvm:skip:f64.wast:1696 -llvm:skip:f64.wast:1697 -llvm:skip:f64.wast:1698 -llvm:skip:f64.wast:1731 -llvm:skip:f64.wast:1732 -llvm:skip:f64.wast:1733 -llvm:skip:f64.wast:1734 -llvm:skip:f64.wast:1735 -llvm:skip:f64.wast:1736 -llvm:skip:f64.wast:1737 -llvm:skip:f64.wast:1738 -llvm:skip:f64.wast:1771 -llvm:skip:f64.wast:1772 -llvm:skip:f64.wast:1773 -llvm:skip:f64.wast:1774 -llvm:skip:f64.wast:1775 -llvm:skip:f64.wast:1776 -llvm:skip:f64.wast:1777 -llvm:skip:f64.wast:1778 -llvm:skip:f64.wast:1811 -llvm:skip:f64.wast:1812 -llvm:skip:f64.wast:1813 -llvm:skip:f64.wast:1814 -llvm:skip:f64.wast:1815 -llvm:skip:f64.wast:1816 -llvm:skip:f64.wast:1817 -llvm:skip:f64.wast:1818 -llvm:skip:f64.wast:1851 -llvm:skip:f64.wast:1852 -llvm:skip:f64.wast:1853 -llvm:skip:f64.wast:1854 -llvm:skip:f64.wast:1855 -llvm:skip:f64.wast:1856 -llvm:skip:f64.wast:1857 -llvm:skip:f64.wast:1858 -llvm:skip:f64.wast:1891 -llvm:skip:f64.wast:1892 -llvm:skip:f64.wast:1893 -llvm:skip:f64.wast:1894 -llvm:skip:f64.wast:1895 -llvm:skip:f64.wast:1896 -llvm:skip:f64.wast:1897 -llvm:skip:f64.wast:1898 -llvm:skip:f64.wast:1931 -llvm:skip:f64.wast:1932 -llvm:skip:f64.wast:1933 -llvm:skip:f64.wast:1934 -llvm:skip:f64.wast:1935 -llvm:skip:f64.wast:1936 -llvm:skip:f64.wast:1937 -llvm:skip:f64.wast:1938 -llvm:skip:f64.wast:1939 -llvm:skip:f64.wast:1940 -llvm:skip:f64.wast:1941 -llvm:skip:f64.wast:1942 -llvm:skip:f64.wast:1943 -llvm:skip:f64.wast:1944 -llvm:skip:f64.wast:1945 -llvm:skip:f64.wast:1946 -llvm:skip:f64.wast:1947 -llvm:skip:f64.wast:1948 -llvm:skip:f64.wast:1949 -llvm:skip:f64.wast:1950 -llvm:skip:f64.wast:1951 -llvm:skip:f64.wast:1952 -llvm:skip:f64.wast:1953 -llvm:skip:f64.wast:1954 -llvm:skip:f64.wast:1955 -llvm:skip:f64.wast:1956 -llvm:skip:f64.wast:1957 -llvm:skip:f64.wast:1958 -llvm:skip:f64.wast:1959 -llvm:skip:f64.wast:1960 -llvm:skip:f64.wast:1961 -llvm:skip:f64.wast:1962 -llvm:skip:f64.wast:1963 -llvm:skip:f64.wast:1964 -llvm:skip:f64.wast:1965 -llvm:skip:f64.wast:1966 -llvm:skip:f64.wast:1967 -llvm:skip:f64.wast:1968 -llvm:skip:f64.wast:1969 -llvm:skip:f64.wast:1970 -llvm:skip:f64.wast:1971 -llvm:skip:f64.wast:1972 -llvm:skip:f64.wast:1973 -llvm:skip:f64.wast:1974 -llvm:skip:f64.wast:1975 -llvm:skip:f64.wast:1976 -llvm:skip:f64.wast:1977 -llvm:skip:f64.wast:1978 -llvm:skip:f64.wast:1979 -llvm:skip:f64.wast:1980 -llvm:skip:f64.wast:1981 -llvm:skip:f64.wast:1982 -llvm:skip:f64.wast:1983 -llvm:skip:f64.wast:1984 -llvm:skip:f64.wast:1985 -llvm:skip:f64.wast:1986 -llvm:skip:f64.wast:1987 -llvm:skip:f64.wast:1988 -llvm:skip:f64.wast:1989 -llvm:skip:f64.wast:1990 -llvm:skip:f64.wast:1991 -llvm:skip:f64.wast:1992 -llvm:skip:f64.wast:1993 -llvm:skip:f64.wast:1994 -llvm:skip:f64.wast:1995 -llvm:skip:f64.wast:1996 -llvm:skip:f64.wast:1997 -llvm:skip:f64.wast:1998 -llvm:skip:f64.wast:1999 -llvm:skip:f64.wast:2000 -llvm:skip:f64.wast:2001 -llvm:skip:f64.wast:2002 -llvm:skip:f64.wast:2005 -llvm:skip:f64.wast:2006 -llvm:skip:f64.wast:2009 -llvm:skip:f64.wast:2010 -llvm:skip:f64.wast:2013 -llvm:skip:f64.wast:2014 -llvm:skip:f64.wast:2017 -llvm:skip:f64.wast:2018 -llvm:skip:f64.wast:2051 -llvm:skip:f64.wast:2052 -llvm:skip:f64.wast:2053 -llvm:skip:f64.wast:2054 -llvm:skip:f64.wast:2055 -llvm:skip:f64.wast:2056 -llvm:skip:f64.wast:2057 -llvm:skip:f64.wast:2058 -llvm:skip:f64.wast:2091 -llvm:skip:f64.wast:2092 -llvm:skip:f64.wast:2093 -llvm:skip:f64.wast:2094 -llvm:skip:f64.wast:2095 -llvm:skip:f64.wast:2096 -llvm:skip:f64.wast:2097 -llvm:skip:f64.wast:2098 -llvm:skip:f64.wast:2131 -llvm:skip:f64.wast:2132 -llvm:skip:f64.wast:2133 -llvm:skip:f64.wast:2134 -llvm:skip:f64.wast:2135 -llvm:skip:f64.wast:2136 -llvm:skip:f64.wast:2137 -llvm:skip:f64.wast:2138 -llvm:skip:f64.wast:2171 -llvm:skip:f64.wast:2172 -llvm:skip:f64.wast:2173 -llvm:skip:f64.wast:2174 -llvm:skip:f64.wast:2175 -llvm:skip:f64.wast:2176 -llvm:skip:f64.wast:2177 -llvm:skip:f64.wast:2178 -llvm:skip:f64.wast:2211 -llvm:skip:f64.wast:2212 -llvm:skip:f64.wast:2213 -llvm:skip:f64.wast:2214 -llvm:skip:f64.wast:2215 -llvm:skip:f64.wast:2216 -llvm:skip:f64.wast:2217 -llvm:skip:f64.wast:2218 -llvm:skip:f64.wast:2251 -llvm:skip:f64.wast:2252 -llvm:skip:f64.wast:2253 -llvm:skip:f64.wast:2254 -llvm:skip:f64.wast:2255 -llvm:skip:f64.wast:2256 -llvm:skip:f64.wast:2257 -llvm:skip:f64.wast:2258 -llvm:skip:f64.wast:2291 -llvm:skip:f64.wast:2292 -llvm:skip:f64.wast:2293 -llvm:skip:f64.wast:2294 -llvm:skip:f64.wast:2295 -llvm:skip:f64.wast:2296 -llvm:skip:f64.wast:2297 -llvm:skip:f64.wast:2298 -llvm:skip:f64.wast:2331 -llvm:skip:f64.wast:2332 -llvm:skip:f64.wast:2333 -llvm:skip:f64.wast:2334 -llvm:skip:f64.wast:2335 -llvm:skip:f64.wast:2336 -llvm:skip:f64.wast:2337 -llvm:skip:f64.wast:2338 -llvm:skip:f64.wast:2339 -llvm:skip:f64.wast:2340 -llvm:skip:f64.wast:2341 -llvm:skip:f64.wast:2342 -llvm:skip:f64.wast:2343 -llvm:skip:f64.wast:2344 -llvm:skip:f64.wast:2345 -llvm:skip:f64.wast:2346 -llvm:skip:f64.wast:2347 -llvm:skip:f64.wast:2348 -llvm:skip:f64.wast:2349 -llvm:skip:f64.wast:2350 -llvm:skip:f64.wast:2351 -llvm:skip:f64.wast:2352 -llvm:skip:f64.wast:2353 -llvm:skip:f64.wast:2354 -llvm:skip:f64.wast:2355 -llvm:skip:f64.wast:2356 -llvm:skip:f64.wast:2357 -llvm:skip:f64.wast:2358 -llvm:skip:f64.wast:2359 -llvm:skip:f64.wast:2360 -llvm:skip:f64.wast:2361 -llvm:skip:f64.wast:2362 -llvm:skip:f64.wast:2363 -llvm:skip:f64.wast:2364 -llvm:skip:f64.wast:2365 -llvm:skip:f64.wast:2366 -llvm:skip:f64.wast:2367 -llvm:skip:f64.wast:2368 -llvm:skip:f64.wast:2369 -llvm:skip:f64.wast:2370 -llvm:skip:f64.wast:2371 -llvm:skip:f64.wast:2372 -llvm:skip:f64.wast:2373 -llvm:skip:f64.wast:2374 -llvm:skip:f64.wast:2375 -llvm:skip:f64.wast:2376 -llvm:skip:f64.wast:2377 -llvm:skip:f64.wast:2378 -llvm:skip:f64.wast:2379 -llvm:skip:f64.wast:2380 -llvm:skip:f64.wast:2381 -llvm:skip:f64.wast:2382 -llvm:skip:f64.wast:2383 -llvm:skip:f64.wast:2384 -llvm:skip:f64.wast:2385 -llvm:skip:f64.wast:2386 -llvm:skip:f64.wast:2387 -llvm:skip:f64.wast:2388 -llvm:skip:f64.wast:2389 -llvm:skip:f64.wast:2390 -llvm:skip:f64.wast:2391 -llvm:skip:f64.wast:2392 -llvm:skip:f64.wast:2393 -llvm:skip:f64.wast:2394 -llvm:skip:f64.wast:2395 -llvm:skip:f64.wast:2396 -llvm:skip:f64.wast:2397 -llvm:skip:f64.wast:2398 -llvm:skip:f64.wast:2399 -llvm:skip:f64.wast:2400 -llvm:skip:f64.wast:2401 -llvm:skip:f64.wast:2402 -llvm:skip:f64.wast:2405 -llvm:skip:f64.wast:2406 -llvm:skip:f64.wast:2409 -llvm:skip:f64.wast:2410 -llvm:skip:f64.wast:2413 -llvm:skip:f64.wast:2414 -llvm:skip:f64.wast:2417 -llvm:skip:f64.wast:2418 - # LLVM -llvm:skip:br_table.wast:1255 -llvm:skip:imports.wast:391 # Running forever -llvm:skip:imports.wast:402 # Running forever -llvm:skip:call.wast:273 # Spec running forever -llvm:skip:call_indirect.wast:556 # Spec running forever -llvm:skip:call_indirect.wast:557 # Spec running forever -llvm:skip:fac.wast:89 # Spec running forever -llvm:skip:skip-stack-guard-page.wast:* # Spec running forever or (signal: 4, SIGILL: illegal instruction) -llvm:skip:linking.wast:236 # terminating with uncaught exception of type WasmTrap -llvm:skip:linking.wast:248 # terminating with uncaught exception of type WasmTrap - llvm:fail:f32.wast:1621 # AssertReturn - result F32(0) ("0x0") does not match expected F32(2147483648) ("0x80000000") llvm:fail:f32.wast:2020 # AssertReturn - result F32(2147483648) ("0x80000000") does not match expected F32(0) ("0x0") llvm:fail:f64.wast:1621 # AssertReturn - result F64(0) ("0x0") does not match expected F64(9223372036854775808) ("0x8000000000000000") llvm:fail:f64.wast:2020 # AssertReturn - result F64(9223372036854775808) ("0x8000000000000000") does not match expected F64(0) ("0x0") -llvm:fail:i32.wast:243 # AssertReturn - result I32(283816271) ("0x10eab14f") does not match expected I32(32) ("0x20") -llvm:fail:i32.wast:252 # AssertReturn - result I32(283816288) ("0x10eab160") does not match expected I32(32) ("0x20") -llvm:fail:i64.wast:243 # AssertReturn - result I64(4578783727) ("0x110eab1ef") does not match expected I64(64) ("0x40") -llvm:fail:i64.wast:252 # AssertReturn - result I64(4578783712) ("0x110eab1e0") does not match expected I64(64) ("0x40") -llvm:fail:linking.wast:387 # AssertReturn - result I32(0) ("0x0") does not match expected I32(104) ("0x68") llvm:fail:linking.wast:388 # AssertReturn - Call failed RuntimeError: WebAssembly trap occurred during runtime: incorrect `call_indirect` signature -llvm:fail:load.wast:201 # AssertReturn - result I32(285315103) ("0x1101901f") does not match expected I32(32) ("0x20") # LLVM Windows llvm:skip:address.wast:*:windows @@ -860,11 +297,17 @@ llvm:skip:traps.wast:*:windows llvm:skip:unreachable.wast:*:windows llvm:skip:unwind.wast:*:windows +# LLVM Linux after OSR - https://github.com/wasmerio/wasmer/pull/567 +llvm:skip:simd.wast:352:unix # Module - caught panic Any +llvm:skip:simd_binaryen.wast:*:unix # Module - caught panic Any + + # Singlepass -singlepass:skip:atomic.wast:* # Threads not implemented singlepass:skip:simd.wast:* # SIMD not implemented singlepass:skip:simd_binaryen.wast:* # SIMD not implemented +singlepass:skip:atomic.wast:*:*:aarch64 # Threads not yet supported on singlepass + singlepass:fail:address.wast:192 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:address.wast:194 # AssertTrap - expected trap, got [] singlepass:fail:address.wast:195 # AssertTrap - expected trap, got [] @@ -898,6 +341,51 @@ singlepass:fail:address.wast:586 # AssertTrap - expected trap, got Runtime:Error singlepass:fail:address.wast:588 # AssertTrap - expected trap, got [] singlepass:fail:address.wast:589 # AssertTrap - expected trap, got [] singlepass:fail:align.wast:864 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:380 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:381 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:382 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:383 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:384 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:385 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:386 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:387 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:388 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:389 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:390 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:391 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:392 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:393 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:394 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:395 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:396 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:397 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:398 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:399 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:400 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:401 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:402 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:403 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:404 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:405 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:406 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:407 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:408 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:409 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:410 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:411 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:412 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:413 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:414 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:415 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:416 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:417 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:418 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:419 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:420 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:421 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:422 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:423 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:atomic.wast:424 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:call.wast:289 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:call_indirect.wast:469 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:call_indirect.wast:470 # AssertTrap - expected trap, got Runtime:Error unknown error @@ -912,494 +400,74 @@ singlepass:fail:call_indirect.wast:493 # AssertTrap - expected trap, got Runtime singlepass:fail:call_indirect.wast:494 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:call_indirect.wast:500 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:call_indirect.wast:501 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:70 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:71 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:72 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:73 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:74 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:75 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:76 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:77 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:92 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:93 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:94 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:95 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:96 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:78 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:79 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:80 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:81 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:82 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:97 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:98 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:99 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:115 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:116 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:117 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:118 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:119 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:100 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:101 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:102 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:103 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:104 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:120 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:121 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:122 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:138 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:139 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:140 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:141 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:142 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:123 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:124 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:125 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:126 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:127 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:143 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:144 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:145 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:146 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:147 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:148 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:166 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:167 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:168 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:169 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:170 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:149 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:150 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:151 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:152 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:153 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:171 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:172 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:173 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:186 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:187 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:188 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:189 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:190 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:174 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:175 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:176 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:177 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:178 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:191 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:192 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:193 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:211 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:212 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:213 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:214 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:215 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:194 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:195 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:196 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:197 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:198 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:216 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:217 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:218 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:235 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:236 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:237 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:238 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:conversions.wast:239 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:219 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:220 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:221 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:222 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:223 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:240 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:241 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:conversions.wast:242 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:243 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:244 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:245 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:246 # AssertTrap - expected trap, got Runtime:Error unknown error +singlepass:fail:conversions.wast:247 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:elem.wast:353 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:f32.wast:1620 # AssertReturn - result F32(0) ("0x0") does not match expected F32(2147483648) ("0x80000000") -singlepass:fail:f32.wast:1652 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1654 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1656 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1658 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1692 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1694 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1696 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1698 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1732 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1734 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1736 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1738 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1772 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1774 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1776 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1778 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1812 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1814 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1816 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1818 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1852 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1854 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1856 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1858 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1892 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1894 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1896 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1898 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1932 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1934 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1936 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1938 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:1939 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.0) -singlepass:fail:f32.wast:1940 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.0) -singlepass:fail:f32.wast:1941 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.0) -singlepass:fail:f32.wast:1942 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.0) -singlepass:fail:f32.wast:1943 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.0) -singlepass:fail:f32.wast:1944 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.0) -singlepass:fail:f32.wast:1945 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.0) -singlepass:fail:f32.wast:1946 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.0) -singlepass:fail:f32.wast:1947 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:1948 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:1949 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:1950 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:1951 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:1952 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:1953 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:1954 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:1955 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:1956 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:1957 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:1958 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:1959 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:1960 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:1961 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:1962 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:1963 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.5) -singlepass:fail:f32.wast:1964 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.5) -singlepass:fail:f32.wast:1965 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.5) -singlepass:fail:f32.wast:1966 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.5) -singlepass:fail:f32.wast:1967 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.5) -singlepass:fail:f32.wast:1968 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.5) -singlepass:fail:f32.wast:1969 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.5) -singlepass:fail:f32.wast:1970 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.5) -singlepass:fail:f32.wast:1971 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-1.0) -singlepass:fail:f32.wast:1972 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-1.0) -singlepass:fail:f32.wast:1973 # "AssertReturnCanonicalNan" - value is not canonical nan F32(1.0) -singlepass:fail:f32.wast:1974 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(1.0) -singlepass:fail:f32.wast:1975 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-1.0) -singlepass:fail:f32.wast:1976 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-1.0) -singlepass:fail:f32.wast:1977 # "AssertReturnCanonicalNan" - value is not canonical nan F32(1.0) -singlepass:fail:f32.wast:1978 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(1.0) -singlepass:fail:f32.wast:1979 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-6.2831855) -singlepass:fail:f32.wast:1980 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-6.2831855) -singlepass:fail:f32.wast:1981 # "AssertReturnCanonicalNan" - value is not canonical nan F32(6.2831855) -singlepass:fail:f32.wast:1982 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(6.2831855) -singlepass:fail:f32.wast:1983 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-6.2831855) -singlepass:fail:f32.wast:1984 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-6.2831855) -singlepass:fail:f32.wast:1985 # "AssertReturnCanonicalNan" - value is not canonical nan F32(6.2831855) -singlepass:fail:f32.wast:1986 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(6.2831855) -singlepass:fail:f32.wast:1987 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:1988 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:1989 # "AssertReturnCanonicalNan" - value is not canonical nan F32(340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:1990 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:1991 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:1992 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:1993 # "AssertReturnCanonicalNan" - value is not canonical nan F32(340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:1994 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:1995 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-inf) -singlepass:fail:f32.wast:1996 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-inf) -singlepass:fail:f32.wast:1997 # "AssertReturnCanonicalNan" - value is not canonical nan F32(inf) -singlepass:fail:f32.wast:1998 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(inf) -singlepass:fail:f32.wast:1999 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-inf) -singlepass:fail:f32.wast:2000 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-inf) -singlepass:fail:f32.wast:2001 # "AssertReturnCanonicalNan" - value is not canonical nan F32(inf) -singlepass:fail:f32.wast:2002 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(inf) -singlepass:fail:f32.wast:2005 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2006 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2009 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2010 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2013 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2014 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2017 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2018 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2021 # AssertReturn - result F32(2147483648) ("0x80000000") does not match expected F32(0) ("0x0") -singlepass:fail:f32.wast:2052 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2054 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2056 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2058 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2092 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2094 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2096 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2098 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2132 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2134 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2136 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2138 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2172 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2174 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2176 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2178 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2212 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2214 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2216 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2218 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2252 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2254 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2256 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2258 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2292 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2294 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2296 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2298 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2332 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2334 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2336 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2338 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2339 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.0) -singlepass:fail:f32.wast:2340 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.0) -singlepass:fail:f32.wast:2341 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.0) -singlepass:fail:f32.wast:2342 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.0) -singlepass:fail:f32.wast:2343 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.0) -singlepass:fail:f32.wast:2344 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.0) -singlepass:fail:f32.wast:2345 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.0) -singlepass:fail:f32.wast:2346 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.0) -singlepass:fail:f32.wast:2347 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:2348 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:2349 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:2350 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:2351 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:2352 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:2353 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:2354 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.000000000000000000000000000000000000000000001) -singlepass:fail:f32.wast:2355 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:2356 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:2357 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:2358 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:2359 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:2360 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:2361 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:2362 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.000000000000000000000000000000000000011754944) -singlepass:fail:f32.wast:2363 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.5) -singlepass:fail:f32.wast:2364 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.5) -singlepass:fail:f32.wast:2365 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.5) -singlepass:fail:f32.wast:2366 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.5) -singlepass:fail:f32.wast:2367 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-0.5) -singlepass:fail:f32.wast:2368 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-0.5) -singlepass:fail:f32.wast:2369 # "AssertReturnCanonicalNan" - value is not canonical nan F32(0.5) -singlepass:fail:f32.wast:2370 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(0.5) -singlepass:fail:f32.wast:2371 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-1.0) -singlepass:fail:f32.wast:2372 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-1.0) -singlepass:fail:f32.wast:2373 # "AssertReturnCanonicalNan" - value is not canonical nan F32(1.0) -singlepass:fail:f32.wast:2374 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(1.0) -singlepass:fail:f32.wast:2375 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-1.0) -singlepass:fail:f32.wast:2376 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-1.0) -singlepass:fail:f32.wast:2377 # "AssertReturnCanonicalNan" - value is not canonical nan F32(1.0) -singlepass:fail:f32.wast:2378 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(1.0) -singlepass:fail:f32.wast:2379 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-6.2831855) -singlepass:fail:f32.wast:2380 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-6.2831855) -singlepass:fail:f32.wast:2381 # "AssertReturnCanonicalNan" - value is not canonical nan F32(6.2831855) -singlepass:fail:f32.wast:2382 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(6.2831855) -singlepass:fail:f32.wast:2383 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-6.2831855) -singlepass:fail:f32.wast:2384 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-6.2831855) -singlepass:fail:f32.wast:2385 # "AssertReturnCanonicalNan" - value is not canonical nan F32(6.2831855) -singlepass:fail:f32.wast:2386 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(6.2831855) -singlepass:fail:f32.wast:2387 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:2388 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:2389 # "AssertReturnCanonicalNan" - value is not canonical nan F32(340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:2390 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:2391 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:2392 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:2393 # "AssertReturnCanonicalNan" - value is not canonical nan F32(340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:2394 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(340282350000000000000000000000000000000.0) -singlepass:fail:f32.wast:2395 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-inf) -singlepass:fail:f32.wast:2396 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-inf) -singlepass:fail:f32.wast:2397 # "AssertReturnCanonicalNan" - value is not canonical nan F32(inf) -singlepass:fail:f32.wast:2398 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(inf) -singlepass:fail:f32.wast:2399 # "AssertReturnCanonicalNan" - value is not canonical nan F32(-inf) -singlepass:fail:f32.wast:2400 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(-inf) -singlepass:fail:f32.wast:2401 # "AssertReturnCanonicalNan" - value is not canonical nan F32(inf) -singlepass:fail:f32.wast:2402 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(inf) -singlepass:fail:f32.wast:2405 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2406 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2409 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2410 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2413 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2414 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2417 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f32.wast:2418 # "AssertReturnArithmeticNan" - value is not arithmetic nan F32(NaN) -singlepass:fail:f64.wast:1620 # AssertReturn - result F64(0) ("0x0") does not match expected F64(9223372036854775808) ("0x8000000000000000") -singlepass:fail:f64.wast:1652 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1654 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1656 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1658 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1692 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1694 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1696 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1698 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1732 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1734 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1736 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1738 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1772 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1774 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1776 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1778 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1812 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1814 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1816 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1818 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1852 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1854 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1856 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1858 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1892 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1894 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1896 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1898 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1932 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1934 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1936 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1938 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:1939 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.0) -singlepass:fail:f64.wast:1940 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.0) -singlepass:fail:f64.wast:1941 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.0) -singlepass:fail:f64.wast:1942 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.0) -singlepass:fail:f64.wast:1943 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.0) -singlepass:fail:f64.wast:1944 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.0) -singlepass:fail:f64.wast:1945 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.0) -singlepass:fail:f64.wast:1946 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.0) -singlepass:fail:f64.wast:1947 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:1948 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:1949 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:1950 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:1951 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:1952 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:1953 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:1954 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:1955 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:1956 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:1957 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:1958 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:1959 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:1960 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:1961 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:1962 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:1963 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.5) -singlepass:fail:f64.wast:1964 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.5) -singlepass:fail:f64.wast:1965 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.5) -singlepass:fail:f64.wast:1966 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.5) -singlepass:fail:f64.wast:1967 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.5) -singlepass:fail:f64.wast:1968 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.5) -singlepass:fail:f64.wast:1969 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.5) -singlepass:fail:f64.wast:1970 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.5) -singlepass:fail:f64.wast:1971 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-1.0) -singlepass:fail:f64.wast:1972 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-1.0) -singlepass:fail:f64.wast:1973 # "AssertReturnCanonicalNan" - value is not canonical nan F64(1.0) -singlepass:fail:f64.wast:1974 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(1.0) -singlepass:fail:f64.wast:1975 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-1.0) -singlepass:fail:f64.wast:1976 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-1.0) -singlepass:fail:f64.wast:1977 # "AssertReturnCanonicalNan" - value is not canonical nan F64(1.0) -singlepass:fail:f64.wast:1978 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(1.0) -singlepass:fail:f64.wast:1979 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-6.283185307179586) -singlepass:fail:f64.wast:1980 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-6.283185307179586) -singlepass:fail:f64.wast:1981 # "AssertReturnCanonicalNan" - value is not canonical nan F64(6.283185307179586) -singlepass:fail:f64.wast:1982 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(6.283185307179586) -singlepass:fail:f64.wast:1983 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-6.283185307179586) -singlepass:fail:f64.wast:1984 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-6.283185307179586) -singlepass:fail:f64.wast:1985 # "AssertReturnCanonicalNan" - value is not canonical nan F64(6.283185307179586) -singlepass:fail:f64.wast:1986 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(6.283185307179586) -singlepass:fail:f64.wast:1987 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:1988 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:1989 # "AssertReturnCanonicalNan" - value is not canonical nan F64(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:1990 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:1991 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:1992 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:1993 # "AssertReturnCanonicalNan" - value is not canonical nan F64(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:1994 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:1995 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-inf) -singlepass:fail:f64.wast:1996 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-inf) -singlepass:fail:f64.wast:1997 # "AssertReturnCanonicalNan" - value is not canonical nan F64(inf) -singlepass:fail:f64.wast:1998 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(inf) -singlepass:fail:f64.wast:1999 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-inf) -singlepass:fail:f64.wast:2000 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-inf) -singlepass:fail:f64.wast:2001 # "AssertReturnCanonicalNan" - value is not canonical nan F64(inf) -singlepass:fail:f64.wast:2002 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(inf) -singlepass:fail:f64.wast:2005 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2006 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2009 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2010 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2013 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2014 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2017 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2018 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2021 # AssertReturn - result F64(9223372036854775808) ("0x8000000000000000") does not match expected F64(0) ("0x0") -singlepass:fail:f64.wast:2052 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2054 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2056 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2058 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2092 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2094 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2096 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2098 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2132 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2134 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2136 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2138 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2172 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2174 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2176 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2178 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2212 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2214 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2216 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2218 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2252 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2254 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2256 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2258 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2292 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2294 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2296 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2298 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2332 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2334 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2336 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2338 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2339 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.0) -singlepass:fail:f64.wast:2340 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.0) -singlepass:fail:f64.wast:2341 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.0) -singlepass:fail:f64.wast:2342 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.0) -singlepass:fail:f64.wast:2343 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.0) -singlepass:fail:f64.wast:2344 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.0) -singlepass:fail:f64.wast:2345 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.0) -singlepass:fail:f64.wast:2346 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.0) -singlepass:fail:f64.wast:2347 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:2348 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:2349 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:2350 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:2351 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:2352 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:2353 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:2354 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005) -singlepass:fail:f64.wast:2355 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:2356 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:2357 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:2358 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:2359 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:2360 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:2361 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:2362 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022250738585072014) -singlepass:fail:f64.wast:2363 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.5) -singlepass:fail:f64.wast:2364 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.5) -singlepass:fail:f64.wast:2365 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.5) -singlepass:fail:f64.wast:2366 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.5) -singlepass:fail:f64.wast:2367 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-0.5) -singlepass:fail:f64.wast:2368 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-0.5) -singlepass:fail:f64.wast:2369 # "AssertReturnCanonicalNan" - value is not canonical nan F64(0.5) -singlepass:fail:f64.wast:2370 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(0.5) -singlepass:fail:f64.wast:2371 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-1.0) -singlepass:fail:f64.wast:2372 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-1.0) -singlepass:fail:f64.wast:2373 # "AssertReturnCanonicalNan" - value is not canonical nan F64(1.0) -singlepass:fail:f64.wast:2374 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(1.0) -singlepass:fail:f64.wast:2375 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-1.0) -singlepass:fail:f64.wast:2376 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-1.0) -singlepass:fail:f64.wast:2377 # "AssertReturnCanonicalNan" - value is not canonical nan F64(1.0) -singlepass:fail:f64.wast:2378 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(1.0) -singlepass:fail:f64.wast:2379 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-6.283185307179586) -singlepass:fail:f64.wast:2380 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-6.283185307179586) -singlepass:fail:f64.wast:2381 # "AssertReturnCanonicalNan" - value is not canonical nan F64(6.283185307179586) -singlepass:fail:f64.wast:2382 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(6.283185307179586) -singlepass:fail:f64.wast:2383 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-6.283185307179586) -singlepass:fail:f64.wast:2384 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-6.283185307179586) -singlepass:fail:f64.wast:2385 # "AssertReturnCanonicalNan" - value is not canonical nan F64(6.283185307179586) -singlepass:fail:f64.wast:2386 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(6.283185307179586) -singlepass:fail:f64.wast:2387 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:2388 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:2389 # "AssertReturnCanonicalNan" - value is not canonical nan F64(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:2390 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:2391 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:2392 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:2393 # "AssertReturnCanonicalNan" - value is not canonical nan F64(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:2394 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0) -singlepass:fail:f64.wast:2395 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-inf) -singlepass:fail:f64.wast:2396 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-inf) -singlepass:fail:f64.wast:2397 # "AssertReturnCanonicalNan" - value is not canonical nan F64(inf) -singlepass:fail:f64.wast:2398 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(inf) -singlepass:fail:f64.wast:2399 # "AssertReturnCanonicalNan" - value is not canonical nan F64(-inf) -singlepass:fail:f64.wast:2400 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(-inf) -singlepass:fail:f64.wast:2401 # "AssertReturnCanonicalNan" - value is not canonical nan F64(inf) -singlepass:fail:f64.wast:2402 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(inf) -singlepass:fail:f64.wast:2405 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2406 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2409 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2410 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2413 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2414 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2417 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) -singlepass:fail:f64.wast:2418 # "AssertReturnArithmeticNan" - value is not arithmetic nan F64(NaN) singlepass:fail:func_ptrs.wast:78 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:func_ptrs.wast:79 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:func_ptrs.wast:80 # AssertTrap - expected trap, got Runtime:Error unknown error @@ -1417,15 +485,6 @@ singlepass:fail:i32.wast:99 # AssertTrap - expected trap, got Runtime:Error unkn singlepass:fail:i32.wast:100 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:i32.wast:120 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:i32.wast:121 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:i32.wast:242 # AssertReturn - result I32(31) ("0x1f") does not match expected I32(0) ("0x0") -singlepass:fail:i32.wast:243 # AssertReturn - result I32(0) ("0x0") does not match expected I32(32) ("0x20") -singlepass:fail:i32.wast:244 # AssertReturn - result I32(15) ("0xf") does not match expected I32(16) ("0x10") -singlepass:fail:i32.wast:245 # AssertReturn - result I32(7) ("0x7") does not match expected I32(24) ("0x18") -singlepass:fail:i32.wast:246 # AssertReturn - result I32(31) ("0x1f") does not match expected I32(0) ("0x0") -singlepass:fail:i32.wast:247 # AssertReturn - result I32(0) ("0x0") does not match expected I32(31) ("0x1f") -singlepass:fail:i32.wast:248 # AssertReturn - result I32(1) ("0x1") does not match expected I32(30) ("0x1e") -singlepass:fail:i32.wast:249 # AssertReturn - result I32(30) ("0x1e") does not match expected I32(1) ("0x1") -singlepass:fail:i32.wast:252 # AssertReturn - result I32(0) ("0x0") does not match expected I32(32) ("0x20") singlepass:fail:i64.wast:62 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:i64.wast:63 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:i64.wast:64 # AssertTrap - expected trap, got Runtime:Error unknown error @@ -1435,15 +494,6 @@ singlepass:fail:i64.wast:99 # AssertTrap - expected trap, got Runtime:Error unkn singlepass:fail:i64.wast:100 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:i64.wast:120 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:i64.wast:121 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:i64.wast:242 # AssertReturn - result I64(63) ("0x3f") does not match expected I64(0) ("0x0") -singlepass:fail:i64.wast:243 # AssertReturn - result I64(0) ("0x0") does not match expected I64(64) ("0x40") -singlepass:fail:i64.wast:244 # AssertReturn - result I64(15) ("0xf") does not match expected I64(48) ("0x30") -singlepass:fail:i64.wast:245 # AssertReturn - result I64(7) ("0x7") does not match expected I64(56) ("0x38") -singlepass:fail:i64.wast:246 # AssertReturn - result I64(63) ("0x3f") does not match expected I64(0) ("0x0") -singlepass:fail:i64.wast:247 # AssertReturn - result I64(0) ("0x0") does not match expected I64(63) ("0x3f") -singlepass:fail:i64.wast:248 # AssertReturn - result I64(1) ("0x1") does not match expected I64(62) ("0x3e") -singlepass:fail:i64.wast:249 # AssertReturn - result I64(62) ("0x3e") does not match expected I64(1) ("0x1") -singlepass:fail:i64.wast:252 # AssertReturn - result I64(0) ("0x0") does not match expected I64(64) ("0x40") singlepass:fail:if.wast:440 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:imports.wast:283 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:imports.wast:286 # AssertTrap - expected trap, got Runtime:Error unknown error @@ -1490,9 +540,7 @@ singlepass:fail:linking.wast:190 # AssertTrap - expected trap, got Runtime:Error singlepass:fail:linking.wast:225 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:linking.wast:236 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:linking.wast:248 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:linking.wast:387 # AssertReturn - result I32(0) ("0x0") does not match expected I32(104) ("0x68") singlepass:fail:linking.wast:388 # AssertReturn - Call failed RuntimeError: unknown error -singlepass:fail:load.wast:201 # AssertReturn - result I32(0) ("0x0") does not match expected I32(32) ("0x20") singlepass:fail:memory_grow.wast:15 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:memory_grow.wast:16 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:memory_grow.wast:17 # AssertTrap - expected trap, got Runtime:Error unknown error @@ -1500,7 +548,6 @@ singlepass:fail:memory_grow.wast:18 # AssertTrap - expected trap, got Runtime:Er singlepass:fail:memory_grow.wast:24 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:memory_grow.wast:25 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:memory_grow.wast:286 # AssertTrap - expected trap, got Runtime:Error unknown error -singlepass:fail:memory_grow.wast:299 # AssertReturn - result I32(0) ("0x0") does not match expected I32(31) ("0x1f") singlepass:fail:memory_trap.wast:23 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:memory_trap.wast:24 # AssertTrap - expected trap, got Runtime:Error unknown error singlepass:fail:memory_trap.wast:25 # AssertTrap - expected trap, got Runtime:Error unknown error diff --git a/lib/spectests/tests/semantics.rs b/lib/spectests/tests/semantics.rs index 0bffa9dca..eadbeef0e 100644 --- a/lib/spectests/tests/semantics.rs +++ b/lib/spectests/tests/semantics.rs @@ -1,10 +1,9 @@ #[cfg(test)] mod tests { use wabt::wat2wasm; - use wasmer_clif_backend::CraneliftCompiler; - use wasmer_runtime_core::{ + use wasmer_runtime::{ error::{CallError, RuntimeError}, - import::ImportObject, + ImportObject, }; // The semantics of stack overflow are documented at: @@ -22,8 +21,7 @@ mod tests { (elem (;0;) (i32.const 0) 0)) "#; let wasm_binary = wat2wasm(module_str.as_bytes()).expect("WAST not valid or malformed"); - let module = wasmer_runtime_core::compile_with(&wasm_binary[..], &CraneliftCompiler::new()) - .expect("WASM can't be compiled"); + let module = wasmer_runtime::compile(&wasm_binary[..]).expect("WASM can't be compiled"); let instance = module .instantiate(&ImportObject::new()) .expect("WASM can't be instantiated"); diff --git a/lib/spectests/tests/spectest.rs b/lib/spectests/tests/spectest.rs index b85b9b0fe..3b03b598f 100644 --- a/lib/spectests/tests/spectest.rs +++ b/lib/spectests/tests/spectest.rs @@ -18,7 +18,8 @@ mod tests { // TODO Files could be run with multiple threads // TODO Allow running WAST &str directly (E.g. for use outside of spectests) - use std::rc::Rc; + use std::collections::HashSet; + use std::sync::{Arc, Mutex}; struct SpecFailure { file: String, @@ -46,15 +47,14 @@ mod tests { pub fn add_failure( &mut self, failure: SpecFailure, - testkey: &str, - excludes: &HashMap, + _testkey: &str, + excludes: &Vec, + line: u64, ) { - if excludes.contains_key(testkey) { - self.allowed_failure += 1; - return; - } - let platform_key = format!("{}:{}", testkey, get_platform()); - if excludes.contains_key(&platform_key) { + if excludes + .iter() + .any(|e| e.line_matches(line) && e.exclude_kind == ExcludeKind::Fail) + { self.allowed_failure += 1; return; } @@ -63,31 +63,6 @@ mod tests { } } - #[cfg(feature = "clif")] - fn get_compiler() -> impl Compiler { - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() - } - - #[cfg(feature = "llvm")] - fn get_compiler() -> impl Compiler { - use wasmer_llvm_backend::LLVMCompiler; - LLVMCompiler::new() - } - - #[cfg(feature = "singlepass")] - fn get_compiler() -> impl Compiler { - use wasmer_singlepass_backend::SinglePassCompiler; - SinglePassCompiler::new() - } - - #[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] - fn get_compiler() -> impl Compiler { - panic!("compiler not specified, activate a compiler via features"); - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() - } - #[cfg(feature = "clif")] fn get_compiler_name() -> &'static str { "clif" @@ -104,45 +79,157 @@ mod tests { } #[cfg(unix)] - fn get_platform() -> &'static str { + fn get_target_family() -> &'static str { "unix" } #[cfg(windows)] - fn get_platform() -> &'static str { + fn get_target_family() -> &'static str { "windows" } + fn get_target_arch() -> &'static str { + if cfg!(target_arch = "x86_64") { + "x86_64" + } else if cfg!(target_arch = "aarch64") { + "aarch64" + } else if cfg!(target_arch = "x86") { + "x86" + } else if cfg!(target_arch = "mips") { + "mips" + } else if cfg!(target_arch = "powerpc") { + "powerpc" + } else if cfg!(target_arch = "powerpc64") { + "powerpc64" + } else if cfg!(target_arch = "arm") { + "arm" + } else { + panic!("unknown target arch") + } + } + + // clif:skip:data.wast:172:unix:x86 + #[allow(dead_code)] + struct Exclude { + backend: Option, + exclude_kind: ExcludeKind, + file: String, + line: Option, + target_family: Option, + target_arch: Option, + } + + impl Exclude { + fn line_matches(&self, value: u64) -> bool { + self.line.is_none() || self.line.unwrap() == value + } + + fn line_exact_match(&self, value: u64) -> bool { + self.line.is_some() && self.line.unwrap() == value + } + + fn matches_backend(&self, value: &str) -> bool { + self.backend.is_none() || self.backend.as_ref().unwrap() == value + } + + fn matches_target_family(&self, value: &str) -> bool { + self.target_family.is_none() || self.target_family.as_ref().unwrap() == value + } + + fn matches_target_arch(&self, value: &str) -> bool { + self.target_arch.is_none() || self.target_arch.as_ref().unwrap() == value + } + + fn from( + backend: &str, + exclude_kind: &str, + file: &str, + line: &str, + target_family: &str, + target_arch: &str, + ) -> Exclude { + let backend: Option = match backend { + "*" => None, + "clif" => Some("clif".to_string()), + "singlepass" => Some("singlepass".to_string()), + "llvm" => Some("llvm".to_string()), + _ => panic!("backend {:?} not recognized", backend), + }; + let exclude_kind = match exclude_kind { + "skip" => ExcludeKind::Skip, + "fail" => ExcludeKind::Fail, + _ => panic!("exclude kind {:?} not recognized", exclude_kind), + }; + let line = match line { + "*" => None, + _ => Some( + line.parse::() + .expect(&format!("expected * or int: {:?}", line)), + ), + }; + let target_family = match target_family { + "*" => None, + _ => Some(target_family.to_string()), + }; + let target_arch = match target_arch { + "*" => None, + _ => Some(target_arch.to_string()), + }; + Exclude { + backend, + exclude_kind, + file: file.to_string(), + line, + target_family, + target_arch, + } + } + } + #[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] fn get_compiler_name() -> &'static str { panic!("compiler not specified, activate a compiler via features"); "unknown" } + fn with_instance( + maybe_instance: Option>>, + named_modules: &HashMap>>, + module: &Option, + f: F, + ) -> Option + where + R: Sized, + F: FnOnce(&Instance) -> R, + { + let ref ins = module + .as_ref() + .and_then(|name| named_modules.get(name).cloned()) + .or(maybe_instance)?; + let guard = ins.lock().unwrap(); + Some(f(guard.borrow())) + } + use glob::glob; use std::collections::HashMap; use std::fs; use std::panic::AssertUnwindSafe; use std::path::PathBuf; use wabt::script::{Action, Command, CommandKind, ScriptParser, Value}; - use wasmer_runtime_core::backend::{Compiler, CompilerConfig, Features}; - use wasmer_runtime_core::error::CompileError; - use wasmer_runtime_core::import::ImportObject; - use wasmer_runtime_core::Instance; - use wasmer_runtime_core::{ - export::Export, - global::Global, - import::LikeNamespace, - memory::Memory, - table::Table, + use wasmer_runtime::{ + compile_with_config, + error::CompileError, + func, imports, types::{ElementType, MemoryDescriptor, TableDescriptor}, units::Pages, + CompilerConfig, Ctx, Export, Features, Global, ImportObject, Instance, LikeNamespace, + Memory, Table, }; - use wasmer_runtime_core::{func, imports, vm::Ctx}; fn parse_and_run( path: &PathBuf, - excludes: &HashMap, + file_excludes: &HashSet, + excludes: &HashMap>, ) -> Result { let mut test_report = TestReport { failures: vec![], @@ -153,47 +240,48 @@ mod tests { let filename = path.file_name().unwrap().to_str().unwrap(); let source = fs::read(&path).unwrap(); - let backend = get_compiler_name(); - let platform = get_platform(); - let star_key = format!("{}:{}:*", backend, filename); - let platform_star_key = format!("{}:{}:*:{}", backend, filename, platform); - if (excludes.contains_key(&star_key) && *excludes.get(&star_key).unwrap() == Exclude::Skip) - || (excludes.contains_key(&platform_star_key) - && *excludes.get(&platform_star_key).unwrap() == Exclude::Skip) - { + // Entire file is excluded by line * and skip + if file_excludes.contains(filename) { return Ok(test_report); } let mut features = wabt::Features::new(); features.enable_simd(); features.enable_threads(); + features.enable_sign_extension(); let mut parser: ScriptParser = ScriptParser::from_source_and_name_with_features(&source, filename, features) .expect(&format!("Failed to parse script {}", &filename)); use std::panic; - let mut instance: Option> = None; + let mut instance: Option>> = None; - let mut named_modules: HashMap> = HashMap::new(); + let mut named_modules: HashMap>> = HashMap::new(); - let mut registered_modules: HashMap> = HashMap::new(); + let mut registered_modules: HashMap>> = HashMap::new(); // + let empty_excludes = vec![]; + let excludes = if excludes.contains_key(filename) { + excludes.get(filename).unwrap() + } else { + &empty_excludes + }; + + let backend = get_compiler_name(); while let Some(Command { kind, line }) = parser.next().map_err(|e| format!("Parse err: {:?}", e))? { let test_key = format!("{}:{}:{}", backend, filename, line); - let test_platform_key = format!("{}:{}:{}:{}", backend, filename, line, platform); // Use this line to debug which test is running println!("Running test: {}", test_key); - if (excludes.contains_key(&test_key) - && *excludes.get(&test_key).unwrap() == Exclude::Skip) - || (excludes.contains_key(&test_platform_key) - && *excludes.get(&test_platform_key).unwrap() == Exclude::Skip) + // Skip tests that match this line + if excludes + .iter() + .any(|e| e.line_exact_match(line) && e.exclude_kind == ExcludeKind::Skip) { - // println!("Skipping test: {}", test_key); continue; } @@ -210,12 +298,8 @@ mod tests { }, ..Default::default() }; - let module = wasmer_runtime_core::compile_with_config( - &module.into_vec(), - &get_compiler(), - config, - ) - .expect("WASM can't be compiled"); + let module = compile_with_config(&module.into_vec(), config) + .expect("WASM can't be compiled"); let i = module .instantiate(&spectest_import_object) .expect("WASM can't be instantiated"); @@ -232,13 +316,14 @@ mod tests { }, &test_key, excludes, + line, ); instance = None; } Ok(i) => { - let i = Rc::new(i); + let i = Arc::new(Mutex::new(i)); if name.is_some() { - named_modules.insert(name.unwrap(), Rc::clone(&i)); + named_modules.insert(name.unwrap(), Arc::clone(&i)); } instance = Some(i); } @@ -251,20 +336,17 @@ mod tests { field, args, } => { - let instance: Option<&Instance> = match module { - Some(ref name) => { - let i = named_modules.get(name); - match i { - Some(ins) => Some(ins.borrow()), - None => None, - } - } - None => match instance { - Some(ref i) => Some(i.borrow()), - None => None, + let maybe_call_result = with_instance( + instance.clone(), + &named_modules, + &module, + |instance| { + let params: Vec = + args.iter().cloned().map(convert_value).collect(); + instance.call(&field, ¶ms[..]) }, - }; - if instance.is_none() { + ); + if maybe_call_result.is_none() { test_report.add_failure( SpecFailure { file: filename.to_string(), @@ -274,11 +356,10 @@ mod tests { }, &test_key, excludes, + line, ); } else { - let params: Vec = - args.iter().cloned().map(|x| convert_value(x)).collect(); - let call_result = instance.unwrap().call(&field, ¶ms[..]); + let call_result = maybe_call_result.unwrap(); match call_result { Err(e) => { test_report.add_failure( @@ -290,6 +371,7 @@ mod tests { }, &test_key, excludes, + line, ); } Ok(values) => { @@ -306,7 +388,7 @@ mod tests { "result {:?} ({:?}) does not match expected {:?} ({:?})", v, to_hex(v.clone()), expected_value, to_hex(expected_value.clone()) ), - }, &test_key, excludes); + }, &test_key, excludes, line); } else { test_report.count_passed(); } @@ -316,20 +398,17 @@ mod tests { } } Action::Get { module, field } => { - let instance: Option<&Instance> = match module { - Some(ref name) => { - let i = named_modules.get(name); - match i { - Some(ins) => Some(ins.borrow()), - None => None, - } - } - None => match instance { - Some(ref i) => Some(i.borrow()), - None => None, + let maybe_call_result = with_instance( + instance.clone(), + &named_modules, + &module, + |instance| { + instance + .get_export(&field) + .expect(&format!("missing global {:?}", &field)) }, - }; - if instance.is_none() { + ); + if maybe_call_result.is_none() { test_report.add_failure( SpecFailure { file: filename.to_string(), @@ -339,12 +418,10 @@ mod tests { }, &test_key, excludes, + line, ); } else { - let export: Export = instance - .unwrap() - .get_export(&field) - .expect(&format!("missing global {:?}", &field)); + let export: Export = maybe_call_result.unwrap(); match export { Export::Global(g) => { let value = g.get(); @@ -365,6 +442,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -378,6 +456,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -392,20 +471,13 @@ mod tests { field, args, } => { - let instance: Option<&Instance> = match module { - Some(ref name) => { - let i = named_modules.get(name); - match i { - Some(ins) => Some(ins.borrow()), - None => None, - } - } - None => match instance { - Some(ref i) => Some(i.borrow()), - None => None, - }, - }; - if instance.is_none() { + let maybe_call_result = + with_instance(instance.clone(), &named_modules, &module, |instance| { + let params: Vec = + args.iter().cloned().map(convert_value).collect(); + instance.call(&field, ¶ms[..]) + }); + if maybe_call_result.is_none() { test_report.add_failure( SpecFailure { file: filename.to_string(), @@ -415,11 +487,10 @@ mod tests { }, &test_key, excludes, + line, ); } else { - let params: Vec = - args.iter().cloned().map(|x| convert_value(x)).collect(); - let call_result = instance.unwrap().call(&field, ¶ms[..]); + let call_result = maybe_call_result.unwrap(); match call_result { Err(e) => { test_report.add_failure( @@ -431,6 +502,7 @@ mod tests { }, &test_key, excludes, + line, ); } Ok(values) => { @@ -447,12 +519,14 @@ mod tests { "AssertReturnCanonicalNan" ), message: format!( - "value is not canonical nan {:?}", - v + "value is not canonical nan {:?} ({:?})", + v, + value_to_hex(v.clone()), ), }, &test_key, excludes, + line, ); } } @@ -468,20 +542,13 @@ mod tests { field, args, } => { - let instance: Option<&Instance> = match module { - Some(ref name) => { - let i = named_modules.get(name); - match i { - Some(ins) => Some(ins.borrow()), - None => None, - } - } - None => match instance { - Some(ref i) => Some(i.borrow()), - None => None, - }, - }; - if instance.is_none() { + let maybe_call_result = + with_instance(instance.clone(), &named_modules, &module, |instance| { + let params: Vec = + args.iter().cloned().map(convert_value).collect(); + instance.call(&field, ¶ms[..]) + }); + if maybe_call_result.is_none() { test_report.add_failure( SpecFailure { file: filename.to_string(), @@ -491,11 +558,10 @@ mod tests { }, &test_key, excludes, + line, ); } else { - let params: Vec = - args.iter().cloned().map(|x| convert_value(x)).collect(); - let call_result = instance.unwrap().call(&field, ¶ms[..]); + let call_result = maybe_call_result.unwrap(); match call_result { Err(e) => { test_report.add_failure( @@ -507,6 +573,7 @@ mod tests { }, &test_key, excludes, + line, ); } Ok(values) => { @@ -523,12 +590,14 @@ mod tests { "AssertReturnArithmeticNan" ), message: format!( - "value is not arithmetic nan {:?}", - v + "value is not arithmetic nan {:?} ({:?})", + v, + value_to_hex(v.clone()), ), }, &test_key, excludes, + line, ); } } @@ -544,20 +613,13 @@ mod tests { field, args, } => { - let instance: Option<&Instance> = match module { - Some(ref name) => { - let i = named_modules.get(name); - match i { - Some(ins) => Some(ins.borrow()), - None => None, - } - } - None => match instance { - Some(ref i) => Some(i.borrow()), - None => None, - }, - }; - if instance.is_none() { + let maybe_call_result = + with_instance(instance.clone(), &named_modules, &module, |instance| { + let params: Vec = + args.iter().cloned().map(convert_value).collect(); + instance.call(&field, ¶ms[..]) + }); + if maybe_call_result.is_none() { test_report.add_failure( SpecFailure { file: filename.to_string(), @@ -567,12 +629,11 @@ mod tests { }, &test_key, excludes, + line, ); } else { - let params: Vec = - args.iter().cloned().map(|x| convert_value(x)).collect(); - let call_result = instance.unwrap().call(&field, ¶ms[..]); - use wasmer_runtime_core::error::{CallError, RuntimeError}; + let call_result = maybe_call_result.unwrap(); + use wasmer_runtime::error::{CallError, RuntimeError}; match call_result { Err(e) => { match e { @@ -586,6 +647,7 @@ mod tests { }, &test_key, excludes, + line, ); } CallError::Runtime(r) => { @@ -607,6 +669,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -623,6 +686,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -640,11 +704,7 @@ mod tests { }, ..Default::default() }; - wasmer_runtime_core::compile_with_config( - &module.into_vec(), - &get_compiler(), - config, - ) + compile_with_config(&module.into_vec(), config) }); match result { Ok(module) => { @@ -666,6 +726,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -679,6 +740,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -694,11 +756,7 @@ mod tests { }, ..Default::default() }; - wasmer_runtime_core::compile_with_config( - &module.into_vec(), - &get_compiler(), - config, - ) + compile_with_config(&module.into_vec(), config) }); match result { @@ -721,6 +779,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -734,14 +793,46 @@ mod tests { }, &test_key, excludes, + line, ); } } } - CommandKind::AssertUninstantiable { - module: _, - message: _, - } => println!("AssertUninstantiable not yet implmented "), + CommandKind::AssertUninstantiable { module, message: _ } => { + let spectest_import_object = get_spectest_import_object(®istered_modules); + let config = CompilerConfig { + features: Features { + simd: true, + threads: true, + }, + ..Default::default() + }; + let module = compile_with_config(&module.into_vec(), config) + .expect("WASM can't be compiled"); + let result = panic::catch_unwind(AssertUnwindSafe(|| { + module + .instantiate(&spectest_import_object) + .expect("WASM can't be instantiated"); + })); + match result { + Err(_) => test_report.count_passed(), + Ok(_) => { + test_report.add_failure( + SpecFailure { + file: filename.to_string(), + line: line, + kind: format!("{}", "AssertUninstantiable"), + message: format!( + "instantiate successful, expected uninstantiable" + ), + }, + &test_key, + excludes, + line, + ); + } + }; + } CommandKind::AssertExhaustion { action, message: _ } => { match action { Action::Invoke { @@ -749,20 +840,17 @@ mod tests { field, args, } => { - let instance: Option<&Instance> = match module { - Some(ref name) => { - let i = named_modules.get(name); - match i { - Some(ins) => Some(ins.borrow()), - None => None, - } - } - None => match instance { - Some(ref i) => Some(i.borrow()), - None => None, + let maybe_call_result = with_instance( + instance.clone(), + &named_modules, + &module, + |instance| { + let params: Vec = + args.iter().cloned().map(convert_value).collect(); + instance.call(&field, ¶ms[..]) }, - }; - if instance.is_none() { + ); + if maybe_call_result.is_none() { test_report.add_failure( SpecFailure { file: filename.to_string(), @@ -772,11 +860,10 @@ mod tests { }, &test_key, excludes, + line, ); } else { - let params: Vec = - args.iter().cloned().map(|x| convert_value(x)).collect(); - let call_result = instance.unwrap().call(&field, ¶ms[..]); + let call_result = maybe_call_result.unwrap(); match call_result { Err(_e) => { // TODO is specific error required? @@ -795,6 +882,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -814,12 +902,8 @@ mod tests { }, ..Default::default() }; - let module = wasmer_runtime_core::compile_with_config( - &module.into_vec(), - &get_compiler(), - config, - ) - .expect("WASM can't be compiled"); + let module = compile_with_config(&module.into_vec(), config) + .expect("WASM can't be compiled"); module.instantiate(&spectest_import_object) })); match result { @@ -833,6 +917,7 @@ mod tests { }, &test_key, excludes, + line, ); } Ok(result) => match result { @@ -848,10 +933,11 @@ mod tests { }, &test_key, excludes, + line, ); } Err(e) => match e { - wasmer_runtime_core::error::Error::LinkError(_) => { + wasmer_runtime::error::Error::LinkError(_) => { test_report.count_passed(); } _ => { @@ -864,6 +950,7 @@ mod tests { }, &test_key, excludes, + line, ); } }, @@ -871,16 +958,16 @@ mod tests { } } CommandKind::Register { name, as_name } => { - let instance: Option> = match name { + let instance: Option>> = match name { Some(ref name) => { let i = named_modules.get(name); match i { - Some(ins) => Some(Rc::clone(ins)), + Some(ins) => Some(Arc::clone(ins)), None => None, } } None => match instance { - Some(ref i) => Some(Rc::clone(i)), + Some(ref i) => Some(Arc::clone(i)), None => None, }, }; @@ -897,6 +984,7 @@ mod tests { }, &test_key, excludes, + line, ); } } @@ -906,21 +994,13 @@ mod tests { field, args, } => { - let instance: Option<&Instance> = match module { - Some(ref name) => { - let i = named_modules.get(name); - match i { - Some(ins) => Some(ins.borrow()), - None => None, - } - } - None => match instance { - Some(ref i) => Some(i.borrow()), - None => None, - }, - }; - - if instance.is_none() { + let maybe_call_result = + with_instance(instance.clone(), &named_modules, &module, |instance| { + let params: Vec = + args.iter().cloned().map(convert_value).collect(); + instance.call(&field, ¶ms[..]) + }); + if maybe_call_result.is_none() { test_report.add_failure( SpecFailure { file: filename.to_string(), @@ -930,11 +1010,10 @@ mod tests { }, &test_key, excludes, + line, ); } else { - let params: Vec = - args.iter().cloned().map(|x| convert_value(x)).collect(); - let call_result = instance.unwrap().call(&field, ¶ms[..]); + let call_result = maybe_call_result.unwrap(); match call_result { Err(e) => { test_report.add_failure( @@ -946,6 +1025,7 @@ mod tests { }, &test_key, excludes, + line, ); } Ok(_values) => { @@ -964,22 +1044,32 @@ mod tests { Ok(test_report) } - fn is_canonical_nan(val: wasmer_runtime_core::types::Value) -> bool { + fn is_canonical_nan(val: wasmer_runtime::types::Value) -> bool { match val { - wasmer_runtime_core::types::Value::F32(x) => x.is_canonical_nan(), - wasmer_runtime_core::types::Value::F64(x) => x.is_canonical_nan(), + wasmer_runtime::types::Value::F32(x) => x.is_canonical_nan(), + wasmer_runtime::types::Value::F64(x) => x.is_canonical_nan(), _ => panic!("value is not a float {:?}", val), } } - fn is_arithmetic_nan(val: wasmer_runtime_core::types::Value) -> bool { + fn is_arithmetic_nan(val: wasmer_runtime::types::Value) -> bool { match val { - wasmer_runtime_core::types::Value::F32(x) => x.is_quiet_nan(), - wasmer_runtime_core::types::Value::F64(x) => x.is_quiet_nan(), + wasmer_runtime::types::Value::F32(x) => x.is_quiet_nan(), + wasmer_runtime::types::Value::F64(x) => x.is_quiet_nan(), _ => panic!("value is not a float {:?}", val), } } + fn value_to_hex(val: wasmer_runtime::types::Value) -> String { + match val { + wasmer_runtime::types::Value::I32(x) => format!("{:#x}", x), + wasmer_runtime::types::Value::I64(x) => format!("{:#x}", x), + wasmer_runtime::types::Value::F32(x) => format!("{:#x}", x.to_bits()), + wasmer_runtime::types::Value::F64(x) => format!("{:#x}", x.to_bits()), + wasmer_runtime::types::Value::V128(x) => format!("{:#x}", x), + } + } + #[derive(Debug, Clone, Eq, PartialEq)] pub enum SpectestValue { I32(i32), @@ -989,13 +1079,13 @@ mod tests { V128(u128), } - fn convert_wasmer_value(other: wasmer_runtime_core::types::Value) -> SpectestValue { + fn convert_wasmer_value(other: wasmer_runtime::types::Value) -> SpectestValue { match other { - wasmer_runtime_core::types::Value::I32(v) => SpectestValue::I32(v), - wasmer_runtime_core::types::Value::I64(v) => SpectestValue::I64(v), - wasmer_runtime_core::types::Value::F32(v) => SpectestValue::F32(v.to_bits()), - wasmer_runtime_core::types::Value::F64(v) => SpectestValue::F64(v.to_bits()), - wasmer_runtime_core::types::Value::V128(v) => SpectestValue::V128(v), + wasmer_runtime::types::Value::I32(v) => SpectestValue::I32(v), + wasmer_runtime::types::Value::I64(v) => SpectestValue::I64(v), + wasmer_runtime::types::Value::F32(v) => SpectestValue::F32(v.to_bits()), + wasmer_runtime::types::Value::F64(v) => SpectestValue::F64(v.to_bits()), + wasmer_runtime::types::Value::V128(v) => SpectestValue::V128(v), } } @@ -1009,13 +1099,13 @@ mod tests { } } - fn convert_value(other: Value) -> wasmer_runtime_core::types::Value { + fn convert_value(other: Value) -> wasmer_runtime::types::Value { match other { - Value::I32(v) => wasmer_runtime_core::types::Value::I32(v), - Value::I64(v) => wasmer_runtime_core::types::Value::I64(v), - Value::F32(v) => wasmer_runtime_core::types::Value::F32(v), - Value::F64(v) => wasmer_runtime_core::types::Value::F64(v), - Value::V128(v) => wasmer_runtime_core::types::Value::V128(v), + Value::I32(v) => wasmer_runtime::types::Value::I32(v), + Value::I64(v) => wasmer_runtime::types::Value::I64(v), + Value::F32(v) => wasmer_runtime::types::Value::F32(v), + Value::F64(v) => wasmer_runtime::types::Value::F64(v), + Value::V128(v) => wasmer_runtime::types::Value::V128(v), } } @@ -1054,18 +1144,14 @@ mod tests { } fn get_spectest_import_object( - registered_modules: &HashMap>, + registered_modules: &HashMap>>, ) -> ImportObject { - let memory = Memory::new(MemoryDescriptor { - minimum: Pages(1), - maximum: Some(Pages(2)), - shared: false, - }) - .unwrap(); + let memory_desc = MemoryDescriptor::new(Pages(1), Some(Pages(2)), false).unwrap(); + let memory = Memory::new(memory_desc).unwrap(); - let global_i32 = Global::new(wasmer_runtime_core::types::Value::I32(666)); - let global_f32 = Global::new(wasmer_runtime_core::types::Value::F32(666.0)); - let global_f64 = Global::new(wasmer_runtime_core::types::Value::F64(666.0)); + let global_i32 = Global::new(wasmer_runtime::types::Value::I32(666)); + let global_f32 = Global::new(wasmer_runtime::types::Value::F32(666.0)); + let global_f64 = Global::new(wasmer_runtime::types::Value::F64(666.0)); let table = Table::new(TableDescriptor { element: ElementType::Anyfunc, @@ -1091,13 +1177,13 @@ mod tests { }; for (name, instance) in registered_modules.iter() { - import_object.register(name.clone(), Rc::clone(instance)); + import_object.register(name.clone(), Arc::clone(instance)); } import_object } #[derive(Debug, Copy, Clone, PartialEq, Eq)] - enum Exclude { + enum ExcludeKind { Skip, Fail, } @@ -1107,13 +1193,18 @@ mod tests { use std::io::{BufRead, BufReader}; /// Reads the excludes.txt file into a hash map - fn read_excludes() -> HashMap { + fn read_excludes() -> (HashMap>, HashSet) { let mut excludes_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); excludes_path.push("tests"); excludes_path.push("excludes.txt"); let input = File::open(excludes_path).unwrap(); let buffered = BufReader::new(input); let mut result = HashMap::new(); + let mut file_excludes = HashSet::new(); + let current_backend = get_compiler_name(); + let current_target_family = get_target_family(); + let current_target_arch = get_target_arch(); + for line in buffered.lines() { let mut line = line.unwrap(); if line.trim().is_empty() || line.starts_with("#") { @@ -1128,26 +1219,53 @@ mod tests { // ::: let split: Vec<&str> = line.trim().split(':').collect(); - let kind = match *split.get(1).unwrap() { - "skip" => Exclude::Skip, - "fail" => Exclude::Fail, - _ => panic!("unknown exclude kind"), + let file = *split.get(2).unwrap(); + let exclude = match split.len() { + 0..=3 => panic!("expected at least 4 exclude conditions"), + 4 => Exclude::from( + *split.get(0).unwrap(), + *split.get(1).unwrap(), + *split.get(2).unwrap(), + *split.get(3).unwrap(), + "*", + "*", + ), + 5 => Exclude::from( + *split.get(0).unwrap(), + *split.get(1).unwrap(), + *split.get(2).unwrap(), + *split.get(3).unwrap(), + *split.get(4).unwrap(), + "*", + ), + 6 => Exclude::from( + *split.get(0).unwrap(), + *split.get(1).unwrap(), + *split.get(2).unwrap(), + *split.get(3).unwrap(), + *split.get(4).unwrap(), + *split.get(5).unwrap(), + ), + _ => panic!("too many exclude conditions {}", split.len()), }; - let has_platform = split.len() > 4; - let backend = split.get(0).unwrap(); - let testfile = split.get(2).unwrap(); - let line = split.get(3).unwrap(); - let key = if has_platform { - let platform = split.get(4).unwrap(); - format!("{}:{}:{}:{}", backend, testfile, line, platform) - } else { - format!("{}:{}:{}", backend, testfile, line) - }; - result.insert(key, kind); + if exclude.matches_backend(current_backend) + && exclude.matches_target_family(current_target_family) + && exclude.matches_target_arch(current_target_arch) + { + // Skip the whole file for line * and skip + if exclude.line.is_none() && exclude.exclude_kind == ExcludeKind::Skip { + file_excludes.insert(file.to_string()); + } + + if !result.contains_key(file) { + result.insert(file.to_string(), vec![]); + } + result.get_mut(file).unwrap().push(exclude); + } } } - result + (result, file_excludes) } #[test] @@ -1155,7 +1273,7 @@ mod tests { let mut success = true; let mut test_reports = vec![]; - let excludes = read_excludes(); + let (excludes, file_excludes) = read_excludes(); let mut glob_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); glob_path.push("spectests"); @@ -1165,7 +1283,7 @@ mod tests { for entry in glob(glob_str).expect("Failed to read glob pattern") { match entry { Ok(wast_path) => { - let result = parse_and_run(&wast_path, &excludes); + let result = parse_and_run(&wast_path, &file_excludes, &excludes); match result { Ok(test_report) => { if test_report.has_failures() { @@ -1265,5 +1383,4 @@ mod tests { self.to_bits() == 0x7FF8_0000_0000_0000 || self.to_bits() == 0xFFF8_0000_0000_0000 } } - } diff --git a/lib/wasi-framebuffer/Cargo.toml b/lib/wasi-framebuffer/Cargo.toml index 259f415d3..63d7c6c5b 100644 --- a/lib/wasi-framebuffer/Cargo.toml +++ b/lib/wasi-framebuffer/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmer-wasi-framebuffer" -version = "0.6.0" +version = "0.11.0" authors = ["The Wasmer Engineering Team "] edition = "2018" repository = "https://github.com/wasmerio/wasmer" @@ -12,6 +12,8 @@ maintenance = { status = "experimental" } [dependencies] minifb = "0.11" -wasmer-wasi = { version = "0.6.0", path = "../wasi" } -wasmer-runtime-core = { version = "0.6.0", path = "../runtime-core" } -ref_thread_local = "0.0" \ No newline at end of file +wasmer-wasi = { version = "0.11.0", path = "../wasi" } +wasmer-runtime-core = { version = "0.11.0", path = "../runtime-core" } +ref_thread_local = "0.0" +serde = "1" +typetag = "0.1" \ No newline at end of file diff --git a/lib/wasi-framebuffer/src/lib.rs b/lib/wasi-framebuffer/src/lib.rs index 0ab218b17..ec4bf74ed 100644 --- a/lib/wasi-framebuffer/src/lib.rs +++ b/lib/wasi-framebuffer/src/lib.rs @@ -1,7 +1,11 @@ +use serde::{Deserialize, Serialize}; use std::collections::VecDeque; use std::convert::TryInto; use std::io::{Read, Seek, SeekFrom, Write}; -use wasmer_wasi::state::{WasiFile, WasiFs, ALL_RIGHTS, VIRTUAL_ROOT_FD}; +use wasmer_wasi::{ + state::{Fd, WasiFile, WasiFs, WasiFsError, ALL_RIGHTS, VIRTUAL_ROOT_FD}, + types::*, +}; use minifb::{Key, KeyRepeat, MouseButton, Scale, Window, WindowOptions}; @@ -21,7 +25,7 @@ use std::borrow::BorrowMut; pub const MAX_X: u32 = 8192; pub const MAX_Y: u32 = 4320; -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub enum FrameBufferFileType { Buffer, Resolution, @@ -219,7 +223,7 @@ impl FrameBufferState { } } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct FrameBuffer { fb_type: FrameBufferFileType, cursor: u32, @@ -405,6 +409,7 @@ impl Write for FrameBuffer { } } +#[typetag::serde] impl WasiFile for FrameBuffer { fn last_accessed(&self) -> u64 { 0 @@ -418,6 +423,15 @@ impl WasiFile for FrameBuffer { fn size(&self) -> u64 { 0 } + fn set_len(&mut self, _new_size: __wasi_filesize_t) -> Result<(), WasiFsError> { + Ok(()) + } + fn unlink(&mut self) -> Result<(), WasiFsError> { + panic!("TODO(mark): actually implement this"); + } + fn bytes_available(&self) -> Result { + Ok(0) + } } pub fn initialize(fs: &mut WasiFs) -> Result<(), String> { @@ -452,7 +466,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> { let fb_fd = unsafe { fs.open_dir_all( VIRTUAL_ROOT_FD, - "sys/class/graphics/wasmerfb0".to_string(), + "sys/class/graphics/wasmerfb".to_string(), ALL_RIGHTS, ALL_RIGHTS, 0, @@ -464,6 +478,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> { .open_file_at( dev_fd, input_file, + Fd::READ, "input".to_string(), ALL_RIGHTS, ALL_RIGHTS, @@ -477,6 +492,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> { .open_file_at( dev_fd, frame_buffer_file, + Fd::READ | Fd::WRITE, "wasmerfb0".to_string(), ALL_RIGHTS, ALL_RIGHTS, @@ -490,6 +506,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> { .open_file_at( fb_fd, resolution_file, + Fd::READ | Fd::WRITE, "virtual_size".to_string(), ALL_RIGHTS, ALL_RIGHTS, @@ -503,6 +520,7 @@ pub fn initialize(fs: &mut WasiFs) -> Result<(), String> { .open_file_at( fb_fd, index_file, + Fd::READ | Fd::WRITE, "buffer_index_display".to_string(), ALL_RIGHTS, ALL_RIGHTS, diff --git a/lib/wasi-tests/Cargo.toml b/lib/wasi-tests/Cargo.toml index 9cf2c37e1..9b84e5ea9 100644 --- a/lib/wasi-tests/Cargo.toml +++ b/lib/wasi-tests/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmer-wasi-tests" -version = "0.6.0" +version = "0.11.0" description = "Tests for our WASI implementation" license = "MIT" authors = ["The Wasmer Engineering Team "] @@ -9,21 +9,21 @@ publish = false build = "build/mod.rs" [dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } -wasmer-wasi = { path = "../wasi", version = "0.6.0" } +# We set default features to false to be able to use the singlepass backend properly +wasmer-runtime = { path = "../runtime", version = "0.11.0", default-features = false } +wasmer-wasi = { path = "../wasi", version = "0.11.0" } # hack to get tests to work -wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.6.0", optional = true } -wasmer-llvm-backend = { path = "../llvm-backend", version = "0.6.0", optional = true } - +wasmer-clif-backend = { path = "../clif-backend", version = "0.11.0", optional = true} +wasmer-singlepass-backend = { path = "../singlepass-backend", version = "0.11.0", optional = true } +wasmer-llvm-backend = { path = "../llvm-backend", version = "0.11.0", features = ["test"], optional = true } [build-dependencies] -glob = "0.3.0" +glob = "0.3" [dev-dependencies] -wasmer-clif-backend = { path = "../clif-backend", version = "0.6.0" } -wasmer-dev-utils = { path = "../dev-utils", version = "0.6.0"} +wasmer-dev-utils = { path = "../dev-utils", version = "0.11.0"} [features] -clif = [] -singlepass = ["wasmer-singlepass-backend"] -llvm = ["wasmer-llvm-backend"] +clif = ["wasmer-clif-backend", "wasmer-runtime/default-backend-cranelift"] +singlepass = ["wasmer-singlepass-backend", "wasmer-runtime/default-backend-singlepass"] +llvm = ["wasmer-llvm-backend", "wasmer-runtime/default-backend-llvm"] diff --git a/lib/wasi-tests/build/wasitests.rs b/lib/wasi-tests/build/wasitests.rs index aefab31ed..5ccda63c9 100644 --- a/lib/wasi-tests/build/wasitests.rs +++ b/lib/wasi-tests/build/wasitests.rs @@ -83,7 +83,7 @@ pub fn compile(file: &str, ignores: &HashSet) -> Option { .arg("+nightly") .arg("--target=wasm32-wasi") .arg("-C") - .arg("opt-level=s") + .arg("opt-level=z") .arg(file) .arg("-o") .arg(&wasm_out_name) @@ -145,7 +145,7 @@ pub fn compile(file: &str, ignores: &HashSet) -> Option { out_str.push_str("vec!["); for entry in args.po_dirs { - out_str.push_str(&format!("\"{}\".to_string(),", entry)); + out_str.push_str(&format!("std::path::PathBuf::from(\"{}\"),", entry)); } out_str.push_str("]"); @@ -153,7 +153,9 @@ pub fn compile(file: &str, ignores: &HashSet) -> Option { }; let contents = format!( - "#[test]{ignore} + "{banner} + +#[test]{ignore} fn test_{rs_module_name}() {{ assert_wasi_output!( \"../../{module_path}\", @@ -165,6 +167,7 @@ fn test_{rs_module_name}() {{ ); }} ", + banner = BANNER, ignore = ignored, module_path = wasm_out_name, rs_module_name = rs_module_name, diff --git a/lib/wasi-tests/src/lib.rs b/lib/wasi-tests/src/lib.rs index 5df757613..dcfa5bc8b 100644 --- a/lib/wasi-tests/src/lib.rs +++ b/lib/wasi-tests/src/lib.rs @@ -1 +1,63 @@ -// nothing to see here +#![cfg(test)] +use wasmer_runtime::{compile, Ctx, Func}; +use wasmer_wasi::{state::*, *}; + +use std::ffi::c_void; + +#[cfg(not(feature = "singlepass"))] +#[test] +fn serializing_works() { + let args = vec![ + b"program_name".into_iter().cloned().collect(), + b"arg1".into_iter().cloned().collect(), + ]; + let envs = vec![ + b"PATH=/bin".into_iter().cloned().collect(), + b"GOROOT=$HOME/.cargo/bin".into_iter().cloned().collect(), + ]; + let wasm_binary = include_bytes!("../wasitests/fd_read.wasm"); + let module = compile(&wasm_binary[..]) + .map_err(|e| format!("Can't compile module: {:?}", e)) + .unwrap(); + + let wasi_version = get_wasi_version(&module).expect("WASI module"); + let import_object = generate_import_object_for_version( + wasi_version, + args.clone(), + envs.clone(), + vec![], + vec![( + ".".to_string(), + std::path::PathBuf::from("wasitests/test_fs/hamlet"), + )], + ); + + let state_bytes = { + let instance = module.instantiate(&import_object).unwrap(); + + let start: Func<(), ()> = instance.func("_start").unwrap(); + start.call().unwrap(); + let state = get_wasi_state(instance.context()); + + assert_eq!(state.args, args); + assert_eq!(state.envs, envs); + let bytes = state.freeze().unwrap(); + + bytes + }; + + let mut instance = module.instantiate(&import_object).unwrap(); + + let wasi_state = Box::new(WasiState::unfreeze(&state_bytes).unwrap()); + + instance.context_mut().data = Box::into_raw(wasi_state) as *mut c_void; + + let second_entry: Func<(), i32> = instance.func("second_entry").unwrap(); + let result = second_entry.call().unwrap(); + assert_eq!(result, true as i32); +} + +#[allow(clippy::mut_from_ref)] +pub(crate) fn get_wasi_state(ctx: &Ctx) -> &mut WasiState { + unsafe { state::get_wasi_state(&mut *(ctx as *const Ctx as *mut Ctx)) } +} diff --git a/lib/wasi-tests/tests/wasitests/_common.rs b/lib/wasi-tests/tests/wasitests/_common.rs index 958fdf6ad..41bf14e44 100644 --- a/lib/wasi-tests/tests/wasitests/_common.rs +++ b/lib/wasi-tests/tests/wasitests/_common.rs @@ -1,39 +1,22 @@ macro_rules! assert_wasi_output { ($file:expr, $name:expr, $po_dir_args: expr, $mapdir_args:expr, $envvar_args:expr, $expected:expr) => {{ use wasmer_dev_utils::stdio::StdioCapturer; - use wasmer_runtime_core::{backend::Compiler, Func}; - use wasmer_wasi::generate_import_object; - - #[cfg(feature = "clif")] - fn get_compiler() -> impl Compiler { - use wasmer_clif_backend::CraneliftCompiler; - CraneliftCompiler::new() - } - - #[cfg(feature = "llvm")] - fn get_compiler() -> impl Compiler { - use wasmer_llvm_backend::LLVMCompiler; - LLVMCompiler::new() - } - - #[cfg(feature = "singlepass")] - fn get_compiler() -> impl Compiler { - use wasmer_singlepass_backend::SinglePassCompiler; - SinglePassCompiler::new() - } - - #[cfg(not(any(feature = "llvm", feature = "clif", feature = "singlepass")))] - fn get_compiler() -> impl Compiler { - compile_error!("compiler not specified, activate a compiler via features"); - unreachable!(); - } + use wasmer_runtime::Func; + use wasmer_wasi::{generate_import_object_for_version, get_wasi_version}; let wasm_bytes = include_bytes!($file); - let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &get_compiler()) - .expect("WASM can't be compiled"); + let module = wasmer_runtime::compile(&wasm_bytes[..]).expect("WASM can't be compiled"); - let import_object = generate_import_object(vec![], vec![], $po_dir_args, $mapdir_args); + let wasi_version = get_wasi_version(&module).expect("WASI module"); + + let import_object = generate_import_object_for_version( + wasi_version, + vec![], + vec![], + $po_dir_args, + $mapdir_args, + ); let instance = module .instantiate(&import_object) diff --git a/lib/wasi-tests/tests/wasitests/close_preopen_fd.rs b/lib/wasi-tests/tests/wasitests/close_preopen_fd.rs new file mode 100644 index 000000000..38089722d --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/close_preopen_fd.rs @@ -0,0 +1,18 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + +#[test] +fn test_close_preopen_fd() { + assert_wasi_output!( + "../../wasitests/close_preopen_fd.wasm", + "close_preopen_fd", + vec![], + vec![( + "hamlet".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet") + ),], + vec![], + "../../wasitests/close_preopen_fd.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/create_dir.rs b/lib/wasi-tests/tests/wasitests/create_dir.rs index 3c8866721..820cadd53 100644 --- a/lib/wasi-tests/tests/wasitests/create_dir.rs +++ b/lib/wasi-tests/tests/wasitests/create_dir.rs @@ -1,9 +1,13 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_create_dir() { assert_wasi_output!( "../../wasitests/create_dir.wasm", "create_dir", - vec![".".to_string(),], + vec![std::path::PathBuf::from("."),], vec![], vec![], "../../wasitests/create_dir.out" diff --git a/lib/wasi-tests/tests/wasitests/envvar.rs b/lib/wasi-tests/tests/wasitests/envvar.rs index 7c66b3d2c..9264b2ac7 100644 --- a/lib/wasi-tests/tests/wasitests/envvar.rs +++ b/lib/wasi-tests/tests/wasitests/envvar.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_envvar() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/fd_allocate.rs b/lib/wasi-tests/tests/wasitests/fd_allocate.rs index b6a43a8e5..a45232f7a 100644 --- a/lib/wasi-tests/tests/wasitests/fd_allocate.rs +++ b/lib/wasi-tests/tests/wasitests/fd_allocate.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_fd_allocate() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/fd_append.rs b/lib/wasi-tests/tests/wasitests/fd_append.rs new file mode 100644 index 000000000..0ac3ea6eb --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/fd_append.rs @@ -0,0 +1,18 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + +#[test] +fn test_fd_append() { + assert_wasi_output!( + "../../wasitests/fd_append.wasm", + "fd_append", + vec![], + vec![( + ".".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/temp") + ),], + vec![], + "../../wasitests/fd_append.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/fd_close.rs b/lib/wasi-tests/tests/wasitests/fd_close.rs new file mode 100644 index 000000000..8bff35789 --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/fd_close.rs @@ -0,0 +1,18 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + +#[test] +fn test_fd_close() { + assert_wasi_output!( + "../../wasitests/fd_close.wasm", + "fd_close", + vec![], + vec![( + ".".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet") + ),], + vec![], + "../../wasitests/fd_close.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/fd_pread.rs b/lib/wasi-tests/tests/wasitests/fd_pread.rs index cfd8f2ac5..01af04a13 100644 --- a/lib/wasi-tests/tests/wasitests/fd_pread.rs +++ b/lib/wasi-tests/tests/wasitests/fd_pread.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_fd_pread() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/fd_read.rs b/lib/wasi-tests/tests/wasitests/fd_read.rs new file mode 100644 index 000000000..926496041 --- /dev/null +++ b/lib/wasi-tests/tests/wasitests/fd_read.rs @@ -0,0 +1,18 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + +#[test] +fn test_fd_read() { + assert_wasi_output!( + "../../wasitests/fd_read.wasm", + "fd_read", + vec![], + vec![( + ".".to_string(), + ::std::path::PathBuf::from("wasitests/test_fs/hamlet") + ),], + vec![], + "../../wasitests/fd_read.out" + ); +} diff --git a/lib/wasi-tests/tests/wasitests/fd_sync.rs b/lib/wasi-tests/tests/wasitests/fd_sync.rs index ab1ed71bf..2540e5031 100644 --- a/lib/wasi-tests/tests/wasitests/fd_sync.rs +++ b/lib/wasi-tests/tests/wasitests/fd_sync.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_fd_sync() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/file_metadata.rs b/lib/wasi-tests/tests/wasitests/file_metadata.rs index 67d3a8611..c05595bea 100644 --- a/lib/wasi-tests/tests/wasitests/file_metadata.rs +++ b/lib/wasi-tests/tests/wasitests/file_metadata.rs @@ -1,9 +1,13 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_file_metadata() { assert_wasi_output!( "../../wasitests/file_metadata.wasm", "file_metadata", - vec![".".to_string(),], + vec![std::path::PathBuf::from("."),], vec![], vec![], "../../wasitests/file_metadata.out" diff --git a/lib/wasi-tests/tests/wasitests/fs_sandbox_test.rs b/lib/wasi-tests/tests/wasitests/fs_sandbox_test.rs index 25fcfa1fa..a1d1e4c44 100644 --- a/lib/wasi-tests/tests/wasitests/fs_sandbox_test.rs +++ b/lib/wasi-tests/tests/wasitests/fs_sandbox_test.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_fs_sandbox_test() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/fseek.rs b/lib/wasi-tests/tests/wasitests/fseek.rs index a1eac93ed..0cc896aa8 100644 --- a/lib/wasi-tests/tests/wasitests/fseek.rs +++ b/lib/wasi-tests/tests/wasitests/fseek.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_fseek() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/hello.rs b/lib/wasi-tests/tests/wasitests/hello.rs index 8cf9ebe49..b9f28e7bc 100644 --- a/lib/wasi-tests/tests/wasitests/hello.rs +++ b/lib/wasi-tests/tests/wasitests/hello.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_hello() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/mapdir.rs b/lib/wasi-tests/tests/wasitests/mapdir.rs index 301f4887a..27064c8ae 100644 --- a/lib/wasi-tests/tests/wasitests/mapdir.rs +++ b/lib/wasi-tests/tests/wasitests/mapdir.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_mapdir() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/mod.rs b/lib/wasi-tests/tests/wasitests/mod.rs index a1b1d3781..d226dc0ee 100644 --- a/lib/wasi-tests/tests/wasitests/mod.rs +++ b/lib/wasi-tests/tests/wasitests/mod.rs @@ -5,10 +5,14 @@ // The _common module is not autogenerated. It provides common macros for the wasitests #[macro_use] mod _common; +mod close_preopen_fd; mod create_dir; mod envvar; mod fd_allocate; +mod fd_append; +mod fd_close; mod fd_pread; +mod fd_read; mod fd_sync; mod file_metadata; mod fs_sandbox_test; diff --git a/lib/wasi-tests/tests/wasitests/path_link.rs b/lib/wasi-tests/tests/wasitests/path_link.rs index a099d0420..b035811ea 100644 --- a/lib/wasi-tests/tests/wasitests/path_link.rs +++ b/lib/wasi-tests/tests/wasitests/path_link.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_path_link() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/path_rename.rs b/lib/wasi-tests/tests/wasitests/path_rename.rs index 9c1d3d7a0..f623d80ba 100644 --- a/lib/wasi-tests/tests/wasitests/path_rename.rs +++ b/lib/wasi-tests/tests/wasitests/path_rename.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_path_rename() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/path_symlink.rs b/lib/wasi-tests/tests/wasitests/path_symlink.rs index 43f0836bf..2eeddc0a8 100644 --- a/lib/wasi-tests/tests/wasitests/path_symlink.rs +++ b/lib/wasi-tests/tests/wasitests/path_symlink.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_path_symlink() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/poll_oneoff.rs b/lib/wasi-tests/tests/wasitests/poll_oneoff.rs index bf4aa04a9..3200f4ad1 100644 --- a/lib/wasi-tests/tests/wasitests/poll_oneoff.rs +++ b/lib/wasi-tests/tests/wasitests/poll_oneoff.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_poll_oneoff() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/quine.rs b/lib/wasi-tests/tests/wasitests/quine.rs index b8ab50ce7..c4139d959 100644 --- a/lib/wasi-tests/tests/wasitests/quine.rs +++ b/lib/wasi-tests/tests/wasitests/quine.rs @@ -1,9 +1,13 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_quine() { assert_wasi_output!( "../../wasitests/quine.wasm", "quine", - vec![".".to_string(),], + vec![std::path::PathBuf::from("."),], vec![], vec![], "../../wasitests/quine.out" diff --git a/lib/wasi-tests/tests/wasitests/readlink.rs b/lib/wasi-tests/tests/wasitests/readlink.rs index 72e0c7fa7..22f72ff81 100644 --- a/lib/wasi-tests/tests/wasitests/readlink.rs +++ b/lib/wasi-tests/tests/wasitests/readlink.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_readlink() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/wasi_sees_virtual_root.rs b/lib/wasi-tests/tests/wasitests/wasi_sees_virtual_root.rs index f604f9678..ede1b0f38 100644 --- a/lib/wasi-tests/tests/wasitests/wasi_sees_virtual_root.rs +++ b/lib/wasi-tests/tests/wasitests/wasi_sees_virtual_root.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_wasi_sees_virtual_root() { assert_wasi_output!( diff --git a/lib/wasi-tests/tests/wasitests/writing.rs b/lib/wasi-tests/tests/wasitests/writing.rs index 2709f2cfa..ce1a6a8c5 100644 --- a/lib/wasi-tests/tests/wasitests/writing.rs +++ b/lib/wasi-tests/tests/wasitests/writing.rs @@ -1,3 +1,7 @@ +// !!! THIS IS A GENERATED FILE !!! +// ANY MANUAL EDITS MAY BE OVERWRITTEN AT ANY TIME +// Files autogenerated with cargo build (build/wasitests.rs). + #[test] fn test_writing() { assert_wasi_output!( diff --git a/lib/wasi-tests/wasitests/close_preopen_fd.out b/lib/wasi-tests/wasitests/close_preopen_fd.out new file mode 100644 index 000000000..e6e9b9f67 --- /dev/null +++ b/lib/wasi-tests/wasitests/close_preopen_fd.out @@ -0,0 +1,3 @@ +accessing preopen fd was a success +Closing preopen fd was a success +accessing closed preopen fd was an EBADF error: true diff --git a/lib/wasi-tests/wasitests/close_preopen_fd.rs b/lib/wasi-tests/wasitests/close_preopen_fd.rs new file mode 100644 index 000000000..ba0a87561 --- /dev/null +++ b/lib/wasi-tests/wasitests/close_preopen_fd.rs @@ -0,0 +1,44 @@ +// Args: +// mapdir: hamlet:wasitests/test_fs/hamlet + +use std::fs; +use std::path::PathBuf; + +#[cfg(target_os = "wasi")] +#[link(wasm_import_module = "wasi_unstable")] +extern "C" { + fn fd_close(fd: u32) -> u16; + fn fd_fdstat_set_flags(fd: u32, flags: u16) -> u16; +} + +const FIRST_PREOPEN_FD: u32 = 4; + +fn main() { + #[cfg(target_os = "wasi")] + { + let result = unsafe { fd_fdstat_set_flags(FIRST_PREOPEN_FD, 1 << 2) }; + println!( + "accessing preopen fd was a {}", + if result == 0 { "success" } else { "failure" } + ); + + let result = unsafe { fd_close(FIRST_PREOPEN_FD) }; + println!( + "Closing preopen fd was a {}", + if result == 0 { "success" } else { "failure" } + ); + + let result = unsafe { fd_fdstat_set_flags(FIRST_PREOPEN_FD, 1 << 2) }; + println!( + "accessing closed preopen fd was an EBADF error: {}", + if result == 8 { "true" } else { "false" } + ); + } + + #[cfg(not(target_os = "wasi"))] + { + println!("accessing preopen fd was a success"); + println!("Closing preopen fd was a success"); + println!("accessing closed preopen fd was an EBADF error: true"); + } +} diff --git a/lib/wasi-tests/wasitests/close_preopen_fd.wasm b/lib/wasi-tests/wasitests/close_preopen_fd.wasm new file mode 100755 index 000000000..5badfb755 Binary files /dev/null and b/lib/wasi-tests/wasitests/close_preopen_fd.wasm differ diff --git a/lib/wasi-tests/wasitests/create_dir.wasm b/lib/wasi-tests/wasitests/create_dir.wasm index 2fa023792..0c2fb0bbf 100755 Binary files a/lib/wasi-tests/wasitests/create_dir.wasm and b/lib/wasi-tests/wasitests/create_dir.wasm differ diff --git a/lib/wasi-tests/wasitests/envvar.wasm b/lib/wasi-tests/wasitests/envvar.wasm index ef539e6a5..b05151905 100755 Binary files a/lib/wasi-tests/wasitests/envvar.wasm and b/lib/wasi-tests/wasitests/envvar.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_allocate.wasm b/lib/wasi-tests/wasitests/fd_allocate.wasm index b3b8a46bd..e604f6e40 100755 Binary files a/lib/wasi-tests/wasitests/fd_allocate.wasm and b/lib/wasi-tests/wasitests/fd_allocate.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_append.out b/lib/wasi-tests/wasitests/fd_append.out new file mode 100644 index 000000000..e9d92ef26 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_append.out @@ -0,0 +1 @@ +"Hello, world!\nGoodbye, world!\n" diff --git a/lib/wasi-tests/wasitests/fd_append.rs b/lib/wasi-tests/wasitests/fd_append.rs new file mode 100644 index 000000000..2e311652e --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_append.rs @@ -0,0 +1,51 @@ +// Args: +// mapdir: .:wasitests/test_fs/temp + +use std::fs::OpenOptions; +use std::io::{Read, Write}; +use std::path::PathBuf; + +static STR1: &str = "Hello, world!\n"; +static STR2: &str = "Goodbye, world!\n"; + +fn main() { + let file = { + #[cfg(not(target_os = "wasi"))] + let mut base = PathBuf::from("wasitests/test_fs/temp"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("."); + + base.push("fd_append_test"); + base + }; + + { + let mut file_handle = OpenOptions::new() + .create_new(true) + .append(true) + .open(&file) + .expect("Couldn't create file"); + file_handle.write(STR1.as_bytes()).unwrap(); + } + { + let mut file_handle = OpenOptions::new() + .append(true) + .open(&file) + .expect("Couldn't reopen file to append"); + file_handle.write(STR2.as_bytes()).unwrap(); + } + + { + let mut file_handle = OpenOptions::new() + .read(true) + .open(&file) + .expect("Couldn't reopen file to read"); + + let mut test = String::new(); + file_handle.read_to_string(&mut test); + + assert_eq!(&test, &format!("{}{}", STR1, STR2)); + println!("{:?}", &test); + } + std::fs::remove_file(&file).unwrap(); +} diff --git a/lib/wasi-tests/wasitests/fd_append.wasm b/lib/wasi-tests/wasitests/fd_append.wasm new file mode 100755 index 000000000..af40f8af9 Binary files /dev/null and b/lib/wasi-tests/wasitests/fd_append.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_close.out b/lib/wasi-tests/wasitests/fd_close.out new file mode 100644 index 000000000..53c9c2727 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_close.out @@ -0,0 +1,3 @@ +Successfully closed file! +Successfully closed stderr! +Successfully closed stdin! diff --git a/lib/wasi-tests/wasitests/fd_close.rs b/lib/wasi-tests/wasitests/fd_close.rs new file mode 100644 index 000000000..e5cdaafd9 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_close.rs @@ -0,0 +1,63 @@ +// Args: +// mapdir: .:wasitests/test_fs/hamlet + +use std::fs; +#[cfg(target_os = "wasi")] +use std::os::wasi::prelude::AsRawFd; +use std::path::PathBuf; + +#[cfg(target_os = "wasi")] +#[link(wasm_import_module = "wasi_unstable")] +extern "C" { + fn fd_close(fd: u32) -> u16; +} + +fn main() { + #[cfg(not(target_os = "wasi"))] + let mut base = PathBuf::from("wasitests/test_fs/hamlet"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("."); + + base.push("act3/scene3.txt"); + let file = fs::File::open(&base).expect("could not open file"); + + #[cfg(target_os = "wasi")] + { + let file_fd = file.as_raw_fd(); + let stdout_fd = std::io::stdout().as_raw_fd(); + let stderr_fd = std::io::stderr().as_raw_fd(); + let stdin_fd = std::io::stdin().as_raw_fd(); + + let result = unsafe { fd_close(file_fd) }; + if result == 0 { + println!("Successfully closed file!") + } else { + println!("Could not close file"); + } + + let result = unsafe { fd_close(stderr_fd) }; + if result == 0 { + println!("Successfully closed stderr!") + } else { + println!("Could not close stderr"); + } + let result = unsafe { fd_close(stdin_fd) }; + if result == 0 { + println!("Successfully closed stdin!") + } else { + println!("Could not close stdin"); + } + let result = unsafe { fd_close(stdout_fd) }; + if result == 0 { + println!("Successfully closed stdout!") + } else { + println!("Could not close stdout"); + } + } + #[cfg(not(target_os = "wasi"))] + { + println!("Successfully closed file!"); + println!("Successfully closed stderr!"); + println!("Successfully closed stdin!"); + } +} diff --git a/lib/wasi-tests/wasitests/fd_close.wasm b/lib/wasi-tests/wasitests/fd_close.wasm new file mode 100755 index 000000000..162e96875 Binary files /dev/null and b/lib/wasi-tests/wasitests/fd_close.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_pread.wasm b/lib/wasi-tests/wasitests/fd_pread.wasm index a6fdb0c33..7b48e4959 100755 Binary files a/lib/wasi-tests/wasitests/fd_pread.wasm and b/lib/wasi-tests/wasitests/fd_pread.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_read.out b/lib/wasi-tests/wasitests/fd_read.out new file mode 100644 index 000000000..f2b9f2169 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_read.out @@ -0,0 +1,3 @@ +SCENE IV. The Queen's closet. + + Enter QUEEN GERTRUDE and POLO \ No newline at end of file diff --git a/lib/wasi-tests/wasitests/fd_read.rs b/lib/wasi-tests/wasitests/fd_read.rs new file mode 100644 index 000000000..c0a229f85 --- /dev/null +++ b/lib/wasi-tests/wasitests/fd_read.rs @@ -0,0 +1,86 @@ +// Args: +// mapdir: .:wasitests/test_fs/hamlet + +// this program is used in the pause/resume test + +use std::fs; +#[cfg(target_os = "wasi")] +use std::os::wasi::prelude::AsRawFd; +use std::path::PathBuf; + +#[cfg(target_os = "wasi")] +#[repr(C)] +struct WasiIovec { + pub buf: u32, + pub buf_len: u32, +} + +#[cfg(target_os = "wasi")] +#[link(wasm_import_module = "wasi_unstable")] +extern "C" { + fn fd_read(fd: u32, iovs: u32, iovs_len: u32, nread: u32) -> u16; +} + +#[cfg(target_os = "wasi")] +fn read(fd: u32, iovs: &[&mut [u8]]) -> u32 { + let mut nread = 0; + let mut processed_iovs = vec![]; + + for iov in iovs { + processed_iovs.push(WasiIovec { + buf: iov.as_ptr() as usize as u32, + buf_len: iov.len() as u32, + }) + } + + unsafe { + fd_read( + fd, + processed_iovs.as_ptr() as usize as u32, + processed_iovs.len() as u32, + &mut nread as *mut u32 as usize as u32, + ); + } + nread +} + +fn main() { + #[cfg(not(target_os = "wasi"))] + let mut base = PathBuf::from("wasitests/test_fs/hamlet"); + #[cfg(target_os = "wasi")] + let mut base = PathBuf::from("."); + + base.push("act3/scene4.txt"); + let mut file = fs::File::open(&base).expect("Could not open file"); + let mut buffer = [0u8; 64]; + + #[cfg(target_os = "wasi")] + { + let raw_fd = file.as_raw_fd(); + assert_eq!(read(raw_fd, &[&mut buffer]), 64); + let str_val = std::str::from_utf8(&buffer[..]).unwrap().to_string(); + println!("{}", &str_val); + } + // leak the file handle so that we can use it later + std::mem::forget(file); + + #[cfg(not(target_os = "wasi"))] + { + // eh, just print the output directly + print!( + "SCENE IV. The Queen's closet. + + Enter QUEEN GERTRUDE and POLO" + ); + } +} + +#[cfg(target_os = "wasi")] +#[no_mangle] +fn second_entry() -> bool { + let raw_fd = 5; + let mut buffer = [0u8; 8]; + let result = read(raw_fd, &[&mut buffer]); + + &buffer == b"NIUS \n\nL" +} diff --git a/lib/wasi-tests/wasitests/fd_read.wasm b/lib/wasi-tests/wasitests/fd_read.wasm new file mode 100755 index 000000000..799ca1645 Binary files /dev/null and b/lib/wasi-tests/wasitests/fd_read.wasm differ diff --git a/lib/wasi-tests/wasitests/fd_sync.wasm b/lib/wasi-tests/wasitests/fd_sync.wasm index b5a3243ff..5fde9b627 100755 Binary files a/lib/wasi-tests/wasitests/fd_sync.wasm and b/lib/wasi-tests/wasitests/fd_sync.wasm differ diff --git a/lib/wasi-tests/wasitests/file_metadata.wasm b/lib/wasi-tests/wasitests/file_metadata.wasm index 986362c58..68c599172 100755 Binary files a/lib/wasi-tests/wasitests/file_metadata.wasm and b/lib/wasi-tests/wasitests/file_metadata.wasm differ diff --git a/lib/wasi-tests/wasitests/fs_sandbox_test.wasm b/lib/wasi-tests/wasitests/fs_sandbox_test.wasm index 255287806..8a8c63494 100755 Binary files a/lib/wasi-tests/wasitests/fs_sandbox_test.wasm and b/lib/wasi-tests/wasitests/fs_sandbox_test.wasm differ diff --git a/lib/wasi-tests/wasitests/fseek.wasm b/lib/wasi-tests/wasitests/fseek.wasm index f50045437..d3a7c8f84 100755 Binary files a/lib/wasi-tests/wasitests/fseek.wasm and b/lib/wasi-tests/wasitests/fseek.wasm differ diff --git a/lib/wasi-tests/wasitests/hello.wasm b/lib/wasi-tests/wasitests/hello.wasm index 758a14bd1..a2625868b 100755 Binary files a/lib/wasi-tests/wasitests/hello.wasm and b/lib/wasi-tests/wasitests/hello.wasm differ diff --git a/lib/wasi-tests/wasitests/mapdir.wasm b/lib/wasi-tests/wasitests/mapdir.wasm index c6e594991..5675534ef 100755 Binary files a/lib/wasi-tests/wasitests/mapdir.wasm and b/lib/wasi-tests/wasitests/mapdir.wasm differ diff --git a/lib/wasi-tests/wasitests/path_link.wasm b/lib/wasi-tests/wasitests/path_link.wasm index 822c78ab3..eae511069 100755 Binary files a/lib/wasi-tests/wasitests/path_link.wasm and b/lib/wasi-tests/wasitests/path_link.wasm differ diff --git a/lib/wasi-tests/wasitests/path_rename.wasm b/lib/wasi-tests/wasitests/path_rename.wasm index 40197b531..7e613b960 100755 Binary files a/lib/wasi-tests/wasitests/path_rename.wasm and b/lib/wasi-tests/wasitests/path_rename.wasm differ diff --git a/lib/wasi-tests/wasitests/path_symlink.wasm b/lib/wasi-tests/wasitests/path_symlink.wasm index 3a773eaed..c30b16378 100755 Binary files a/lib/wasi-tests/wasitests/path_symlink.wasm and b/lib/wasi-tests/wasitests/path_symlink.wasm differ diff --git a/lib/wasi-tests/wasitests/poll_oneoff.wasm b/lib/wasi-tests/wasitests/poll_oneoff.wasm index 950345d0b..009a57e80 100755 Binary files a/lib/wasi-tests/wasitests/poll_oneoff.wasm and b/lib/wasi-tests/wasitests/poll_oneoff.wasm differ diff --git a/lib/wasi-tests/wasitests/quine.wasm b/lib/wasi-tests/wasitests/quine.wasm index fc6361b5c..21fb813a3 100755 Binary files a/lib/wasi-tests/wasitests/quine.wasm and b/lib/wasi-tests/wasitests/quine.wasm differ diff --git a/lib/wasi-tests/wasitests/readlink.wasm b/lib/wasi-tests/wasitests/readlink.wasm index 67ada4404..d8ef0c74f 100755 Binary files a/lib/wasi-tests/wasitests/readlink.wasm and b/lib/wasi-tests/wasitests/readlink.wasm differ diff --git a/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm index 4c819b4cc..0abf77294 100755 Binary files a/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm and b/lib/wasi-tests/wasitests/wasi_sees_virtual_root.wasm differ diff --git a/lib/wasi-tests/wasitests/writing.wasm b/lib/wasi-tests/wasitests/writing.wasm index 148add04d..0b8b96abc 100755 Binary files a/lib/wasi-tests/wasitests/writing.wasm and b/lib/wasi-tests/wasitests/writing.wasm differ diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index 26a09c5ba..f118c1b1c 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -1,21 +1,25 @@ [package] name = "wasmer-wasi" -version = "0.6.0" +version = "0.11.0" description = "Wasmer runtime WASI implementation library" license = "MIT" authors = ["The Wasmer Engineering Team "] repository = "https://github.com/wasmerio/wasmer" +keywords = ["wasm", "webassembly", "wasi", "sandbox", "ABI"] +categories = ["wasm"] edition = "2018" [dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } +bincode = "1" +byteorder = "1.3" +generational-arena = { version = "0.2", features = ["serde"] } libc = "0.2.60" -rand = "0.7.0" -# wasmer-runtime-abi = { path = "../runtime-abi" } -generational-arena = "0.2.2" -log = "0.4.8" -byteorder = "1.3.2" -time = "0.1.42" +log = "0.4" +getrandom = "0.1" +time = "0.1" +typetag = "0.1" +serde = { version = "1", features = ["derive"] } +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } [target.'cfg(windows)'.dependencies] -winapi = "0.3.7" +winapi = "0.3" diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index afd37a922..062ad0d4a 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/src/lib.rs @@ -7,6 +7,19 @@ unused_unsafe, unreachable_patterns )] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + +//! Wasmer's WASI implementation +//! +//! Use `generate_import_object` to create an [`ImportObject`]. This [`ImportObject`] +//! can be combined with a module to create an `Instance` which can execute WASI +//! Wasm functions. +//! +//! See `state` for the experimental WASI FS API. Also see the +//! [WASI plugin example](https://github.com/wasmerio/wasmer/blob/master/examples/plugin.rs) +//! for an example of how to extend WASI using the WASI FS API. + #[cfg(target = "windows")] extern crate winapi; @@ -24,7 +37,7 @@ use self::syscalls::*; use std::ffi::c_void; use std::path::PathBuf; -pub use self::utils::is_wasi_module; +pub use self::utils::{get_wasi_version, is_wasi_module, WasiVersion}; use wasmer_runtime_core::{func, import::ImportObject, imports}; @@ -34,42 +47,41 @@ pub struct ExitCode { pub code: syscalls::types::__wasi_exitcode_t, } +/// Creates a Wasi [`ImportObject`] with [`WasiState`] with the latest snapshot +/// of WASI. pub fn generate_import_object( args: Vec>, envs: Vec>, - preopened_files: Vec, + preopened_files: Vec, mapped_dirs: Vec<(String, PathBuf)>, ) -> ImportObject { - generate_import_object_with_fs_setup(args, envs, preopened_files, mapped_dirs, None) + generate_import_object_for_version( + WasiVersion::Latest, + args, + envs, + preopened_files, + mapped_dirs, + ) } -pub fn generate_import_object_with_fs_setup( - args: Vec>, - envs: Vec>, - preopened_files: Vec, - mapped_dirs: Vec<(String, PathBuf)>, - setup_fs: Option Result<(), String>>>, +pub fn generate_import_object_from_state( + wasi_state: WasiState, + _version: WasiVersion, ) -> ImportObject { + // THIS IS DEVELOPMENT CODE; DO NOT SHIP + let wasi_state_bytes = wasi_state.freeze().unwrap(); let state_gen = move || { fn state_destructor(data: *mut c_void) { unsafe { - drop(Box::from_raw(data as *mut WasiState)); + drop(std::rc::Rc::from_raw(data as *mut WasiState)); } } - let mut fs = WasiFs::new(&preopened_files, &mapped_dirs).unwrap(); - if let Some(sfn) = &setup_fs { - sfn(&mut fs).unwrap(); - } - - let state = Box::new(WasiState { - fs, - args: &args[..], - envs: &envs[..], - }); + let wasi_state = Box::new(WasiState::unfreeze(&wasi_state_bytes).unwrap()); + dbg!(&wasi_state); ( - Box::leak(state) as *mut WasiState as *mut c_void, + Box::into_raw(wasi_state) as *mut c_void, state_destructor as fn(*mut c_void), ) }; @@ -77,6 +89,94 @@ pub fn generate_import_object_with_fs_setup( // This generates the wasi state. state_gen, "wasi_unstable" => { + "args_get" => func!(args_get), + "args_sizes_get" => func!(args_sizes_get), + "clock_res_get" => func!(clock_res_get), + "clock_time_get" => func!(clock_time_get), + "environ_get" => func!(environ_get), + "environ_sizes_get" => func!(environ_sizes_get), + "fd_advise" => func!(fd_advise), + "fd_allocate" => func!(fd_allocate), + "fd_close" => func!(fd_close), + "fd_datasync" => func!(fd_datasync), + "fd_fdstat_get" => func!(fd_fdstat_get), + "fd_fdstat_set_flags" => func!(fd_fdstat_set_flags), + "fd_fdstat_set_rights" => func!(fd_fdstat_set_rights), + "fd_filestat_get" => func!(legacy::snapshot0::fd_filestat_get), + "fd_filestat_set_size" => func!(fd_filestat_set_size), + "fd_filestat_set_times" => func!(fd_filestat_set_times), + "fd_pread" => func!(fd_pread), + "fd_prestat_get" => func!(fd_prestat_get), + "fd_prestat_dir_name" => func!(fd_prestat_dir_name), + "fd_pwrite" => func!(fd_pwrite), + "fd_read" => func!(fd_read), + "fd_readdir" => func!(fd_readdir), + "fd_renumber" => func!(fd_renumber), + "fd_seek" => func!(legacy::snapshot0::fd_seek), + "fd_sync" => func!(fd_sync), + "fd_tell" => func!(fd_tell), + "fd_write" => func!(fd_write), + "path_create_directory" => func!(path_create_directory), + "path_filestat_get" => func!(legacy::snapshot0::path_filestat_get), + "path_filestat_set_times" => func!(path_filestat_set_times), + "path_link" => func!(path_link), + "path_open" => func!(path_open), + "path_readlink" => func!(path_readlink), + "path_remove_directory" => func!(path_remove_directory), + "path_rename" => func!(path_rename), + "path_symlink" => func!(path_symlink), + "path_unlink_file" => func!(path_unlink_file), + "poll_oneoff" => func!(legacy::snapshot0::poll_oneoff), + "proc_exit" => func!(proc_exit), + "proc_raise" => func!(proc_raise), + "random_get" => func!(random_get), + "sched_yield" => func!(sched_yield), + "sock_recv" => func!(sock_recv), + "sock_send" => func!(sock_send), + "sock_shutdown" => func!(sock_shutdown), + }, + } +} + +/* +pub fn generate_import_object_with_fs_setup( + args: Vec>, + envs: Vec>, + preopened_files: Vec, + mapped_dirs: Vec<(String, PathBuf)>, + setup_fs: Option Result<(), String> + Send>>, +) -> ImportObject { + let state_gen = move || { + // TODO: look into removing all these unnecessary clones + fn state_destructor(data: *mut c_void) { + unsafe { + drop(Box::from_raw(data as *mut WasiState)); + } + } + let preopened_files = preopened_files.clone(); + let mapped_dirs = mapped_dirs.clone(); + //let wasi_builder = create_wasi_instance(); + + let mut fs = WasiFs::new(&preopened_files, &mapped_dirs).unwrap(); + if let Some(sfn) = &setup_fs { + sfn(&mut fs).unwrap(); + } + + let state = Box::new(WasiState { + fs: WasiFs::new(&preopened_files, &mapped_dirs).expect("Could not create WASI FS"), + args: args.clone(), + envs: envs.clone(), + }); + + ( + Box::into_raw(state) as *mut c_void, + state_destructor as fn(*mut c_void), + ) + }; + imports! { + // This generates the wasi state. + state_gen, + "wasi_snapshot_preview1" => { "args_get" => func!(args_get), "args_sizes_get" => func!(args_sizes_get), "clock_res_get" => func!(clock_res_get), @@ -124,4 +224,104 @@ pub fn generate_import_object_with_fs_setup( "sock_shutdown" => func!(sock_shutdown), }, } +}*/ + +/// Creates a Wasi [`ImportObject`] with [`WasiState`] for the given [`WasiVersion`]. +pub fn generate_import_object_for_version( + version: WasiVersion, + args: Vec>, + envs: Vec>, + preopened_files: Vec, + mapped_dirs: Vec<(String, PathBuf)>, +) -> ImportObject { + match version { + WasiVersion::Snapshot0 => { + generate_import_object_snapshot0(args, envs, preopened_files, mapped_dirs) + } + WasiVersion::Snapshot1 | WasiVersion::Latest => { + generate_import_object(args, envs, preopened_files, mapped_dirs) + } + } +} + +/// Creates a legacy Wasi [`ImportObject`] with [`WasiState`]. +fn generate_import_object_snapshot0( + args: Vec>, + envs: Vec>, + preopened_files: Vec, + mapped_dirs: Vec<(String, PathBuf)>, +) -> ImportObject { + let state_gen = move || { + // TODO: look into removing all these unnecessary clones + fn state_destructor(data: *mut c_void) { + unsafe { + drop(Box::from_raw(data as *mut WasiState)); + } + } + let preopened_files = preopened_files.clone(); + let mapped_dirs = mapped_dirs.clone(); + //let wasi_builder = create_wasi_instance(); + + let state = Box::new(WasiState { + fs: WasiFs::new(&preopened_files, &mapped_dirs).expect("Could not create WASI FS"), + args: args.clone(), + envs: envs.clone(), + }); + + ( + Box::into_raw(state) as *mut c_void, + state_destructor as fn(*mut c_void), + ) + }; + imports! { + // This generates the wasi state. + state_gen, + "wasi_unstable" => { + "args_get" => func!(args_get), + "args_sizes_get" => func!(args_sizes_get), + "clock_res_get" => func!(clock_res_get), + "clock_time_get" => func!(clock_time_get), + "environ_get" => func!(environ_get), + "environ_sizes_get" => func!(environ_sizes_get), + "fd_advise" => func!(fd_advise), + "fd_allocate" => func!(fd_allocate), + "fd_close" => func!(fd_close), + "fd_datasync" => func!(fd_datasync), + "fd_fdstat_get" => func!(fd_fdstat_get), + "fd_fdstat_set_flags" => func!(fd_fdstat_set_flags), + "fd_fdstat_set_rights" => func!(fd_fdstat_set_rights), + "fd_filestat_get" => func!(legacy::snapshot0::fd_filestat_get), + "fd_filestat_set_size" => func!(fd_filestat_set_size), + "fd_filestat_set_times" => func!(fd_filestat_set_times), + "fd_pread" => func!(fd_pread), + "fd_prestat_get" => func!(fd_prestat_get), + "fd_prestat_dir_name" => func!(fd_prestat_dir_name), + "fd_pwrite" => func!(fd_pwrite), + "fd_read" => func!(fd_read), + "fd_readdir" => func!(fd_readdir), + "fd_renumber" => func!(fd_renumber), + "fd_seek" => func!(legacy::snapshot0::fd_seek), + "fd_sync" => func!(fd_sync), + "fd_tell" => func!(fd_tell), + "fd_write" => func!(fd_write), + "path_create_directory" => func!(path_create_directory), + "path_filestat_get" => func!(legacy::snapshot0::path_filestat_get), + "path_filestat_set_times" => func!(path_filestat_set_times), + "path_link" => func!(path_link), + "path_open" => func!(path_open), + "path_readlink" => func!(path_readlink), + "path_remove_directory" => func!(path_remove_directory), + "path_rename" => func!(path_rename), + "path_symlink" => func!(path_symlink), + "path_unlink_file" => func!(path_unlink_file), + "poll_oneoff" => func!(legacy::snapshot0::poll_oneoff), + "proc_exit" => func!(proc_exit), + "proc_raise" => func!(proc_raise), + "random_get" => func!(random_get), + "sched_yield" => func!(sched_yield), + "sock_recv" => func!(sock_recv), + "sock_send" => func!(sock_send), + "sock_shutdown" => func!(sock_shutdown), + }, + } } diff --git a/lib/wasi/src/state/builder.rs b/lib/wasi/src/state/builder.rs new file mode 100644 index 000000000..572197382 --- /dev/null +++ b/lib/wasi/src/state/builder.rs @@ -0,0 +1,333 @@ +//! Builder code for [`WasiState`] + +use crate::state::{WasiFs, WasiState}; +use std::path::{Path, PathBuf}; +use std::rc::Rc; + +/// Creates an empty [`WasiStateBuilder`]. +pub(crate) fn create_wasi_state(program_name: &str) -> WasiStateBuilder { + WasiStateBuilder { + args: vec![program_name.bytes().collect()], + ..WasiStateBuilder::default() + } +} + +/// Type for building an instance of [`WasiState`] +#[derive(Default, Clone)] +pub struct WasiStateBuilder { + args: Vec>, + envs: Vec>, + preopened_files: Vec, + mapped_dirs: Vec<(String, PathBuf)>, + setup_fs_fn: Option Result<(), String> + Send>>, +} + +impl std::fmt::Debug for WasiStateBuilder { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("WasiStateBuilder") + .field("args", &self.args) + .field("envs", &self.envs) + .field("preopend_files", &self.preopened_files) + .field("mapped_dirs", &self.mapped_dirs) + .field("setup_fs_fn exists", &self.setup_fs_fn.is_some()) + .finish() + } +} + +/// Error type returned when bad data is given to [`WasiStateBuilder`]. +#[derive(Debug, PartialEq, Eq)] +pub enum WasiStateCreationError { + EnvironmentVariableFormatError(String), + ArgumentContainsNulByte(String), + PreopenedDirectoryNotFound(PathBuf), + MappedDirAliasFormattingError(String), + WasiFsCreationError(String), + WasiFsSetupError(String), +} + +fn validate_mapped_dir_alias(alias: &str) -> Result<(), WasiStateCreationError> { + for byte in alias.bytes() { + match byte { + b'/' => { + return Err(WasiStateCreationError::MappedDirAliasFormattingError( + format!("Alias \"{}\" contains the character '/'", alias), + )); + } + b'\0' => { + return Err(WasiStateCreationError::MappedDirAliasFormattingError( + format!("Alias \"{}\" contains a nul byte", alias), + )); + } + _ => (), + } + } + + Ok(()) +} + +// TODO add other WasiFS APIs here like swapping out stdout, for example (though we need to +// return stdout somehow, it's unclear what that API should look like) +impl WasiStateBuilder { + /// Add an environment variable pair. + /// Environment variable keys and values must not contain the byte `=` (0x3d) + /// or nul (0x0). + pub fn env(&mut self, key: Key, value: Value) -> &mut Self + where + Key: AsRef<[u8]>, + Value: AsRef<[u8]>, + { + let key_b = key.as_ref(); + let val_b = value.as_ref(); + + let length = key_b.len() + val_b.len() + 1; + let mut byte_vec = Vec::with_capacity(length); + + byte_vec.extend_from_slice(&key_b); + byte_vec.push(b'='); + byte_vec.extend_from_slice(&val_b); + + self.envs.push(byte_vec); + + self + } + + /// Add an argument. + /// Arguments must not contain the nul (0x0) byte + pub fn arg(&mut self, arg: Arg) -> &mut Self + where + Arg: AsRef<[u8]>, + { + let arg_b = arg.as_ref(); + let mut byte_vec = Vec::with_capacity(arg_b.len()); + byte_vec.extend_from_slice(&arg_b); + self.args.push(byte_vec); + + self + } + + /// Add multiple environment variable pairs. + /// Keys and values must not contain the `=` (0x3d) or nul (0x0) byte. + pub fn envs(&mut self, env_pairs: I) -> &mut Self + where + I: IntoIterator, + Key: AsRef<[u8]>, + Value: AsRef<[u8]>, + { + for (key, value) in env_pairs { + let key_b = key.as_ref(); + let val_b = value.as_ref(); + + let length = key_b.len() + val_b.len() + 1; + let mut byte_vec = Vec::with_capacity(length); + + byte_vec.extend_from_slice(&key_b); + byte_vec.push(b'='); + byte_vec.extend_from_slice(&val_b); + + self.envs.push(byte_vec); + } + + self + } + + /// Add multiple arguments. + /// Arguments must not contain the nul (0x0) byte + pub fn args(&mut self, args: I) -> &mut Self + where + I: IntoIterator, + Arg: AsRef<[u8]>, + { + for arg in args { + let arg_b = arg.as_ref(); + let mut byte_vec = Vec::with_capacity(arg_b.len()); + byte_vec.extend_from_slice(&arg_b); + self.args.push(byte_vec); + } + + self + } + + /// Preopen a directory + /// This opens the given directory at the virtual root, `/`, and allows + /// the WASI module to read and write to the given directory. + // TODO: design a simple API for passing in permissions here (i.e. read-only) + pub fn preopen_dir(&mut self, po_dir: FilePath) -> &mut Self + where + FilePath: AsRef, + { + let path = po_dir.as_ref(); + self.preopened_files.push(path.to_path_buf()); + + self + } + + /// Preopen a directory + /// This opens the given directory at the virtual root, `/`, and allows + /// the WASI module to read and write to the given directory. + pub fn preopen_dirs(&mut self, po_dirs: I) -> &mut Self + where + I: IntoIterator, + FilePath: AsRef, + { + for po_dir in po_dirs { + let path = po_dir.as_ref(); + self.preopened_files.push(path.to_path_buf()); + } + + self + } + + /// Preopen a directory with a different name exposed to the WASI. + pub fn map_dir(&mut self, alias: &str, po_dir: FilePath) -> &mut Self + where + FilePath: AsRef, + { + let path = po_dir.as_ref(); + self.mapped_dirs + .push((alias.to_string(), path.to_path_buf())); + + self + } + + /// Preopen directorys with a different names exposed to the WASI. + pub fn map_dirs(&mut self, mapped_dirs: I) -> &mut Self + where + I: IntoIterator, + FilePath: AsRef, + { + for (alias, dir) in mapped_dirs { + let path = dir.as_ref(); + self.mapped_dirs.push((alias, path.to_path_buf())); + } + + self + } + + /// Setup the WASI filesystem before running + // TODO: improve ergonomics on this function + pub fn setup_fs( + &mut self, + setup_fs_fn: Rc Result<(), String> + Send>, + ) -> &mut Self { + self.setup_fs_fn = Some(setup_fs_fn); + + self + } + + /// Consumes the [`WasiStateBuilder`] and produces a [`WasiState`] + /// + /// Returns the error from `WasiFs::new` if there's an error + pub fn build(&mut self) -> Result { + for (i, arg) in self.args.iter().enumerate() { + for b in arg.iter() { + if *b == 0 { + return Err(WasiStateCreationError::ArgumentContainsNulByte( + std::str::from_utf8(arg) + .unwrap_or(if i == 0 { + "Inner error: program name is invalid utf8!" + } else { + "Inner error: arg is invalid utf8!" + }) + .to_string(), + )); + } + } + } + for env in self.envs.iter() { + let mut eq_seen = false; + for b in env.iter() { + match *b { + b'=' => { + if eq_seen { + return Err(WasiStateCreationError::EnvironmentVariableFormatError( + format!( + "found '=' in env var string \"{}\" (key=value)", + std::str::from_utf8(env) + .unwrap_or("Inner error: env var is invalid_utf8!") + ), + )); + } + eq_seen = true; + } + 0 => { + return Err(WasiStateCreationError::EnvironmentVariableFormatError( + format!( + "found nul byte in env var string \"{}\" (key=value)", + std::str::from_utf8(env) + .unwrap_or("Inner error: env var is invalid_utf8!") + ), + )); + } + _ => (), + } + } + } + + for po_f in self.preopened_files.iter() { + if !po_f.exists() { + return Err(WasiStateCreationError::PreopenedDirectoryNotFound( + po_f.clone(), + )); + } + } + + for (alias, po_f) in self.mapped_dirs.iter() { + if !po_f.exists() { + return Err(WasiStateCreationError::PreopenedDirectoryNotFound( + po_f.clone(), + )); + } + validate_mapped_dir_alias(&alias)?; + } + let mut wasi_fs = WasiFs::new(&self.preopened_files, &self.mapped_dirs) + .map_err(WasiStateCreationError::WasiFsCreationError)?; + if let Some(f) = &self.setup_fs_fn { + f(&mut wasi_fs).map_err(WasiStateCreationError::WasiFsSetupError)?; + } + Ok(WasiState { + fs: wasi_fs, + args: self.args.clone(), + envs: self.envs.clone(), + }) + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn env_var_errors() { + let output = create_wasi_state("test_prog") + .env("HOM=E", "/home/home") + .build(); + match output { + Err(WasiStateCreationError::EnvironmentVariableFormatError(_)) => assert!(true), + _ => assert!(false), + } + + let output = create_wasi_state("test_prog") + .env("HOME\0", "/home/home") + .build(); + match output { + Err(WasiStateCreationError::EnvironmentVariableFormatError(_)) => assert!(true), + _ => assert!(false), + } + } + + #[test] + fn nul_character_in_args() { + let output = create_wasi_state("test_prog").arg("--h\0elp").build(); + match output { + Err(WasiStateCreationError::ArgumentContainsNulByte(_)) => assert!(true), + _ => assert!(false), + } + let output = create_wasi_state("test_prog") + .args(&["--help", "--wat\0"]) + .build(); + match output { + Err(WasiStateCreationError::ArgumentContainsNulByte(_)) => assert!(true), + _ => assert!(false), + } + } +} diff --git a/lib/wasi/src/state/mod.rs b/lib/wasi/src/state/mod.rs index 14bc7d306..f89d38866 100644 --- a/lib/wasi/src/state/mod.rs +++ b/lib/wasi/src/state/mod.rs @@ -1,19 +1,33 @@ //! WARNING: the API exposed here is unstable and very experimental. Certain things are not ready //! yet and may be broken in patch releases. If you're using this and have any specific needs, //! please let us know here https://github.com/wasmerio/wasmer/issues/583 or by filing an issue. +//! +//! Wasmer always has a virtual root directory located at `/` at which all pre-opened directories can +//! be found. It's possible to traverse between preopened directories this way as well (for example +//! `preopen-dir1/../preopen-dir2`). +//! +//! A preopened directory is a directory or directory + name combination passed into the +//! `generate_import_object` function. These are directories that the caller has given +//! the WASI module permission to access. +//! +//! You can implement `WasiFile` for your own types to get custom behavior and extend WASI, see the +//! [WASI plugin example](https://github.com/wasmerio/wasmer/blob/master/examples/plugin.rs). +mod builder; mod types; +pub use self::builder::*; pub use self::types::*; use crate::syscalls::types::*; use generational_arena::Arena; pub use generational_arena::Index as Inode; +use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::{ borrow::Borrow, cell::Cell, fs, - io::{self, Write}, + io::Write, path::{Path, PathBuf}, time::SystemTime, }; @@ -23,6 +37,19 @@ use wasmer_runtime_core::{debug, vm::Ctx}; pub const VIRTUAL_ROOT_FD: __wasi_fd_t = 3; /// all the rights enabled pub const ALL_RIGHTS: __wasi_rights_t = 0x1FFFFFFF; +const STDIN_DEFAULT_RIGHTS: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC + | __WASI_RIGHT_FD_READ + | __WASI_RIGHT_FD_SYNC + | __WASI_RIGHT_FD_ADVISE + | __WASI_RIGHT_FD_FILESTAT_GET + | __WASI_RIGHT_POLL_FD_READWRITE; +const STDOUT_DEFAULT_RIGHTS: __wasi_rights_t = __WASI_RIGHT_FD_DATASYNC + | __WASI_RIGHT_FD_WRITE + | __WASI_RIGHT_FD_SYNC + | __WASI_RIGHT_FD_ADVISE + | __WASI_RIGHT_FD_FILESTAT_GET + | __WASI_RIGHT_POLL_FD_READWRITE; +const STDERR_DEFAULT_RIGHTS: __wasi_rights_t = STDOUT_DEFAULT_RIGHTS; /// Get WasiState from a Ctx /// This function is unsafe because it must be called on a WASI Ctx @@ -35,7 +62,7 @@ pub unsafe fn get_wasi_state(ctx: &mut Ctx) -> &mut WasiState { pub const MAX_SYMLINKS: u32 = 128; /// A file that Wasi knows about that may or may not be open -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct InodeVal { pub stat: __wasi_filestat_t, pub is_preopened: bool, @@ -43,32 +70,15 @@ pub struct InodeVal { pub kind: Kind, } -/*impl WasiFdBacking for InodeVal { - fn get_stat(&self) -> &__wasi_filestat_t { - &self.stat - } - - fn get_stat_mut(&mut self) -> &mut __wasi_filestat_t { - &mut self.stat - } - - fn is_preopened(&self) -> bool { - self.is_preopened - } - - fn get_name(&self) -> &str { - self.name.as_ref() - } -}*/ - -#[allow(dead_code)] -#[derive(Debug)] +/// The core of the filesystem abstraction. Includes directories, +/// files, and symlinks. +#[derive(Debug, Serialize, Deserialize)] pub enum Kind { File { /// the open file, if it's open handle: Option>, /// The path on the host system where the file is located - /// This is deprecated and will be removed in 0.7.0 or a shortly thereafter + /// This is deprecated and will be removed soon path: PathBuf, /// Marks the file as a special file that only one `fd` can exist for /// This is useful when dealing with host-provided special files that @@ -111,16 +121,26 @@ pub enum Kind { }, } -#[derive(Debug)] +#[derive(Debug, Serialize, Deserialize)] pub struct Fd { pub rights: __wasi_rights_t, pub rights_inheriting: __wasi_rights_t, pub flags: __wasi_fdflags_t, pub offset: u64, + /// Used when reopening the file on the host system + pub open_flags: u16, pub inode: Inode, } -#[derive(Debug)] +impl Fd { + pub const READ: u16 = 1; + pub const WRITE: u16 = 2; + pub const APPEND: u16 = 4; + pub const TRUNCATE: u16 = 8; + pub const CREATE: u16 = 16; +} + +#[derive(Debug, Serialize, Deserialize)] /// Warning, modifying these fields directly may cause invariants to break and /// should be considered unsafe. These fields may be made private in a future release pub struct WasiFs { @@ -133,15 +153,11 @@ pub struct WasiFs { inode_counter: Cell, /// for fds still open after the file has been deleted pub orphan_fds: HashMap, - - pub stdout: Box, - pub stderr: Box, - pub stdin: Box, } impl WasiFs { pub fn new( - preopened_dirs: &[String], + preopened_dirs: &[PathBuf], mapped_dirs: &[(String, PathBuf)], ) -> Result { debug!("wasi::fs::inodes"); @@ -154,11 +170,11 @@ impl WasiFs { next_fd: Cell::new(3), inode_counter: Cell::new(1024), orphan_fds: HashMap::new(), - - stdin: Box::new(Stdin(io::stdin())), - stdout: Box::new(Stdout(io::stdout())), - stderr: Box::new(Stderr(io::stderr())), }; + wasi_fs.create_stdin(); + wasi_fs.create_stdout(); + wasi_fs.create_stderr(); + // create virtual root let root_inode = { let all_rights = 0x1FFFFFFF; @@ -181,34 +197,39 @@ impl WasiFs { & (!__WASI_RIGHT_PATH_REMOVE_DIRECTORY)*/; let inode = wasi_fs.create_virtual_root(); let fd = wasi_fs - .create_fd(root_rights, root_rights, 0, inode) - .expect("Could not create root fd"); + .create_fd(root_rights, root_rights, 0, Fd::READ, inode) + .map_err(|e| format!("Could not create root fd: {}", e))?; wasi_fs.preopen_fds.push(fd); inode }; debug!("wasi::fs::preopen_dirs"); for dir in preopened_dirs { - debug!("Attempting to preopen {}", &dir); + debug!("Attempting to preopen {}", &dir.to_string_lossy()); // TODO: think about this let default_rights = 0x1FFFFFFF; // all rights - let cur_dir = PathBuf::from(dir); - let cur_dir_metadata = cur_dir.metadata().expect("Could not find directory"); + let cur_dir_metadata = dir.metadata().map_err(|e| { + format!( + "Could not get metadata for file {:?}: {}", + dir, + e.to_string() + ) + })?; let kind = if cur_dir_metadata.is_dir() { Kind::Dir { parent: Some(root_inode), - path: cur_dir.clone(), + path: dir.clone(), entries: Default::default(), } } else { return Err(format!( "WASI only supports pre-opened directories right now; found \"{}\"", - &dir + &dir.to_string_lossy() )); }; // TODO: handle nested pats in `file` let inode = wasi_fs - .create_inode(kind, true, dir.to_string()) + .create_inode(kind, true, dir.to_string_lossy().into_owned()) .map_err(|e| { format!( "Failed to create inode for preopened dir: WASI error code: {}", @@ -216,11 +237,19 @@ impl WasiFs { ) })?; let fd = wasi_fs - .create_fd(default_rights, default_rights, 0, inode) - .expect("Could not open fd"); + .create_fd( + default_rights, + default_rights, + 0, + Fd::READ | Fd::WRITE, + inode, + ) + .map_err(|e| format!("Could not open fd for file {:?}: {}", dir, e))?; if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { // todo handle collisions - assert!(entries.insert(dir.to_string(), inode).is_none()) + assert!(entries + .insert(dir.to_string_lossy().into_owned(), inode) + .is_none()) } wasi_fs.preopen_fds.push(fd); } @@ -229,9 +258,13 @@ impl WasiFs { debug!("Attempting to open {:?} at {}", real_dir, alias); // TODO: think about this let default_rights = 0x1FFFFFFF; // all rights - let cur_dir_metadata = real_dir - .metadata() - .expect("mapped dir not at previously verified location"); + let cur_dir_metadata = real_dir.metadata().map_err(|e| { + format!( + "Could not get metadata for file {:?}: {}", + &real_dir, + e.to_string() + ) + })?; let kind = if cur_dir_metadata.is_dir() { Kind::Dir { parent: Some(root_inode), @@ -254,8 +287,14 @@ impl WasiFs { ) })?; let fd = wasi_fs - .create_fd(default_rights, default_rights, 0, inode) - .expect("Could not open fd"); + .create_fd( + default_rights, + default_rights, + 0, + Fd::READ | Fd::WRITE, + inode, + ) + .map_err(|e| format!("Could not open fd for file {:?}: {}", &real_dir, e))?; if let Kind::Root { entries } = &mut wasi_fs.inodes[root_inode].kind { // todo handle collisions assert!(entries.insert(alias.clone(), inode).is_none()); @@ -267,6 +306,67 @@ impl WasiFs { Ok(wasi_fs) } + /// Get the `WasiFile` object at stdout + pub fn stdout(&self) -> Result<&Option>, WasiFsError> { + self.std_dev_get(__WASI_STDOUT_FILENO) + } + /// Get the `WasiFile` object at stdout mutably + pub fn stdout_mut(&mut self) -> Result<&mut Option>, WasiFsError> { + self.std_dev_get_mut(__WASI_STDOUT_FILENO) + } + + /// Get the `WasiFile` object at stderr + pub fn stderr(&self) -> Result<&Option>, WasiFsError> { + self.std_dev_get(__WASI_STDERR_FILENO) + } + /// Get the `WasiFile` object at stderr mutably + pub fn stderr_mut(&mut self) -> Result<&mut Option>, WasiFsError> { + self.std_dev_get_mut(__WASI_STDERR_FILENO) + } + + /// Get the `WasiFile` object at stdin + pub fn stdin(&self) -> Result<&Option>, WasiFsError> { + self.std_dev_get(__WASI_STDIN_FILENO) + } + /// Get the `WasiFile` object at stdin mutably + pub fn stdin_mut(&mut self) -> Result<&mut Option>, WasiFsError> { + self.std_dev_get_mut(__WASI_STDIN_FILENO) + } + + /// Internal helper function to get a standard device handle. + /// Expects one of `__WASI_STDIN_FILENO`, `__WASI_STDOUT_FILENO`, `__WASI_STDERR_FILENO`. + fn std_dev_get(&self, fd: __wasi_fd_t) -> Result<&Option>, WasiFsError> { + if let Some(fd) = self.fd_map.get(&fd) { + if let Kind::File { ref handle, .. } = self.inodes[fd.inode].kind { + Ok(handle) + } else { + // Our public API should ensure that this is not possible + unreachable!("Non-file found in standard device location") + } + } else { + // this should only trigger if we made a mistake in this crate + Err(WasiFsError::NoDevice) + } + } + /// Internal helper function to mutably get a standard device handle. + /// Expects one of `__WASI_STDIN_FILENO`, `__WASI_STDOUT_FILENO`, `__WASI_STDERR_FILENO`. + fn std_dev_get_mut( + &mut self, + fd: __wasi_fd_t, + ) -> Result<&mut Option>, WasiFsError> { + if let Some(fd) = self.fd_map.get_mut(&fd) { + if let Kind::File { ref mut handle, .. } = self.inodes[fd.inode].kind { + Ok(handle) + } else { + // Our public API should ensure that this is not possible + unreachable!("Non-file found in standard device location") + } + } else { + // this should only trigger if we made a mistake in this crate + Err(WasiFsError::NoDevice) + } + } + fn get_next_inode_index(&mut self) -> u64 { let next = self.inode_counter.get(); self.inode_counter.set(next + 1); @@ -327,8 +427,15 @@ impl WasiFs { } } - self.create_fd(rights, rights_inheriting, flags, cur_inode) - .map_err(WasiFsError::from_wasi_err) + // TODO: review open flags (read, write); they were added without consideration + self.create_fd( + rights, + rights_inheriting, + flags, + Fd::READ | Fd::WRITE, + cur_inode, + ) + .map_err(WasiFsError::from_wasi_err) } /// Opens a user-supplied file in the directory specified with the @@ -339,6 +446,7 @@ impl WasiFs { &mut self, base: __wasi_fd_t, file: Box, + open_flags: u16, name: String, rights: __wasi_rights_t, rights_inheriting: __wasi_rights_t, @@ -376,7 +484,7 @@ impl WasiFs { _ => unreachable!("Dir or Root became not Dir or Root"), } - self.create_fd(rights, rights_inheriting, flags, inode) + self.create_fd(rights, rights_inheriting, flags, open_flags, inode) .map_err(WasiFsError::from_wasi_err) } _ => Err(WasiFsError::BaseNotDirectory), @@ -392,21 +500,16 @@ impl WasiFs { fd: __wasi_fd_t, file: Box, ) -> Result>, WasiFsError> { + let mut ret = Some(file); match fd { __WASI_STDIN_FILENO => { - let mut ret = file; - std::mem::swap(&mut self.stdin, &mut ret); - Ok(Some(ret)) + std::mem::swap(self.stdin_mut()?, &mut ret); } __WASI_STDOUT_FILENO => { - let mut ret = file; - std::mem::swap(&mut self.stdout, &mut ret); - Ok(Some(ret)) + std::mem::swap(self.stdout_mut()?, &mut ret); } __WASI_STDERR_FILENO => { - let mut ret = file; - std::mem::swap(&mut self.stderr, &mut ret); - Ok(Some(ret)) + std::mem::swap(self.stderr_mut()?, &mut ret); } _ => { let base_fd = self.get_fd(fd).map_err(WasiFsError::from_wasi_err)?; @@ -414,14 +517,14 @@ impl WasiFs { match &mut self.inodes[base_inode].kind { Kind::File { ref mut handle, .. } => { - let mut ret = Some(file); std::mem::swap(handle, &mut ret); - Ok(ret) } _ => return Err(WasiFsError::NotAFile), } } } + + Ok(ret) } /// refresh size from filesystem @@ -715,7 +818,7 @@ impl WasiFs { // even if it's false, it still follows symlinks, just not the last // symlink so // This will be resolved when we have tests asserting the correct behavior - pub fn get_inode_at_path( + pub(crate) fn get_inode_at_path( &mut self, base: __wasi_fd_t, path: &str, @@ -726,7 +829,7 @@ impl WasiFs { /// Returns the parent Dir or Root that the file at a given path is in and the file name /// stripped off - pub fn get_parent_inode_at_path( + pub(crate) fn get_parent_inode_at_path( &mut self, base: __wasi_fd_t, path: &Path, @@ -768,6 +871,17 @@ impl WasiFs { } pub fn fdstat(&self, fd: __wasi_fd_t) -> Result<__wasi_fdstat_t, __wasi_errno_t> { + match fd { + __WASI_STDOUT_FILENO => { + return Ok(__wasi_fdstat_t { + fs_filetype: __WASI_FILETYPE_CHARACTER_DEVICE, + fs_flags: 0, + fs_rights_base: ALL_RIGHTS, + fs_rights_inheriting: ALL_RIGHTS, + }) + } + _ => (), + } let fd = self.get_fd(fd)?; debug!("fdstat: {:?}", fd); @@ -808,8 +922,18 @@ impl WasiFs { pub fn flush(&mut self, fd: __wasi_fd_t) -> Result<(), __wasi_errno_t> { match fd { __WASI_STDIN_FILENO => (), - __WASI_STDOUT_FILENO => self.stdout.flush().map_err(|_| __WASI_EIO)?, - __WASI_STDERR_FILENO => self.stderr.flush().map_err(|_| __WASI_EIO)?, + __WASI_STDOUT_FILENO => self + .stdout_mut() + .map_err(WasiFsError::into_wasi_err)? + .as_mut() + .and_then(|f| f.flush().ok()) + .ok_or(__WASI_EIO)?, + __WASI_STDERR_FILENO => self + .stderr_mut() + .map_err(WasiFsError::into_wasi_err)? + .as_mut() + .and_then(|f| f.flush().ok()) + .ok_or(__WASI_EIO)?, _ => { let fd = self.fd_map.get(&fd).ok_or(__WASI_EBADF)?; if fd.rights & __WASI_RIGHT_FD_DATASYNC == 0 { @@ -819,13 +943,16 @@ impl WasiFs { let inode = &mut self.inodes[fd.inode]; match &mut inode.kind { - Kind::File { - handle: Some(handle), - .. - } => handle.flush().map_err(|_| __WASI_EIO)?, + Kind::File { handle, .. } => { + if let Some(file) = handle { + file.flush().map_err(|_| __WASI_EIO)? + } else { + return Err(__WASI_EIO); + } + } // TODO: verify this behavior Kind::Dir { .. } => return Err(__WASI_EISDIR), - Kind::Symlink { .. } => unimplemented!(), + Kind::Symlink { .. } => unimplemented!("WasiFs::flush Kind::Symlink"), Kind::Buffer { .. } => (), _ => return Err(__WASI_EIO), } @@ -835,7 +962,7 @@ impl WasiFs { } /// Creates an inode and inserts it given a Kind and some extra data - pub fn create_inode( + pub(crate) fn create_inode( &mut self, kind: Kind, is_preopened: bool, @@ -853,7 +980,7 @@ impl WasiFs { } /// creates an inode and inserts it given a Kind, does not assume the file exists to - pub fn create_inode_with_default_stat( + pub(crate) fn create_inode_with_default_stat( &mut self, kind: Kind, is_preopened: bool, @@ -875,6 +1002,7 @@ impl WasiFs { rights: __wasi_rights_t, rights_inheriting: __wasi_rights_t, flags: __wasi_fdflags_t, + open_flags: u16, inode: Inode, ) -> Result<__wasi_fd_t, __wasi_errno_t> { let idx = self.next_fd.get(); @@ -886,6 +1014,7 @@ impl WasiFs { rights_inheriting, flags, offset: 0, + open_flags, inode, }, ); @@ -895,7 +1024,7 @@ impl WasiFs { /// This function is unsafe because it's the caller's responsibility to ensure that /// all refences to the given inode have been removed from the filesystem /// - /// returns true if the inode existed and was removed + /// returns the inode if it existed and was removed pub unsafe fn remove_inode(&mut self, inode: Inode) -> Option { self.inodes.remove(inode) } @@ -911,13 +1040,79 @@ impl WasiFs { }; self.inodes.insert(InodeVal { - stat: stat, + stat, is_preopened: true, name: "/".to_string(), kind: root_kind, }) } + fn create_stdout(&mut self) { + self.create_std_dev_inner( + Box::new(Stdout), + "stdout", + __WASI_STDOUT_FILENO, + STDOUT_DEFAULT_RIGHTS, + __WASI_FDFLAG_APPEND, + ); + } + fn create_stdin(&mut self) { + self.create_std_dev_inner( + Box::new(Stdin), + "stdin", + __WASI_STDIN_FILENO, + STDIN_DEFAULT_RIGHTS, + 0, + ); + } + fn create_stderr(&mut self) { + self.create_std_dev_inner( + Box::new(Stderr), + "stderr", + __WASI_STDERR_FILENO, + STDERR_DEFAULT_RIGHTS, + __WASI_FDFLAG_APPEND, + ); + } + + fn create_std_dev_inner( + &mut self, + handle: Box, + name: &'static str, + raw_fd: __wasi_fd_t, + rights: __wasi_rights_t, + fd_flags: __wasi_fdflags_t, + ) { + let stat = __wasi_filestat_t { + st_filetype: __WASI_FILETYPE_CHARACTER_DEVICE, + st_ino: self.get_next_inode_index(), + ..__wasi_filestat_t::default() + }; + let kind = Kind::File { + fd: Some(raw_fd), + handle: Some(handle), + path: "".into(), + }; + let inode = self.inodes.insert(InodeVal { + stat, + is_preopened: true, + name: name.to_string(), + kind, + }); + self.fd_map.insert( + raw_fd, + Fd { + rights, + rights_inheriting: 0, + flags: fd_flags, + // since we're not calling open on this, we don't need open flags + open_flags: 0, + offset: 0, + inode, + }, + ); + } + pub fn get_stat_for_kind(&self, kind: &Kind) -> Option<__wasi_filestat_t> { let md = match kind { Kind::File { handle, path, .. } => match handle { @@ -987,13 +1182,117 @@ impl WasiFs { ..__wasi_filestat_t::default() }) } + + /// Closes an open FD, handling all details such as FD being preopen + pub(crate) fn close_fd(&mut self, fd: __wasi_fd_t) -> Result<(), __wasi_errno_t> { + let inodeval_mut = self.get_inodeval_mut(fd)?; + let is_preopened = inodeval_mut.is_preopened; + + match &mut inodeval_mut.kind { + Kind::File { ref mut handle, .. } => { + let mut empty_handle = None; + std::mem::swap(handle, &mut empty_handle); + } + Kind::Dir { parent, path, .. } => { + debug!("Closing dir {:?}", &path); + let key = path + .file_name() + .ok_or(__WASI_EINVAL)? + .to_string_lossy() + .to_string(); + if let Some(p) = parent.clone() { + match &mut self.inodes[p].kind { + Kind::Dir { entries, .. } | Kind::Root { entries } => { + self.fd_map.remove(&fd).unwrap(); + if is_preopened { + let mut idx = None; + for (i, po_fd) in self.preopen_fds.iter().enumerate() { + if *po_fd == fd { + idx = Some(i); + break; + } + } + if let Some(i) = idx { + // only remove entry properly if this is the original preopen FD + // calling `path_open` can give you an fd to the same inode as a preopen fd + entries.remove(&key); + self.preopen_fds.remove(i); + // Maybe recursively closes fds if original preopen? + } + } + } + _ => unreachable!( + "Fatal internal logic error, directory's parent is not a directory" + ), + } + } else { + // this shouldn't be possible anymore due to Root + debug!("HIT UNREACHABLE CODE! Non-root directory does not have a parent"); + return Err(__WASI_EINVAL); + } + } + Kind::Root { .. } => return Err(__WASI_EACCES), + Kind::Symlink { .. } | Kind::Buffer { .. } => return Err(__WASI_EINVAL), + } + + Ok(()) + } } -#[derive(Debug)] -pub struct WasiState<'a> { +/// Top level data type containing all* the state with which WASI can +/// interact. +/// +/// * The contents of files are not stored and may be modified by +/// other, concurrently running programs. Data such as the contents +/// of directories are lazily loaded. +#[derive(Debug, Serialize, Deserialize)] +pub struct WasiState { pub fs: WasiFs, - pub args: &'a [Vec], - pub envs: &'a [Vec], + pub args: Vec>, + pub envs: Vec>, +} + +impl Clone for WasiState { + fn clone(&self) -> Self { + unimplemented!() + } +} + +impl WasiState { + /// Create a [`WasiStateBuilder`] to construct a validated instance of + /// [`WasiState`]. + /// + /// Usage: + /// + /// ``` + /// # use wasmer_wasi::state::WasiState; + /// WasiState::new("program_name") + /// .env(b"HOME", "/home/home".to_string()) + /// .arg("--help") + /// .envs({ let mut hm = std::collections::HashMap::new(); + /// hm.insert("COLOR_OUTPUT", "TRUE"); + /// hm.insert("PATH", "/usr/bin"); + /// hm + /// }) + /// .args(&["--verbose", "list"]) + /// .preopen_dir("src") + /// .map_dir("dot", ".") + /// .build() + /// .unwrap(); + /// ``` + pub fn new(program_name: &str) -> WasiStateBuilder { + create_wasi_state(program_name) + } + + /// Turn the WasiState into bytes + pub fn freeze(&self) -> Option> { + bincode::serialize(self).ok() + } + + /// Get a WasiState from bytes + pub fn unfreeze(bytes: &[u8]) -> Option { + bincode::deserialize(bytes).ok() + } } pub fn host_file_type_to_wasi_file_type(file_type: fs::FileType) -> __wasi_filetype_t { diff --git a/lib/wasi/src/state/types.rs b/lib/wasi/src/state/types.rs index 85b84a811..5eb55c670 100644 --- a/lib/wasi/src/state/types.rs +++ b/lib/wasi/src/state/types.rs @@ -1,5 +1,6 @@ /// types for use in the WASI filesystem use crate::syscalls::types::*; +use serde::{de, Deserialize, Serialize}; #[cfg(unix)] use std::convert::TryInto; use std::{ @@ -8,6 +9,7 @@ use std::{ path::PathBuf, time::SystemTime, }; +use wasmer_runtime_core::debug; /// Error type for external users #[derive(Copy, Clone, Debug, PartialEq, Eq)] @@ -47,6 +49,8 @@ pub enum WasiFsError { NotConnected, /// The requested file or directory could not be found EntityNotFound, + /// The requested device couldn't be accessed + NoDevice, /// Caller was not allowed to perform this operation PermissionDenied, /// The operation did not complete within the given amount of time @@ -78,6 +82,7 @@ impl WasiFsError { __WASI_EINTR => WasiFsError::Interrupted, __WASI_EINVAL => WasiFsError::InvalidInput, __WASI_ENOTCONN => WasiFsError::NotConnected, + __WASI_ENODEV => WasiFsError::NoDevice, __WASI_ENOENT => WasiFsError::EntityNotFound, __WASI_EPERM => WasiFsError::PermissionDenied, __WASI_ETIMEDOUT => WasiFsError::TimedOut, @@ -103,6 +108,7 @@ impl WasiFsError { WasiFsError::InvalidFd => __WASI_EBADF, WasiFsError::InvalidInput => __WASI_EINVAL, WasiFsError::IOError => __WASI_EIO, + WasiFsError::NoDevice => __WASI_ENODEV, WasiFsError::NotAFile => __WASI_EINVAL, WasiFsError::NotConnected => __WASI_ENOTCONN, WasiFsError::EntityNotFound => __WASI_ENOENT, @@ -117,61 +123,61 @@ impl WasiFsError { } /// This trait relies on your file closing when it goes out of scope via `Drop` +#[typetag::serde(tag = "type")] pub trait WasiFile: std::fmt::Debug + Write + Read + Seek { /// the last time the file was accessed in nanoseconds as a UNIX timestamp fn last_accessed(&self) -> __wasi_timestamp_t; + /// the last time the file was modified in nanoseconds as a UNIX timestamp fn last_modified(&self) -> __wasi_timestamp_t; + /// the time at which the file was created in nanoseconds as a UNIX timestamp fn created_time(&self) -> __wasi_timestamp_t; + /// set the last time the file was accessed in nanoseconds as a UNIX timestamp - // TODO: stablize this in 0.7.0 by removing default impl fn set_last_accessed(&self, _last_accessed: __wasi_timestamp_t) { - panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::set_last_accessed for your type before then"); + debug!("{:?} did nothing in WasiFile::set_last_accessed due to using the default implementation", self); } + /// set the last time the file was modified in nanoseconds as a UNIX timestamp - // TODO: stablize this in 0.7.0 by removing default impl fn set_last_modified(&self, _last_modified: __wasi_timestamp_t) { - panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::set_last_modified for your type before then"); + debug!("{:?} did nothing in WasiFile::set_last_modified due to using the default implementation", self); } + /// set the time at which the file was created in nanoseconds as a UNIX timestamp - // TODO: stablize this in 0.7.0 by removing default impl fn set_created_time(&self, _created_time: __wasi_timestamp_t) { - panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::set_created_time for your type before then"); + debug!( + "{:?} did nothing in WasiFile::set_created_time to using the default implementation", + self + ); } + /// the size of the file in bytes fn size(&self) -> u64; + /// Change the size of the file, if the `new_size` is greater than the current size /// the extra bytes will be allocated and zeroed - // TODO: stablize this in 0.7.0 by removing default impl - fn set_len(&mut self, _new_size: __wasi_filesize_t) -> Result<(), WasiFsError> { - panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::allocate for your type before then"); - } + fn set_len(&mut self, _new_size: __wasi_filesize_t) -> Result<(), WasiFsError>; /// Request deletion of the file - // TODO: break this out into a WasiPath trait which is dynamically in Kind::File - // this change can't be done until before release - fn unlink(&mut self) -> Result<(), WasiFsError> { - panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::unlink for your type before then"); - } + fn unlink(&mut self) -> Result<(), WasiFsError>; /// Store file contents and metadata to disk - // TODO: stablize this in 0.7.0 by removing default impl + /// Default implementation returns `Ok(())`. You should implement this method if you care + /// about flushing your cache to permanent storage fn sync_to_disk(&self) -> Result<(), WasiFsError> { - panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0. Please implement WasiFile::sync_to_disk for your type before then"); + Ok(()) } /// Moves the file to a new location /// NOTE: the signature of this function will change before stabilization // TODO: stablizie this in 0.7.0 or 0.8.0 by removing default impl fn rename_file(&self, _new_name: &std::path::Path) -> Result<(), WasiFsError> { - panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0 or 0.8.0. Please implement WasiFile::rename_file for your type before then"); + panic!("Default implementation for now as this method is unstable; this default implementation or this entire method may be removed in a future release."); } /// Returns the number of bytes available. This function must not block - fn bytes_available(&self) -> Result { - panic!("Default implementation for compatibilty in the 0.6.X releases; this will be removed in 0.7.0 or 0.8.0. Please implement WasiFile::bytes_available for your type before then"); - } + fn bytes_available(&self) -> Result; /// Used for polling. Default returns `None` because this method cannot be implemented for most types /// Returns the underlying host fd @@ -339,18 +345,122 @@ pub(crate) fn poll( pub trait WasiPath {} /// A thin wrapper around `std::fs::File` -#[derive(Debug)] +#[derive(Debug, Serialize)] pub struct HostFile { + #[serde(skip_serializing)] pub inner: fs::File, pub host_path: PathBuf, + flags: u16, +} + +impl<'de> Deserialize<'de> for HostFile { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "snake_case")] + enum Field { + HostPath, + Flags, + } + + struct HostFileVisitor; + + impl<'de> de::Visitor<'de> for HostFileVisitor { + type Value = HostFile; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("struct HostFile") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: de::SeqAccess<'de>, + { + let host_path = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(0, &self))?; + let flags = seq + .next_element()? + .ok_or_else(|| de::Error::invalid_length(1, &self))?; + let inner = std::fs::OpenOptions::new() + .read(flags & HostFile::READ != 0) + .write(flags & HostFile::WRITE != 0) + .append(flags & HostFile::APPEND != 0) + .open(&host_path) + .map_err(|_| de::Error::custom("Could not open file on this system"))?; + Ok(HostFile { + inner, + host_path, + flags, + }) + } + + fn visit_map(self, mut map: V) -> Result + where + V: de::MapAccess<'de>, + { + let mut host_path = None; + let mut flags = None; + while let Some(key) = map.next_key()? { + match key { + Field::HostPath => { + if host_path.is_some() { + return Err(de::Error::duplicate_field("host_path")); + } + host_path = Some(map.next_value()?); + } + Field::Flags => { + if flags.is_some() { + return Err(de::Error::duplicate_field("flags")); + } + flags = Some(map.next_value()?); + } + } + } + let host_path = host_path.ok_or_else(|| de::Error::missing_field("host_path"))?; + let flags = flags.ok_or_else(|| de::Error::missing_field("flags"))?; + let inner = std::fs::OpenOptions::new() + .read(flags & HostFile::READ != 0) + .write(flags & HostFile::WRITE != 0) + .append(flags & HostFile::APPEND != 0) + .open(&host_path) + .map_err(|_| de::Error::custom("Could not open file on this system"))?; + Ok(HostFile { + inner, + host_path, + flags, + }) + } + } + + const FIELDS: &'static [&'static str] = &["host_path", "flags"]; + deserializer.deserialize_struct("HostFile", FIELDS, HostFileVisitor) + } } impl HostFile { + const READ: u16 = 1; + const WRITE: u16 = 2; + const APPEND: u16 = 4; + /// creates a new host file from a `std::fs::File` and a path - pub fn new(file: fs::File, host_path: PathBuf) -> Self { + pub fn new(file: fs::File, host_path: PathBuf, read: bool, write: bool, append: bool) -> Self { + let mut flags = 0; + if read { + flags |= Self::READ; + } + if write { + flags |= Self::WRITE; + } + if append { + flags |= Self::APPEND; + } Self { inner: file, host_path, + flags, } } @@ -393,6 +503,7 @@ impl Write for HostFile { } } +#[typetag::serde] impl WasiFile for HostFile { fn last_accessed(&self) -> u64 { self.metadata() @@ -519,57 +630,57 @@ fn host_file_bytes_available(_raw_fd: i32) -> Result { unimplemented!("host_file_bytes_available not yet implemented for non-Unix-like targets. This probably means the program tried to use wasi::poll_oneoff") } -#[derive(Debug)] -pub struct Stdout(pub std::io::Stdout); +/// A wrapper type around Stdout that implements `WasiFile` and +/// `Serialize` + `Deserialize`. +#[derive(Debug, Serialize, Deserialize)] +pub struct Stdout; impl Read for Stdout { fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not read from stdout", )) } fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not read from stdout", )) } fn read_to_string(&mut self, _buf: &mut String) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not read from stdout", )) } fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not read from stdout", )) } } impl Seek for Stdout { fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not seek stdout", - )) + Err(io::Error::new(io::ErrorKind::Other, "can not seek stdout")) } } impl Write for Stdout { fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) + io::stdout().write(buf) } fn flush(&mut self) -> io::Result<()> { - self.0.flush() + io::stdout().flush() } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.0.write_all(buf) + io::stdout().write_all(buf) } fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { - self.0.write_fmt(fmt) + io::stdout().write_fmt(fmt) } } +#[typetag::serde] impl WasiFile for Stdout { fn last_accessed(&self) -> u64 { 0 @@ -583,6 +694,13 @@ impl WasiFile for Stdout { fn size(&self) -> u64 { 0 } + fn set_len(&mut self, _new_size: __wasi_filesize_t) -> Result<(), WasiFsError> { + debug!("Calling WasiFile::set_len on stdout; this is probably a bug"); + Err(WasiFsError::PermissionDenied) + } + fn unlink(&mut self) -> Result<(), WasiFsError> { + Ok(()) + } fn bytes_available(&self) -> Result { // unwrap is safe because of get_raw_fd implementation @@ -594,7 +712,7 @@ impl WasiFile for Stdout { #[cfg(unix)] fn get_raw_fd(&self) -> Option { use std::os::unix::io::AsRawFd; - Some(self.0.as_raw_fd()) + Some(io::stdout().as_raw_fd()) } #[cfg(not(unix))] @@ -605,57 +723,57 @@ impl WasiFile for Stdout { } } -#[derive(Debug)] -pub struct Stderr(pub std::io::Stderr); +/// A wrapper type around Stderr that implements `WasiFile` and +/// `Serialize` + `Deserialize`. +#[derive(Debug, Serialize, Deserialize)] +pub struct Stderr; impl Read for Stderr { fn read(&mut self, _buf: &mut [u8]) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not read from stderr", )) } fn read_to_end(&mut self, _buf: &mut Vec) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not read from stderr", )) } fn read_to_string(&mut self, _buf: &mut String) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not read from stderr", )) } fn read_exact(&mut self, _buf: &mut [u8]) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not read from stderr", )) } } impl Seek for Stderr { fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not seek stderr", - )) + Err(io::Error::new(io::ErrorKind::Other, "can not seek stderr")) } } impl Write for Stderr { fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) + io::stderr().write(buf) } fn flush(&mut self) -> io::Result<()> { - self.0.flush() + io::stderr().flush() } fn write_all(&mut self, buf: &[u8]) -> io::Result<()> { - self.0.write_all(buf) + io::stderr().write_all(buf) } fn write_fmt(&mut self, fmt: ::std::fmt::Arguments) -> io::Result<()> { - self.0.write_fmt(fmt) + io::stderr().write_fmt(fmt) } } +#[typetag::serde] impl WasiFile for Stderr { fn last_accessed(&self) -> u64 { 0 @@ -669,6 +787,13 @@ impl WasiFile for Stderr { fn size(&self) -> u64 { 0 } + fn set_len(&mut self, _new_size: __wasi_filesize_t) -> Result<(), WasiFsError> { + debug!("Calling WasiFile::set_len on stderr; this is probably a bug"); + Err(WasiFsError::PermissionDenied) + } + fn unlink(&mut self) -> Result<(), WasiFsError> { + Ok(()) + } fn bytes_available(&self) -> Result { // unwrap is safe because of get_raw_fd implementation @@ -680,7 +805,7 @@ impl WasiFile for Stderr { #[cfg(unix)] fn get_raw_fd(&self) -> Option { use std::os::unix::io::AsRawFd; - Some(self.0.as_raw_fd()) + Some(io::stderr().as_raw_fd()) } #[cfg(not(unix))] @@ -691,57 +816,57 @@ impl WasiFile for Stderr { } } -#[derive(Debug)] -pub struct Stdin(pub std::io::Stdin); +/// A wrapper type around Stdin that implements `WasiFile` and +/// `Serialize` + `Deserialize`. +#[derive(Debug, Serialize, Deserialize)] +pub struct Stdin; impl Read for Stdin { fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) + io::stdin().read(buf) } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { - self.0.read_to_end(buf) + io::stdin().read_to_end(buf) } fn read_to_string(&mut self, buf: &mut String) -> io::Result { - self.0.read_to_string(buf) + io::stdin().read_to_string(buf) } fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { - self.0.read_exact(buf) + io::stdin().read_exact(buf) } } impl Seek for Stdin { fn seek(&mut self, _pos: io::SeekFrom) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, - "can not seek stdin", - )) + Err(io::Error::new(io::ErrorKind::Other, "can not seek stdin")) } } impl Write for Stdin { fn write(&mut self, _buf: &[u8]) -> io::Result { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not write to stdin", )) } fn flush(&mut self) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not write to stdin", )) } fn write_all(&mut self, _buf: &[u8]) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not write to stdin", )) } fn write_fmt(&mut self, _fmt: ::std::fmt::Arguments) -> io::Result<()> { - Err(std::io::Error::new( - std::io::ErrorKind::Other, + Err(io::Error::new( + io::ErrorKind::Other, "can not write to stdin", )) } } +#[typetag::serde] impl WasiFile for Stdin { fn last_accessed(&self) -> u64 { 0 @@ -755,6 +880,14 @@ impl WasiFile for Stdin { fn size(&self) -> u64 { 0 } + fn set_len(&mut self, _new_size: __wasi_filesize_t) -> Result<(), WasiFsError> { + debug!("Calling WasiFile::set_len on stdin; this is probably a bug"); + Err(WasiFsError::PermissionDenied) + } + + fn unlink(&mut self) -> Result<(), WasiFsError> { + Ok(()) + } fn bytes_available(&self) -> Result { // unwrap is safe because of get_raw_fd implementation @@ -766,7 +899,7 @@ impl WasiFile for Stdin { #[cfg(unix)] fn get_raw_fd(&self) -> Option { use std::os::unix::io::AsRawFd; - Some(self.0.as_raw_fd()) + Some(io::stdin().as_raw_fd()) } #[cfg(not(unix))] diff --git a/lib/wasi/src/syscalls/legacy/mod.rs b/lib/wasi/src/syscalls/legacy/mod.rs new file mode 100644 index 000000000..62cc08f9e --- /dev/null +++ b/lib/wasi/src/syscalls/legacy/mod.rs @@ -0,0 +1,5 @@ +//! These modules provide wrappers and implementations for older version of WASI. +//! +//! If you are relying on legacy WASI, please upgrade for the best experience. + +pub mod snapshot0; diff --git a/lib/wasi/src/syscalls/legacy/snapshot0.rs b/lib/wasi/src/syscalls/legacy/snapshot0.rs new file mode 100644 index 000000000..67566a50c --- /dev/null +++ b/lib/wasi/src/syscalls/legacy/snapshot0.rs @@ -0,0 +1,178 @@ +use crate::ptr::{Array, WasmPtr}; +use crate::syscalls; +use crate::syscalls::types::{self, snapshot0}; +use wasmer_runtime_core::{debug, vm::Ctx}; + +/// Wrapper around `syscalls::fd_filestat_get` with extra logic to handle the size +/// difference of `wasi_filestat_t` +/// +/// WARNING: this function involves saving, clobbering, and restoring unrelated +/// Wasm memory. If the memory clobbered by the current syscall is also used by +/// that syscall, then it may break. +pub fn fd_filestat_get( + ctx: &mut Ctx, + fd: types::__wasi_fd_t, + buf: WasmPtr, +) -> types::__wasi_errno_t { + let memory = ctx.memory(0); + + // transmute the WasmPtr into a WasmPtr where T2 > T1, this will read extra memory. + // The edge case of this causing an OOB is not handled, if the new field is OOB, then the entire + // memory access will fail. + let new_buf: WasmPtr = unsafe { std::mem::transmute(buf) }; + + // Copy the data including the extra data + let new_filestat_setup: types::__wasi_filestat_t = + wasi_try!(new_buf.deref(memory)).get().clone(); + + // Set up complete, make the call with the pointer that will write to the + // struct and some unrelated memory after the struct. + let result = syscalls::fd_filestat_get(ctx, fd, new_buf); + + // reborrow memory + let memory = ctx.memory(0); + + // get the values written to memory + let new_filestat = wasi_try!(new_buf.deref(memory)).get(); + // translate the new struct into the old struct in host memory + let old_stat = snapshot0::__wasi_filestat_t { + st_dev: new_filestat.st_dev, + st_ino: new_filestat.st_ino, + st_filetype: new_filestat.st_filetype, + st_nlink: new_filestat.st_nlink as u32, + st_size: new_filestat.st_size, + st_atim: new_filestat.st_atim, + st_mtim: new_filestat.st_mtim, + st_ctim: new_filestat.st_ctim, + }; + + // write back the original values at the pointer's memory locations + // (including the memory unrelated to the pointer) + wasi_try!(new_buf.deref(memory)).set(new_filestat_setup); + + // Now that this memory is back as it was, write the translated filestat + // into memory leaving it as it should be + wasi_try!(buf.deref(memory)).set(old_stat); + + result +} + +/// Wrapper around `syscalls::path_filestat_get` with extra logic to handle the size +/// difference of `wasi_filestat_t` +pub fn path_filestat_get( + ctx: &mut Ctx, + fd: types::__wasi_fd_t, + flags: types::__wasi_lookupflags_t, + path: WasmPtr, + path_len: u32, + buf: WasmPtr, +) -> types::__wasi_errno_t { + // see `fd_filestat_get` in this file for an explanation of this strange behavior + let memory = ctx.memory(0); + + let new_buf: WasmPtr = unsafe { std::mem::transmute(buf) }; + let new_filestat_setup: types::__wasi_filestat_t = + wasi_try!(new_buf.deref(memory)).get().clone(); + + let result = syscalls::path_filestat_get(ctx, fd, flags, path, path_len, new_buf); + + let memory = ctx.memory(0); + let new_filestat = wasi_try!(new_buf.deref(memory)).get(); + let old_stat = snapshot0::__wasi_filestat_t { + st_dev: new_filestat.st_dev, + st_ino: new_filestat.st_ino, + st_filetype: new_filestat.st_filetype, + st_nlink: new_filestat.st_nlink as u32, + st_size: new_filestat.st_size, + st_atim: new_filestat.st_atim, + st_mtim: new_filestat.st_mtim, + st_ctim: new_filestat.st_ctim, + }; + + wasi_try!(new_buf.deref(memory)).set(new_filestat_setup); + wasi_try!(buf.deref(memory)).set(old_stat); + + result +} + +/// Wrapper around `syscalls::fd_seek` with extra logic to remap the values +/// of `__wasi_whence_t` +pub fn fd_seek( + ctx: &mut Ctx, + fd: types::__wasi_fd_t, + offset: types::__wasi_filedelta_t, + whence: snapshot0::__wasi_whence_t, + newoffset: WasmPtr, +) -> types::__wasi_errno_t { + let new_whence = match whence { + snapshot0::__WASI_WHENCE_CUR => types::__WASI_WHENCE_CUR, + snapshot0::__WASI_WHENCE_END => types::__WASI_WHENCE_END, + snapshot0::__WASI_WHENCE_SET => types::__WASI_WHENCE_SET, + // if it's invalid, let the new fd_seek handle it + _ => whence, + }; + syscalls::fd_seek(ctx, fd, offset, new_whence, newoffset) +} + +/// Wrapper around `syscalls::poll_oneoff` with extra logic to add the removed +/// userdata field back +pub fn poll_oneoff( + ctx: &mut Ctx, + in_: WasmPtr, + out_: WasmPtr, + nsubscriptions: u32, + nevents: WasmPtr, +) -> types::__wasi_errno_t { + // in this case the new type is smaller than the old type, so it all fits into memory, + // we just need to readjust and copy it + + // we start by adjusting `in_` into a format that the new code can understand + let memory = ctx.memory(0); + let mut in_origs: Vec = vec![]; + for in_sub in wasi_try!(in_.deref(memory, 0, nsubscriptions)) { + in_origs.push(in_sub.get().clone()); + } + + // get a pointer to the smaller new type + let in_new_type_ptr: WasmPtr = + unsafe { std::mem::transmute(in_) }; + + for (in_sub_new, orig) in wasi_try!(in_new_type_ptr.deref(memory, 0, nsubscriptions)) + .iter() + .zip(in_origs.iter()) + { + in_sub_new.set(types::__wasi_subscription_t { + userdata: orig.userdata, + type_: orig.type_, + u: if orig.type_ == types::__WASI_EVENTTYPE_CLOCK { + types::__wasi_subscription_u { + clock: types::__wasi_subscription_clock_t { + clock_id: unsafe { orig.u.clock.clock_id }, + timeout: unsafe { orig.u.clock.timeout }, + precision: unsafe { orig.u.clock.precision }, + flags: unsafe { orig.u.clock.flags }, + }, + } + } else { + types::__wasi_subscription_u { + fd_readwrite: unsafe { orig.u.fd_readwrite }, + } + }, + }); + } + + // make the call + let result = syscalls::poll_oneoff(ctx, in_new_type_ptr, out_, nsubscriptions, nevents); + + // replace the old values of in, in case the calling code reuses the memory + let memory = ctx.memory(0); + + for (in_sub, orig) in wasi_try!(in_.deref(memory, 0, nsubscriptions)) + .iter() + .zip(in_origs.into_iter()) + { + in_sub.set(orig); + } + + result +} diff --git a/lib/wasi/src/syscalls/mod.rs b/lib/wasi/src/syscalls/mod.rs index 01bfdf926..0279ae348 100644 --- a/lib/wasi/src/syscalls/mod.rs +++ b/lib/wasi/src/syscalls/mod.rs @@ -5,6 +5,8 @@ pub mod unix; #[cfg(any(target_os = "windows"))] pub mod windows; +pub mod legacy; + use self::types::*; use crate::{ ptr::{Array, WasmPtr}, @@ -15,7 +17,6 @@ use crate::{ }, ExitCode, }; -use rand::{thread_rng, Rng}; use std::borrow::Borrow; use std::cell::Cell; use std::convert::{Infallible, TryInto}; @@ -374,23 +375,12 @@ pub fn fd_allocate( /// If `fd` is invalid or not open pub fn fd_close(ctx: &mut Ctx, fd: __wasi_fd_t) -> __wasi_errno_t { debug!("wasi::fd_close"); - - let memory = ctx.memory(0); + debug!("=> fd={}", fd); let state = get_wasi_state(ctx); - let inode_val = wasi_try!(state.fs.get_inodeval_mut(fd)); - if inode_val.is_preopened { - return __WASI_EACCES; - } - match &mut inode_val.kind { - Kind::File { ref mut handle, .. } => { - let mut empty_handle = None; - std::mem::swap(handle, &mut empty_handle); - } - Kind::Dir { .. } => return __WASI_EISDIR, - Kind::Root { .. } => return __WASI_EACCES, - Kind::Symlink { .. } | Kind::Buffer { .. } => return __WASI_EINVAL, - } + let fd_entry = wasi_try!(state.fs.get_fd(fd)).clone(); + + wasi_try!(state.fs.close_fd(fd)); __WASI_ESUCCESS } @@ -674,7 +664,15 @@ pub fn fd_pread( let state = get_wasi_state(ctx); let bytes_read = match fd { - __WASI_STDIN_FILENO => wasi_try!(read_bytes(&mut state.fs.stdin, memory, iov_cells)), + __WASI_STDIN_FILENO => { + if let Some(ref mut stdin) = + wasi_try!(state.fs.stdin_mut().map_err(WasiFsError::into_wasi_err)) + { + wasi_try!(read_bytes(stdin, memory, iov_cells)) + } else { + return __WASI_EBADF; + } + } __WASI_STDOUT_FILENO => return __WASI_EINVAL, __WASI_STDERR_FILENO => return __WASI_EINVAL, _ => { @@ -689,23 +687,11 @@ pub fn fd_pread( match &mut state.fs.inodes[inode].kind { Kind::File { handle, .. } => { if let Some(h) = handle { - let current_pos = - wasi_try!(h.seek(std::io::SeekFrom::Current(0)).ok(), __WASI_EIO); wasi_try!( h.seek(std::io::SeekFrom::Start(offset as u64)).ok(), __WASI_EIO ); let bytes_read = wasi_try!(read_bytes(h, memory, iov_cells)); - // reborrow so we can seek it back (the &mut gets moved into `read_bytes` - // and we can't use it after) - // If you're in the future and there's a nicer way to do this, please - // clean up this code - if let Some(h) = handle { - wasi_try!( - h.seek(std::io::SeekFrom::Start(current_pos)).ok(), - __WASI_EIO - ); - } bytes_read } else { return __WASI_EINVAL; @@ -826,8 +812,24 @@ pub fn fd_pwrite( let bytes_written = match fd { __WASI_STDIN_FILENO => return __WASI_EINVAL, - __WASI_STDOUT_FILENO => wasi_try!(write_bytes(&mut state.fs.stdout, memory, iovs_arr_cell)), - __WASI_STDERR_FILENO => wasi_try!(write_bytes(&mut state.fs.stderr, memory, iovs_arr_cell)), + __WASI_STDOUT_FILENO => { + if let Some(ref mut stdout) = + wasi_try!(state.fs.stdout_mut().map_err(WasiFsError::into_wasi_err)) + { + wasi_try!(write_bytes(stdout, memory, iovs_arr_cell)) + } else { + return __WASI_EBADF; + } + } + __WASI_STDERR_FILENO => { + if let Some(ref mut stderr) = + wasi_try!(state.fs.stderr_mut().map_err(WasiFsError::into_wasi_err)) + { + wasi_try!(write_bytes(stderr, memory, iovs_arr_cell)) + } else { + return __WASI_EBADF; + } + } _ => { let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); @@ -896,7 +898,15 @@ pub fn fd_read( let state = get_wasi_state(ctx); let bytes_read = match fd { - __WASI_STDIN_FILENO => wasi_try!(read_bytes(&mut state.fs.stdin, memory, iovs_arr_cell)), + __WASI_STDIN_FILENO => { + if let Some(ref mut stdin) = + wasi_try!(state.fs.stdin_mut().map_err(WasiFsError::into_wasi_err)) + { + wasi_try!(read_bytes(stdin, memory, iovs_arr_cell)) + } else { + return __WASI_EBADF; + } + } __WASI_STDOUT_FILENO | __WASI_STDERR_FILENO => return __WASI_EINVAL, _ => { let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); @@ -1235,8 +1245,24 @@ pub fn fd_write( let bytes_written = match fd { __WASI_STDIN_FILENO => return __WASI_EINVAL, - __WASI_STDOUT_FILENO => wasi_try!(write_bytes(&mut state.fs.stdout, memory, iovs_arr_cell)), - __WASI_STDERR_FILENO => wasi_try!(write_bytes(&mut state.fs.stderr, memory, iovs_arr_cell)), + __WASI_STDOUT_FILENO => { + if let Some(ref mut stdout) = + wasi_try!(state.fs.stdout_mut().map_err(WasiFsError::into_wasi_err)) + { + wasi_try!(write_bytes(stdout, memory, iovs_arr_cell)) + } else { + return __WASI_EBADF; + } + } + __WASI_STDERR_FILENO => { + if let Some(ref mut stderr) = + wasi_try!(state.fs.stderr_mut().map_err(WasiFsError::into_wasi_err)) + { + wasi_try!(write_bytes(stderr, memory, iovs_arr_cell)) + } else { + return __WASI_EBADF; + } + } _ => { let state = get_wasi_state(ctx); let fd_entry = wasi_try!(state.fs.fd_map.get_mut(&fd).ok_or(__WASI_EBADF)); @@ -1678,12 +1704,7 @@ pub fn path_open( dirflags & __WASI_LOOKUP_SYMLINK_FOLLOW != 0, ); - dbg!(&maybe_inode); - - if let Ok(m) = maybe_inode { - &state.fs.inodes[m]; - } - + let mut open_flags = 0; // TODO: traverse rights of dirs properly // COMMENTED OUT: WASI isn't giving appropriate rights here when opening // TODO: look into this; file a bug report if this is a bug @@ -1711,16 +1732,41 @@ pub fn path_open( } } let mut open_options = std::fs::OpenOptions::new(); + let write_permission = adjusted_rights & __WASI_RIGHT_FD_WRITE != 0; + // append, truncate, and create all require the permission to write + let (append_permission, truncate_permission, create_permission) = + if write_permission { + ( + fs_flags & __WASI_FDFLAG_APPEND != 0, + o_flags & __WASI_O_TRUNC != 0, + o_flags & __WASI_O_CREAT != 0, + ) + } else { + (false, false, false) + }; let open_options = open_options .read(true) // TODO: ensure these rights are actually valid given parent, etc. - .write(adjusted_rights & __WASI_RIGHT_FD_WRITE != 0) - .create(o_flags & __WASI_O_CREAT != 0) - .truncate(o_flags & __WASI_O_TRUNC != 0); - + .write(write_permission) + .create(create_permission) + .append(append_permission) + .truncate(truncate_permission); + open_flags |= Fd::READ; + if adjusted_rights & __WASI_RIGHT_FD_WRITE != 0 { + open_flags |= Fd::WRITE; + } + if o_flags & __WASI_O_CREAT != 0 { + open_flags |= Fd::CREATE; + } + if o_flags & __WASI_O_TRUNC != 0 { + open_flags |= Fd::TRUNCATE; + } *handle = Some(Box::new(HostFile::new( wasi_try!(open_options.open(&path).map_err(|_| __WASI_EIO)), path.to_path_buf(), + true, + adjusted_rights & __WASI_RIGHT_FD_WRITE != 0, + false, ))); } Kind::Buffer { .. } => unimplemented!("wasi::path_open for Buffer type files"), @@ -1737,7 +1783,7 @@ pub fn path_open( path_to_symlink, relative_path, } => { - // I think this should return an error + // I think this should return an error (because symlinks should be resolved away by the path traversal) // TODO: investigate this unimplemented!("SYMLINKS IN PATH_OPEN"); } @@ -1773,10 +1819,12 @@ pub fn path_open( let mut open_options = std::fs::OpenOptions::new(); let open_options = open_options .read(true) + .append(fs_flags & __WASI_FDFLAG_APPEND != 0) // TODO: ensure these rights are actually valid given parent, etc. // write access is required for creating a file .write(true) .create_new(true); + open_flags |= Fd::READ | Fd::WRITE | Fd::CREATE | Fd::TRUNCATE; Some(Box::new(HostFile::new( wasi_try!(open_options.open(&new_file_host_path).map_err(|e| { @@ -1784,6 +1832,9 @@ pub fn path_open( __WASI_EIO })), new_file_host_path.clone(), + true, + true, + true, )) as Box) }; @@ -1816,12 +1867,16 @@ pub fn path_open( // TODO: check and reduce these // TODO: ensure a mutable fd to root can never be opened - let out_fd = - wasi_try!(state - .fs - .create_fd(adjusted_rights, fs_rights_inheriting, fs_flags, inode)); + let out_fd = wasi_try!(state.fs.create_fd( + adjusted_rights, + fs_rights_inheriting, + fs_flags, + open_flags, + inode + )); fd_cell.set(out_fd); + debug!("wasi::path_open returning fd {}", out_fd); __WASI_ESUCCESS } @@ -2205,7 +2260,7 @@ pub fn path_unlink_file( } else { // File is closed // problem with the abstraction, we can't call unlink because there's no handle - // TODO: replace this code in 0.7.0 + // TODO: replace this code wasi_try!(std::fs::remove_file(path).map_err(|_| __WASI_EIO)); } } @@ -2322,9 +2377,21 @@ pub fn poll_oneoff( if let Some(fd) = fd { let wasi_file_ref: &dyn WasiFile = match fd { - __WASI_STDERR_FILENO => state.fs.stderr.as_ref(), - __WASI_STDIN_FILENO => state.fs.stdin.as_ref(), - __WASI_STDOUT_FILENO => state.fs.stdout.as_ref(), + __WASI_STDERR_FILENO => wasi_try!( + wasi_try!(state.fs.stderr().map_err(WasiFsError::into_wasi_err)).as_ref(), + __WASI_EBADF + ) + .as_ref(), + __WASI_STDIN_FILENO => wasi_try!( + wasi_try!(state.fs.stdin().map_err(WasiFsError::into_wasi_err)).as_ref(), + __WASI_EBADF + ) + .as_ref(), + __WASI_STDOUT_FILENO => wasi_try!( + wasi_try!(state.fs.stdout().map_err(WasiFsError::into_wasi_err)).as_ref(), + __WASI_EBADF + ) + .as_ref(), _ => { let fd_entry = wasi_try!(state.fs.get_fd(fd)); let inode = fd_entry.inode; @@ -2408,7 +2475,8 @@ pub fn poll_oneoff( } for clock_info in clock_subs { let event = __wasi_event_t { - userdata: clock_info.userdata, + // TOOD: review userdata value + userdata: 0, error: __WASI_ESUCCESS, type_: __WASI_EVENTTYPE_CLOCK, u: unsafe { @@ -2445,17 +2513,18 @@ pub fn proc_raise(ctx: &mut Ctx, sig: __wasi_signal_t) -> __wasi_errno_t { /// The number of bytes that will be written pub fn random_get(ctx: &mut Ctx, buf: WasmPtr, buf_len: u32) -> __wasi_errno_t { debug!("wasi::random_get buf_len: {}", buf_len); - let mut rng = thread_rng(); let memory = ctx.memory(0); let buf = wasi_try!(buf.deref(memory, 0, buf_len)); - unsafe { + let res = unsafe { let u8_buffer = &mut *(buf as *const [_] as *mut [_] as *mut [u8]); - thread_rng().fill(u8_buffer); + getrandom::getrandom(u8_buffer) + }; + match res { + Ok(()) => __WASI_ESUCCESS, + Err(_) => __WASI_EIO, } - - __WASI_ESUCCESS } /// ### `sched_yield()` diff --git a/lib/wasi/src/syscalls/types.rs b/lib/wasi/src/syscalls/types.rs index a1c97308f..5f5559ce4 100644 --- a/lib/wasi/src/syscalls/types.rs +++ b/lib/wasi/src/syscalls/types.rs @@ -2,17 +2,18 @@ use crate::ptr::{Array, WasmPtr}; use byteorder::{ReadBytesExt, WriteBytesExt, LE}; +use serde::{Deserialize, Serialize}; use std::fmt; use std::mem; use wasmer_runtime_core::types::ValueType; pub type __wasi_advice_t = u8; -pub const __WASI_ADVICE_DONTNEED: u8 = 0; -pub const __WASI_ADVICE_NOREUSE: u8 = 1; -pub const __WASI_ADVICE_NORMAL: u8 = 2; -pub const __WASI_ADVICE_RANDOM: u8 = 3; -pub const __WASI_ADVICE_SEQUENTIAL: u8 = 4; -pub const __WASI_ADVICE_WILLNEED: u8 = 5; +pub const __WASI_ADVICE_NORMAL: u8 = 0; +pub const __WASI_ADVICE_SEQUENTIAL: u8 = 1; +pub const __WASI_ADVICE_RANDOM: u8 = 2; +pub const __WASI_ADVICE_WILLNEED: u8 = 3; +pub const __WASI_ADVICE_DONTNEED: u8 = 4; +pub const __WASI_ADVICE_NOREUSE: u8 = 5; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(C)] @@ -316,7 +317,7 @@ pub type __wasi_filedelta_t = i64; pub type __wasi_filesize_t = u64; -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] #[repr(C)] pub struct __wasi_filestat_t { pub st_dev: __wasi_device_t, @@ -415,7 +416,7 @@ pub struct __wasi_iovec_t { unsafe impl ValueType for __wasi_iovec_t {} -pub type __wasi_linkcount_t = u32; +pub type __wasi_linkcount_t = u64; pub type __wasi_lookupflags_t = u32; pub const __WASI_LOOKUP_SYMLINK_FOLLOW: u32 = 1 << 0; @@ -456,8 +457,8 @@ pub const __WASI_RIGHT_FD_FILESTAT_GET: u64 = 1 << 21; pub const __WASI_RIGHT_FD_FILESTAT_SET_SIZE: u64 = 1 << 22; pub const __WASI_RIGHT_FD_FILESTAT_SET_TIMES: u64 = 1 << 23; pub const __WASI_RIGHT_PATH_SYMLINK: u64 = 1 << 24; -pub const __WASI_RIGHT_PATH_UNLINK_FILE: u64 = 1 << 25; -pub const __WASI_RIGHT_PATH_REMOVE_DIRECTORY: u64 = 1 << 26; +pub const __WASI_RIGHT_PATH_REMOVE_DIRECTORY: u64 = 1 << 25; +pub const __WASI_RIGHT_PATH_UNLINK_FILE: u64 = 1 << 26; pub const __WASI_RIGHT_POLL_FD_READWRITE: u64 = 1 << 27; pub const __WASI_RIGHT_SOCK_SHUTDOWN: u64 = 1 << 28; @@ -521,32 +522,36 @@ pub const __WASI_SHUT_WR: u8 = 1 << 1; pub type __wasi_siflags_t = u16; pub type __wasi_signal_t = u8; -pub const __WASI_SIGABRT: u8 = 0; -pub const __WASI_SIGALRM: u8 = 1; -pub const __WASI_SIGBUS: u8 = 2; -pub const __WASI_SIGCHLD: u8 = 3; -pub const __WASI_SIGCONT: u8 = 4; -pub const __WASI_SIGFPE: u8 = 5; -pub const __WASI_SIGHUP: u8 = 6; -pub const __WASI_SIGILL: u8 = 7; -pub const __WASI_SIGINT: u8 = 8; +pub const __WASI_SIGHUP: u8 = 1; +pub const __WASI_SIGINT: u8 = 2; +pub const __WASI_SIGQUIT: u8 = 3; +pub const __WASI_SIGILL: u8 = 4; +pub const __WASI_SIGTRAP: u8 = 5; +pub const __WASI_SIGABRT: u8 = 6; +pub const __WASI_SIGBUS: u8 = 7; +pub const __WASI_SIGFPE: u8 = 8; pub const __WASI_SIGKILL: u8 = 9; -pub const __WASI_SIGPIPE: u8 = 10; -pub const __WASI_SIGQUIT: u8 = 11; -pub const __WASI_SIGSEGV: u8 = 12; -pub const __WASI_SIGSTOP: u8 = 13; -pub const __WASI_SIGSYS: u8 = 14; +pub const __WASI_SIGUSR1: u8 = 10; +pub const __WASI_SIGSEGV: u8 = 11; +pub const __WASI_SIGUSR2: u8 = 12; +pub const __WASI_SIGPIPE: u8 = 13; +pub const __WASI_SIGALRM: u8 = 14; pub const __WASI_SIGTERM: u8 = 15; -pub const __WASI_SIGTRAP: u8 = 16; -pub const __WASI_SIGTSTP: u8 = 17; -pub const __WASI_SIGTTIN: u8 = 18; -pub const __WASI_SIGTTOU: u8 = 19; -pub const __WASI_SIGURG: u8 = 20; -pub const __WASI_SIGUSR1: u8 = 21; -pub const __WASI_SIGUSR2: u8 = 22; -pub const __WASI_SIGVTALRM: u8 = 23; -pub const __WASI_SIGXCPU: u8 = 24; -pub const __WASI_SIGXFSZ: u8 = 25; +pub const __WASI_SIGCHLD: u8 = 16; +pub const __WASI_SIGCONT: u8 = 17; +pub const __WASI_SIGSTOP: u8 = 18; +pub const __WASI_SIGTSTP: u8 = 19; +pub const __WASI_SIGTTIN: u8 = 20; +pub const __WASI_SIGTTOU: u8 = 21; +pub const __WASI_SIGURG: u8 = 22; +pub const __WASI_SIGXCPU: u8 = 23; +pub const __WASI_SIGXFSZ: u8 = 24; +pub const __WASI_SIGVTALRM: u8 = 25; +pub const __WASI_SIGPROF: u8 = 26; +pub const __WASI_SIGWINCH: u8 = 27; +pub const __WASI_SIGPOLL: u8 = 28; +pub const __WASI_SIGPWR: u8 = 29; +pub const __WASI_SIGSYS: u8 = 30; pub type __wasi_subclockflags_t = u16; pub const __WASI_SUBSCRIPTION_CLOCK_ABSTIME: u16 = 1 << 0; @@ -554,7 +559,6 @@ pub const __WASI_SUBSCRIPTION_CLOCK_ABSTIME: u16 = 1 << 0; #[derive(Debug, Copy, Clone, PartialEq, Eq)] #[repr(C)] pub struct __wasi_subscription_clock_t { - pub userdata: __wasi_userdata_t, pub clock_id: __wasi_clockid_t, pub timeout: __wasi_timestamp_t, pub precision: __wasi_timestamp_t, @@ -570,8 +574,8 @@ pub struct __wasi_subscription_fs_readwrite_t { #[derive(Copy, Clone)] #[repr(C)] pub union __wasi_subscription_u { - clock: __wasi_subscription_clock_t, - fd_readwrite: __wasi_subscription_fs_readwrite_t, + pub clock: __wasi_subscription_clock_t, + pub fd_readwrite: __wasi_subscription_fs_readwrite_t, } #[derive(Copy, Clone)] @@ -693,6 +697,107 @@ pub type __wasi_timestamp_t = u64; pub type __wasi_userdata_t = u64; pub type __wasi_whence_t = u8; -pub const __WASI_WHENCE_CUR: u8 = 0; -pub const __WASI_WHENCE_END: u8 = 1; -pub const __WASI_WHENCE_SET: u8 = 2; +pub const __WASI_WHENCE_SET: u8 = 0; +pub const __WASI_WHENCE_CUR: u8 = 1; +pub const __WASI_WHENCE_END: u8 = 2; + +pub mod snapshot0 { + use serde::{Deserialize, Serialize}; + pub type __wasi_linkcount_t = u32; + use wasmer_runtime_core::types::ValueType; + + #[derive(Debug, Copy, Clone, PartialEq, Eq)] + #[repr(C)] + pub struct __wasi_subscription_clock_t { + pub userdata: super::__wasi_userdata_t, + pub clock_id: super::__wasi_clockid_t, + pub timeout: super::__wasi_timestamp_t, + pub precision: super::__wasi_timestamp_t, + pub flags: super::__wasi_subclockflags_t, + } + + #[derive(Copy, Clone)] + #[repr(C)] + pub union __wasi_subscription_u { + pub clock: __wasi_subscription_clock_t, + pub fd_readwrite: super::__wasi_subscription_fs_readwrite_t, + } + + #[derive(Copy, Clone)] + #[repr(C)] + pub struct __wasi_subscription_t { + pub userdata: super::__wasi_userdata_t, + pub type_: super::__wasi_eventtype_t, + pub u: __wasi_subscription_u, + } + + unsafe impl ValueType for __wasi_subscription_t {} + + pub type __wasi_whence_t = u8; + pub const __WASI_WHENCE_CUR: u8 = 0; + pub const __WASI_WHENCE_END: u8 = 1; + pub const __WASI_WHENCE_SET: u8 = 2; + + #[derive(Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + #[repr(C)] + pub struct __wasi_filestat_t { + pub st_dev: super::__wasi_device_t, + pub st_ino: super::__wasi_inode_t, + pub st_filetype: super::__wasi_filetype_t, + pub st_nlink: __wasi_linkcount_t, + pub st_size: super::__wasi_filesize_t, + pub st_atim: super::__wasi_timestamp_t, + pub st_mtim: super::__wasi_timestamp_t, + pub st_ctim: super::__wasi_timestamp_t, + } + + unsafe impl ValueType for __wasi_filestat_t {} + + impl std::fmt::Debug for __wasi_filestat_t { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let convert_ts_into_time_string = |ts| { + let tspec = + time::Timespec::new(ts as i64 / 1_000_000_000, (ts % 1_000_000_000) as i32); + let tm = time::at(tspec); + let out_time = tm.rfc822(); + format!("{} ({})", out_time, ts) + }; + f.debug_struct("__wasi_filestat_t") + .field("st_dev", &self.st_dev) + .field("st_ino", &self.st_ino) + .field( + "st_filetype", + &format!( + "{} ({})", + super::wasi_filetype_to_name(self.st_filetype), + self.st_filetype, + ), + ) + .field("st_nlink", &self.st_nlink) + .field("st_size", &self.st_size) + .field("st_atim", &convert_ts_into_time_string(self.st_atim)) + .field("st_mtim", &convert_ts_into_time_string(self.st_mtim)) + .field("st_ctim", &convert_ts_into_time_string(self.st_ctim)) + .finish() + } + } + + impl std::fmt::Debug for __wasi_subscription_t { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("__wasi_subscription_t") + .field("userdata", &self.userdata) + .field("type", &super::eventtype_to_str(self.type_)) + .field( + "u", + match self.type_ { + super::__WASI_EVENTTYPE_CLOCK => unsafe { &self.u.clock }, + super::__WASI_EVENTTYPE_FD_READ | super::__WASI_EVENTTYPE_FD_WRITE => unsafe { + &self.u.fd_readwrite + }, + _ => &"INVALID EVENTTYPE", + }, + ) + .finish() + } + } +} diff --git a/lib/wasi/src/utils.rs b/lib/wasi/src/utils.rs index 4c3680be2..d57510eb5 100644 --- a/lib/wasi/src/utils.rs +++ b/lib/wasi/src/utils.rs @@ -1,15 +1,54 @@ use wasmer_runtime_core::module::Module; -/// Check if a provided module is compiled with WASI support +#[allow(dead_code)] +/// Check if a provided module is compiled for some version of WASI. +/// Use [`get_wasi_version`] to find out which version of WASI the module is. pub fn is_wasi_module(module: &Module) -> bool { - for (_, import_name) in &module.info().imported_functions { - let namespace = module - .info() - .namespace_table - .get(import_name.namespace_index); - if namespace == "wasi_unstable" { - return true; - } - } - false + get_wasi_version(module).is_some() +} + +/// The version of WASI. This is determined by the imports namespace +/// string. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum WasiVersion { + /// `wasi_unstable`. + Snapshot0, + /// `wasi_snapshot_preview1`. + Snapshot1, + + /// Latest version. + /// + /// It's a “floating” version, i.e. it's an alias to the latest + /// version (for the moment, `Snapshot1`). Using this version is a + /// way to ensure that modules will run only if they come with the + /// latest WASI version (in case of security issues for instance), + /// by just updating the runtime. + /// + /// Note that this version is never returned by an API. It is + /// provided only by the user. + Latest, +} + +/// Detect the version of WASI being used from the namespace +pub fn get_wasi_version(module: &Module) -> Option { + let mut import_iter = module + .info() + .imported_functions + .iter() + .map(|(_, import_name)| import_name.namespace_index); + + // returns None if empty + let first = import_iter.next()?; + if import_iter.all(|idx| idx == first) { + // once we know that all the namespaces are the same, we can use it to + // detect which version of WASI this is + match module.info().namespace_table.get(first) { + "wasi_unstable" => Some(WasiVersion::Snapshot0), + "wasi_snapshot_preview1" => Some(WasiVersion::Snapshot1), + _ => None, + } + } else { + // not all funcs have the same namespace, therefore it's not WASI + None + } } diff --git a/lib/win-exception-handler/Cargo.toml b/lib/win-exception-handler/Cargo.toml index 17d1bfc3f..17f89a0a6 100644 --- a/lib/win-exception-handler/Cargo.toml +++ b/lib/win-exception-handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasmer-win-exception-handler" -version = "0.6.0" +version = "0.11.0" description = "Wasmer runtime exception handling for Windows" license = "MIT" authors = ["The Wasmer Engineering Team "] @@ -8,11 +8,9 @@ repository = "https://github.com/wasmerio/wasmer" edition = "2018" [target.'cfg(windows)'.dependencies] -wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } -winapi = { version = "0.3.7", features = ["winbase", "errhandlingapi", "minwindef", "minwinbase", "winnt"] } +wasmer-runtime-core = { path = "../runtime-core", version = "0.11.0" } +winapi = { version = "0.3.8", features = ["winbase", "errhandlingapi", "minwindef", "minwinbase", "winnt"] } libc = "0.2.60" [build-dependencies] -cmake = "0.1.40" -bindgen = "0.51.0" -regex = "1.2.1" +cmake = "0.1" diff --git a/lib/win-exception-handler/README.md b/lib/win-exception-handler/README.md index 9a32e1753..351228d04 100644 --- a/lib/win-exception-handler/README.md +++ b/lib/win-exception-handler/README.md @@ -1,15 +1,15 @@

- Wasmer logo + Wasmer logo

- - Build Status + + Build Status - License + License Join the Wasmer Community diff --git a/lib/win-exception-handler/src/exception_handling.rs b/lib/win-exception-handler/src/exception_handling.rs index c448392b0..712214c8b 100644 --- a/lib/win-exception-handler/src/exception_handling.rs +++ b/lib/win-exception-handler/src/exception_handling.rs @@ -1,7 +1,9 @@ use std::ptr::NonNull; -use wasmer_runtime_core::vm::{Ctx, Func}; +use wasmer_runtime_core::{ + typed_func::Trampoline, + vm::{Ctx, Func}, +}; -type Trampoline = unsafe extern "C" fn(*mut Ctx, NonNull, *const u64, *mut u64); type CallProtectedResult = Result<(), CallProtectedData>; #[repr(C)] diff --git a/lib/win-exception-handler/src/lib.rs b/lib/win-exception-handler/src/lib.rs index cf5e8396f..459fbe991 100644 --- a/lib/win-exception-handler/src/lib.rs +++ b/lib/win-exception-handler/src/lib.rs @@ -7,6 +7,9 @@ unused_unsafe, unreachable_patterns )] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + #[cfg(windows)] mod exception_handling; diff --git a/scripts/binary-name.sh b/scripts/binary-name.sh index 4ac8ef8e3..0245690b7 100755 --- a/scripts/binary-name.sh +++ b/scripts/binary-name.sh @@ -5,9 +5,11 @@ initArch() { if [ -n "$WASMER_ARCH" ]; then ARCH="$WASMER_ARCH" fi + # If you modify this list, please also modify install.sh case $ARCH in amd64) ARCH="amd64";; x86_64) ARCH="amd64";; + aarch64) ARCH="arm64";; i386) ARCH="386";; *) echo "Architecture ${ARCH} is not supported by this installation script"; exit 1;; esac diff --git a/scripts/update_version_numbers.sh b/scripts/update_version_numbers.sh index 217784810..af1485a3f 100755 --- a/scripts/update_version_numbers.sh +++ b/scripts/update_version_numbers.sh @@ -1,5 +1,5 @@ -PREVIOUS_VERSION='0.5.7' -NEXT_VERSION='0.6.0' +PREVIOUS_VERSION='0.10.2' +NEXT_VERSION='0.11.0' # quick hack fd Cargo.toml --exec sed -i '' "s/version = \"$PREVIOUS_VERSION\"/version = \"$NEXT_VERSION\"/" @@ -11,6 +11,7 @@ echo "manually check changes to wasmer.iss" # Order to upload packages in ## runtime-core ## win-exception-handler +## middleware-common ## clif-backend ## llvm-backend ## singlepass-backend diff --git a/src/bin/kwasmd.rs b/src/bin/kwasmd.rs index 62e22b731..79949417b 100644 --- a/src/bin/kwasmd.rs +++ b/src/bin/kwasmd.rs @@ -63,6 +63,7 @@ fn handle_client(mut stream: UnixStream) { enforce_stack_check: true, track_state: false, features: Default::default(), + ..Default::default() }, &SinglePassCompiler::new(), ) diff --git a/src/bin/wasmer.rs b/src/bin/wasmer.rs index 23e5654b7..d5a9b0865 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -10,7 +10,7 @@ extern crate structopt; use std::env; -use std::fs::{read_to_string, File}; +use std::fs::{metadata, read_to_string, File}; use std::io; use std::io::Read; use std::path::PathBuf; @@ -18,54 +18,33 @@ use std::process::exit; use std::str::FromStr; use std::collections::HashMap; -use structopt::StructOpt; +use structopt::{clap, StructOpt}; use wasmer::*; +#[cfg(feature = "backend-cranelift")] use wasmer_clif_backend::CraneliftCompiler; #[cfg(feature = "backend-llvm")] use wasmer_llvm_backend::{LLVMCompiler, LLVMOptions}; use wasmer_runtime::{ cache::{Cache as BaseCache, FileSystemCache, WasmHash}, - Func, Value, VERSION, + Value, VERSION, }; +#[cfg(feature = "managed")] +use wasmer_runtime_core::tiering::{run_tiering, InteractiveShellContext, ShellExitOperation}; use wasmer_runtime_core::{ self, backend::{Backend, Compiler, CompilerConfig, Features, MemoryBoundCheckMode}, debug, loader::{Instance as LoadedInstance, LocalLoader}, }; -#[cfg(feature = "backend-singlepass")] -use wasmer_singlepass_backend::SinglePassCompiler; #[cfg(feature = "wasi")] use wasmer_wasi; -// stub module to make conditional compilation happy -#[cfg(not(feature = "wasi"))] -mod wasmer_wasi { - use wasmer_runtime_core::{import::ImportObject, module::Module}; - - pub fn is_wasi_module(_module: &Module) -> bool { - false - } - - pub struct WasiFs; - - pub fn generate_import_object_with_fs_setup( - _args: Vec>, - _envs: Vec>, - _preopened_files: Vec, - _mapped_dirs: Vec<(String, std::path::PathBuf)>, - _setup_fs: Option Result<(), String>>>, - ) -> ImportObject { - unimplemented!() - } -} - #[derive(Debug, StructOpt)] -#[structopt(name = "wasmer", about = "Wasm execution runtime.")] +#[structopt(name = "wasmer", about = "Wasm execution runtime.", author)] /// The options for the wasmer Command Line Interface enum CLIOptions { - /// Run a WebAssembly file. Formats accepted: wasm, wast + /// Run a WebAssembly file. Formats accepted: wasm, wat #[structopt(name = "run")] Run(Run), @@ -82,7 +61,7 @@ enum CLIOptions { SelfUpdate, } -#[derive(Debug, StructOpt)] +#[derive(Debug, StructOpt, Clone)] struct PrestandardFeatures { /// Enable support for the SIMD proposal. #[structopt(long = "enable-simd")] @@ -97,6 +76,29 @@ struct PrestandardFeatures { all: bool, } +impl PrestandardFeatures { + /// Generate [`wabt::Features`] struct from CLI options + pub fn into_wabt_features(&self) -> wabt::Features { + let mut features = wabt::Features::new(); + if self.simd || self.all { + features.enable_simd(); + } + if self.threads || self.all { + features.enable_threads(); + } + features.enable_sign_extension(); + features + } + + /// Generate [`Features`] struct from CLI options + pub fn into_backend_features(&self) -> Features { + Features { + simd: self.simd || self.all, + threads: self.threads || self.all, + } + } +} + #[cfg(feature = "backend-llvm")] #[derive(Debug, StructOpt, Clone)] /// LLVM backend flags. @@ -110,13 +112,13 @@ pub struct LLVMCLIOptions { post_opt_ir: Option, /// Emit LLVM generated native code object file. - #[structopt(long = "backend-llvm-object-file", parse(from_os_str))] + #[structopt(long = "llvm-object-file", parse(from_os_str))] obj_file: Option, } -#[derive(Debug, StructOpt)] +#[derive(Debug, StructOpt, Clone)] struct Run { - // Disable the cache + /// Disable the cache #[structopt(long = "disable-cache")] disable_cache: bool, @@ -124,14 +126,30 @@ struct Run { #[structopt(parse(from_os_str))] path: PathBuf, - // Disable the cache + /// Name of the backend to use. (x86_64) + #[cfg(target_arch = "x86_64")] #[structopt( long = "backend", - default_value = "cranelift", - raw(possible_values = "Backend::variants()", case_insensitive = "true") + default_value = "auto", + case_insensitive = true, + possible_values = Backend::variants(), )] backend: Backend, + /// Name of the backend to use. (aarch64) + #[cfg(target_arch = "aarch64")] + #[structopt( + long = "backend", + default_value = "singlepass", + case_insensitive = true, + possible_values = Backend::variants(), + )] + backend: Backend, + + /// Invoke a specified function + #[structopt(long = "invoke", short = "i")] + invoke: Option, + /// Emscripten symbol map #[structopt(long = "em-symbol-map", parse(from_os_str), group = "emscripten")] em_symbol_map: Option, @@ -142,7 +160,7 @@ struct Run { /// WASI pre-opened directory #[structopt(long = "dir", multiple = true, group = "wasi")] - pre_opened_directories: Vec, + pre_opened_directories: Vec, /// Map a host directory to a different location for the wasm module #[structopt(long = "mapdir", multiple = true)] @@ -155,19 +173,38 @@ struct Run { /// Custom code loader #[structopt( long = "loader", - raw(possible_values = "LoaderName::variants()", case_insensitive = "true") + case_insensitive = true, + possible_values = LoaderName::variants(), )] loader: Option, /// Path to previously saved instance image to resume. - #[cfg(feature = "backend-singlepass")] + #[cfg(feature = "managed")] #[structopt(long = "resume")] resume: Option, + /// Optimized backends for higher tiers. + #[cfg(feature = "managed")] + #[structopt( + long = "optimized-backends", + multiple = true, + case_insensitive = true, + possible_values = Backend::variants(), + )] + optimized_backends: Vec, + /// Whether or not state tracking should be disabled during compilation. /// State tracking is necessary for tier switching and backtracing. - #[structopt(long = "no-track-state")] - no_track_state: bool, + #[structopt(long = "track-state")] + track_state: bool, + + // Enable the CallTrace middleware. + #[structopt(long = "call-trace")] + call_trace: bool, + + // Enable the BlockTrace middleware. + #[structopt(long = "block-trace")] + block_trace: bool, /// The command name is a string that will override the first argument passed /// to the wasm program. This is used in wapm to provide nicer output in @@ -193,7 +230,7 @@ struct Run { enable_experimental_framebuffer: bool, /// Application arguments - #[structopt(name = "--", raw(multiple = "true"))] + #[structopt(name = "--", multiple = true)] args: Vec, } @@ -301,6 +338,7 @@ fn get_mapped_dirs(input: &[String]) -> Result, String> { Ok(md) } +#[cfg(feature = "wasi")] fn get_env_var_args(input: &[String]) -> Result, String> { let mut ev = vec![]; for entry in input.iter() { @@ -316,11 +354,155 @@ fn get_env_var_args(input: &[String]) -> Result, String> { Ok(ev) } +/// Helper function for `execute_wasm` (the `Run` command) +#[cfg(feature = "wasi")] +fn execute_wasi( + wasi_version: wasmer_wasi::WasiVersion, + options: &Run, + env_vars: Vec<(&str, &str)>, + module: wasmer_runtime_core::Module, + mapped_dirs: Vec<(String, PathBuf)>, + _wasm_binary: &[u8], +) -> Result<(), String> { + let name = if let Some(cn) = &options.command_name { + cn.clone() + } else { + options.path.to_str().unwrap().to_owned() + }; + + let args = options.args.iter().cloned().map(|arg| arg.into_bytes()); + let preopened_files = options.pre_opened_directories.clone(); + let mut wasi_state_builder = wasmer_wasi::state::WasiState::new(&name); + wasi_state_builder + .args(args) + .envs(env_vars) + .preopen_dirs(preopened_files) + .map_dirs(mapped_dirs); + + #[cfg(feature = "experimental-framebuffer")] + { + if options.enable_experimental_framebuffer { + wasi_state_builder.setup_fs(std::rc::Rc::new(wasmer_wasi_framebuffer::initialize)); + } + } + let wasi_state = wasi_state_builder.build().map_err(|e| format!("{:?}", e))?; + + let import_object = wasmer_wasi::generate_import_object_from_state(wasi_state, wasi_version); /*wasmer_wasi::generate_import_object_for_version( + wasi_version, + args, + envs, + preopened_files, + mapped_dirs, + );*/ + + #[allow(unused_mut)] // mut used in feature + let mut instance = module + .instantiate(&import_object) + .map_err(|e| format!("Can't instantiate WASI module: {:?}", e))?; + + let start: wasmer_runtime::Func<(), ()> = + instance.func("_start").map_err(|e| format!("{:?}", e))?; + + #[cfg(feature = "managed")] + { + let start_raw: extern "C" fn(&mut wasmer_runtime_core::vm::Ctx) = + unsafe { ::std::mem::transmute(start.get_vm_func()) }; + + unsafe { + run_tiering( + module.info(), + &_wasm_binary, + if let Some(ref path) = options.resume { + let mut f = File::open(path).unwrap(); + let mut out: Vec = vec![]; + f.read_to_end(&mut out).unwrap(); + Some( + wasmer_runtime_core::state::InstanceImage::from_bytes(&out) + .expect("failed to decode image"), + ) + } else { + None + }, + &import_object, + start_raw, + &mut instance, + options.backend, + options + .optimized_backends + .iter() + .map( + |&backend| -> (Backend, Box Box + Send>) { + let options = options.clone(); + ( + backend, + Box::new(move || { + get_compiler_by_backend(backend, &options).unwrap() + }), + ) + }, + ) + .collect(), + interactive_shell, + )? + }; + } + + #[cfg(not(feature = "managed"))] + { + use wasmer_runtime::error::RuntimeError; + #[cfg(unix)] + use wasmer_runtime_core::{ + fault::{pop_code_version, push_code_version}, + state::CodeVersion, + }; + + let result; + + #[cfg(unix)] + { + let cv_pushed = + if let Some(msm) = instance.module.runnable_module.get_module_state_map() { + push_code_version(CodeVersion { + baseline: true, + msm: msm, + base: instance.module.runnable_module.get_code().unwrap().as_ptr() as usize, + backend: options.backend, + }); + true + } else { + false + }; + result = start.call(); + if cv_pushed { + pop_code_version().unwrap(); + } + } + #[cfg(not(unix))] + { + result = start.call(); + } + + if let Err(ref err) = result { + match err { + RuntimeError::Trap { msg } => return Err(format!("wasm trap occured: {}", msg)), + RuntimeError::Error { data } => { + if let Some(error_code) = data.downcast_ref::() { + std::process::exit(error_code.code as i32) + } + } + } + return Err(format!("error: {:?}", err)); + } + } + Ok(()) +} + /// Execute a wasm/wat file fn execute_wasm(options: &Run) -> Result<(), String> { let disable_cache = options.disable_cache; let mapped_dirs = get_mapped_dirs(&options.mapped_dirs[..])?; + #[cfg(feature = "wasi")] let env_vars = get_env_var_args(&options.env_vars[..])?; let wasm_path = &options.path; @@ -382,17 +564,16 @@ fn execute_wasm(options: &Run) -> Result<(), String> { } if !utils::is_wasm_binary(&wasm_binary) { - let mut features = wabt::Features::new(); - if options.features.simd || options.features.all { - features.enable_simd(); - } - if options.features.threads || options.features.all { - features.enable_threads(); - } + let features = options.features.into_wabt_features(); wasm_binary = wabt::wat2wasm_with_features(wasm_binary, features) .map_err(|e| format!("Can't convert from wast to wasm: {:?}", e))?; } + let compiler: Box = match get_compiler_by_backend(options.backend, options) { + Some(x) => x, + None => return Err("the requested backend is not enabled".into()), + }; + #[cfg(feature = "backend-llvm")] { if options.backend == Backend::LLVM { @@ -407,19 +588,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> { } } - let compiler: Box = match options.backend { - #[cfg(feature = "backend-singlepass")] - Backend::Singlepass => Box::new(SinglePassCompiler::new()), - #[cfg(not(feature = "backend-singlepass"))] - Backend::Singlepass => return Err("The singlepass backend is not enabled".to_string()), - Backend::Cranelift => Box::new(CraneliftCompiler::new()), - #[cfg(feature = "backend-llvm")] - Backend::LLVM => Box::new(LLVMCompiler::new()), - #[cfg(not(feature = "backend-llvm"))] - Backend::LLVM => return Err("the llvm backend is not enabled".to_string()), - }; - - let track_state = !options.no_track_state; + let track_state = options.track_state; #[cfg(feature = "loader-kernel")] let is_kernel_loader = if let Some(LoaderName::Kernel) = options.loader { @@ -435,14 +604,12 @@ fn execute_wasm(options: &Run) -> Result<(), String> { webassembly::compile_with_config_with( &wasm_binary[..], CompilerConfig { - symbol_map: em_symbol_map, + symbol_map: em_symbol_map.clone(), memory_bound_check_mode: MemoryBoundCheckMode::Disable, enforce_stack_check: true, track_state, - features: Features { - simd: options.features.simd || options.features.all, - threads: options.features.threads || options.features.all, - }, + features: options.features.into_backend_features(), + ..Default::default() }, &*compiler, ) @@ -451,12 +618,9 @@ fn execute_wasm(options: &Run) -> Result<(), String> { webassembly::compile_with_config_with( &wasm_binary[..], CompilerConfig { - symbol_map: em_symbol_map, + symbol_map: em_symbol_map.clone(), track_state, - features: Features { - simd: options.features.simd || options.features.all, - threads: options.features.threads || options.features.all, - }, + features: options.features.into_backend_features(), ..Default::default() }, &*compiler, @@ -472,7 +636,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> { let mut cache = unsafe { FileSystemCache::new(wasmer_cache_dir).map_err(|e| format!("Cache error: {:?}", e))? }; - let load_cache_key = || -> Result<_, String> { + let mut load_cache_key = || -> Result<_, String> { if let Some(ref prehashed_cache_key) = options.cache_key { if let Ok(module) = WasmHash::decode(prehashed_cache_key).and_then(|prehashed_key| { @@ -499,12 +663,9 @@ fn execute_wasm(options: &Run) -> Result<(), String> { let module = webassembly::compile_with_config_with( &wasm_binary[..], CompilerConfig { - symbol_map: em_symbol_map, + symbol_map: em_symbol_map.clone(), track_state, - features: Features { - simd: options.features.simd || options.features.all, - threads: options.features.threads || options.features.all, - }, + features: options.features.into_backend_features(), ..Default::default() }, &*compiler, @@ -526,19 +687,19 @@ fn execute_wasm(options: &Run) -> Result<(), String> { import_object.allow_missing_functions = true; // Import initialization might be left to the loader. let instance = module .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate module: {:?}", e))?; + .map_err(|e| format!("Can't instantiate loader module: {:?}", e))?; - let args: Vec = options - .args - .iter() - .map(|arg| arg.as_str()) - .map(|x| { - Value::I32(x.parse().expect(&format!( + let mut args: Vec = Vec::new(); + for arg in options.args.iter() { + let x = arg.as_str().parse().map_err(|_| { + format!( "Can't parse the provided argument {:?} as a integer", - x - ))) - }) - .collect(); + arg.as_str() + ) + })?; + args.push(Value::I32(x)); + } + let index = instance .resolve_func("_start") .expect("The loader requires a _start function to be present in the module"); @@ -565,7 +726,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> { let import_object = wasmer_emscripten::generate_emscripten_env(&mut emscripten_globals); let mut instance = module .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate module: {:?}", e))?; + .map_err(|e| format!("Can't instantiate emscripten module: {:?}", e))?; wasmer_emscripten::run_emscripten_instance( &module, @@ -582,165 +743,57 @@ fn execute_wasm(options: &Run) -> Result<(), String> { ) .map_err(|e| format!("{:?}", e))?; } else { - if cfg!(feature = "wasi") && wasmer_wasi::is_wasi_module(&module) { - let import_object = wasmer_wasi::generate_import_object_with_fs_setup( - if let Some(cn) = &options.command_name { - [cn.clone()] - } else { - [options.path.to_str().unwrap().to_owned()] - } - .iter() - .chain(options.args.iter()) - .cloned() - .map(|arg| arg.into_bytes()) - .collect(), - env_vars - .into_iter() - .map(|(k, v)| format!("{}={}", k, v).into_bytes()) - .collect(), - options.pre_opened_directories.clone(), + #[cfg(feature = "wasi")] + let wasi_version = wasmer_wasi::get_wasi_version(&module); + #[cfg(feature = "wasi")] + let is_wasi = wasi_version.is_some(); + #[cfg(not(feature = "wasi"))] + let is_wasi = false; + + if is_wasi { + #[cfg(feature = "wasi")] + execute_wasi( + wasi_version.unwrap(), + options, + env_vars, + module, mapped_dirs, - #[cfg(feature = "experimental-framebuffer")] - { - if options.enable_experimental_framebuffer { - Some(Box::new(wasmer_wasi_framebuffer::initialize)) - } else { - None - } - }, - #[cfg(not(feature = "experimental-framebuffer"))] - None, - ); - - #[allow(unused_mut)] // mut used in feature - let mut instance = module - .instantiate(&import_object) - .map_err(|e| format!("Can't instantiate module: {:?}", e))?; - - let start: Func<(), ()> = instance.func("_start").map_err(|e| format!("{:?}", e))?; - - #[cfg(feature = "backend-singlepass")] - unsafe { - if options.backend == Backend::Singlepass { - use wasmer_runtime_core::fault::{catch_unsafe_unwind, ensure_sighandler}; - use wasmer_runtime_core::state::{ - x64::invoke_call_return_on_stack, InstanceImage, - }; - use wasmer_runtime_core::vm::Ctx; - - ensure_sighandler(); - - let start_raw: extern "C" fn(&mut Ctx) = - ::std::mem::transmute(start.get_vm_func()); - - let mut image: Option = if let Some(ref path) = options.resume { - let mut f = File::open(path).unwrap(); - let mut out: Vec = vec![]; - f.read_to_end(&mut out).unwrap(); - Some(InstanceImage::from_bytes(&out).expect("failed to decode image")) - } else { - None - }; - let breakpoints = instance.module.runnable_module.get_breakpoints(); - - loop { - let ret = if let Some(image) = image.take() { - let msm = instance - .module - .runnable_module - .get_module_state_map() - .unwrap(); - let code_base = - instance.module.runnable_module.get_code().unwrap().as_ptr() - as usize; - invoke_call_return_on_stack( - &msm, - code_base, - image, - instance.context_mut(), - breakpoints.clone(), - ) - .map(|_| ()) - } else { - catch_unsafe_unwind( - || start_raw(instance.context_mut()), - breakpoints.clone(), - ) - }; - if let Err(e) = ret { - if let Some(new_image) = e.downcast_ref::() { - let op = interactive_shell(InteractiveShellContext { - image: Some(new_image.clone()), - }); - match op { - ShellExitOperation::ContinueWith(new_image) => { - image = Some(new_image); - } - } - } else { - return Err("Error while executing WebAssembly".into()); - } - } else { - return Ok(()); - } - } - } - } - - { - use wasmer_runtime::error::RuntimeError; - let result = start.call(); - - if let Err(ref err) = result { - match err { - RuntimeError::Trap { msg } => panic!("wasm trap occured: {}", msg), - #[cfg(feature = "wasi")] - RuntimeError::Error { data } => { - if let Some(error_code) = data.downcast_ref::() { - std::process::exit(error_code.code as i32) - } - } - #[cfg(not(feature = "wasi"))] - RuntimeError::Error { .. } => (), - } - panic!("error: {:?}", err) - } - } + &wasm_binary, + )?; } else { let import_object = wasmer_runtime_core::import::ImportObject::new(); let instance = module .instantiate(&import_object) .map_err(|e| format!("Can't instantiate module: {:?}", e))?; - let args: Vec = options - .args - .iter() - .map(|arg| arg.as_str()) - .map(|x| Value::I32(x.parse().unwrap())) - .collect(); - instance - .dyn_func("main") + let mut args: Vec = Vec::new(); + for arg in options.args.iter() { + let x = arg.as_str().parse().map_err(|_| { + format!( + "Can't parse the provided argument {:?} as a integer", + arg.as_str() + ) + })?; + args.push(Value::I32(x)); + } + + let invoke_fn = match options.invoke.as_ref() { + Some(fun) => fun, + _ => "main", + }; + let result = instance + .dyn_func(&invoke_fn) .map_err(|e| format!("{:?}", e))? .call(&args) .map_err(|e| format!("{:?}", e))?; + println!("main() returned: {:?}", result); } } Ok(()) } -#[cfg(feature = "backend-singlepass")] -struct InteractiveShellContext { - image: Option, -} - -#[cfg(feature = "backend-singlepass")] -#[derive(Debug)] -enum ShellExitOperation { - ContinueWith(wasmer_runtime_core::state::InstanceImage), -} - -#[cfg(feature = "backend-singlepass")] +#[cfg(feature = "managed")] fn interactive_shell(mut ctx: InteractiveShellContext) -> ShellExitOperation { use std::io::Write; @@ -797,7 +850,7 @@ fn interactive_shell(mut ctx: InteractiveShellContext) -> ShellExitOperation { } "backtrace" | "bt" => { if let Some(ref image) = ctx.image { - println!("{}", image.execution_state.colored_output()); + println!("{}", image.execution_state.output()); } else { println!("State not available"); } @@ -813,11 +866,33 @@ fn interactive_shell(mut ctx: InteractiveShellContext) -> ShellExitOperation { } } -fn run(options: Run) { - match execute_wasm(&options) { +fn update_backend(options: &mut Run) { + let binary_size = match metadata(&options.path) { + Ok(wasm_binary) => wasm_binary.len(), + Err(_e) => 0, + }; + + // Update backend when a backend flag is `auto`. + // Use the Singlepass backend if it's enabled and the file provided is larger + // than 10MiB (10485760 bytes), or it's enabled and the target architecture + // is AArch64. Otherwise, use the Cranelift backend. + if options.backend == Backend::Auto { + if Backend::variants().contains(&Backend::Singlepass.to_string()) + && (binary_size > 10485760 || cfg!(target_arch = "aarch64")) + { + options.backend = Backend::Singlepass; + } else { + options.backend = Backend::Cranelift; + } + } +} + +fn run(options: &mut Run) { + update_backend(options); + match execute_wasm(options) { Ok(()) => {} Err(message) => { - eprintln!("execute_wasm: {:?}", message); + eprintln!("Error: {}", message); exit(1); } } @@ -844,10 +919,7 @@ fn validate_wasm(validate: Validate) -> Result<(), String> { wasmer_runtime_core::validate_and_report_errors_with_features( &wasm_binary, - Features { - simd: validate.features.simd || validate.features.all, - threads: validate.features.threads || validate.features.all, - }, + validate.features.into_backend_features(), ) .map_err(|err| format!("Validation failed: {}", err))?; @@ -865,10 +937,62 @@ fn validate(validate: Validate) { } } +fn get_compiler_by_backend(backend: Backend, _opts: &Run) -> Option> { + Some(match backend { + #[cfg(feature = "backend-singlepass")] + Backend::Singlepass => { + use wasmer_runtime_core::codegen::MiddlewareChain; + use wasmer_runtime_core::codegen::StreamingCompiler; + use wasmer_singlepass_backend::ModuleCodeGenerator as SinglePassMCG; + + let opts = _opts.clone(); + let middlewares_gen = move || { + let mut middlewares = MiddlewareChain::new(); + if opts.call_trace { + use wasmer_middleware_common::call_trace::CallTrace; + middlewares.push(CallTrace::new()); + } + if opts.block_trace { + use wasmer_middleware_common::block_trace::BlockTrace; + middlewares.push(BlockTrace::new()); + } + middlewares + }; + + let c: StreamingCompiler = + StreamingCompiler::new(middlewares_gen); + Box::new(c) + } + #[cfg(not(feature = "backend-singlepass"))] + Backend::Singlepass => return None, + #[cfg(feature = "backend-cranelift")] + Backend::Cranelift => Box::new(CraneliftCompiler::new()), + #[cfg(not(feature = "backend-cranelift"))] + Backend::Cranelift => return None, + #[cfg(feature = "backend-llvm")] + Backend::LLVM => Box::new(LLVMCompiler::new()), + #[cfg(not(feature = "backend-llvm"))] + Backend::LLVM => return None, + Backend::Auto => return None, + }) +} + fn main() { - let options = CLIOptions::from_args(); + // We try to run wasmer with the normal arguments. + // Eg. `wasmer ` + // In case that fails, we fallback trying the Run subcommand directly. + // Eg. `wasmer myfile.wasm --dir=.` + let options = CLIOptions::from_iter_safe(env::args()).unwrap_or_else(|e| { + match e.kind { + // This fixes a issue that: + // 1. Shows the version twice when doing `wasmer -V` + // 2. Shows the run help (instead of normal help) when doing `wasmer --help` + clap::ErrorKind::VersionDisplayed | clap::ErrorKind::HelpDisplayed => e.exit(), + _ => CLIOptions::Run(Run::from_args()), + } + }); match options { - CLIOptions::Run(options) => run(options), + CLIOptions::Run(mut options) => run(&mut options), #[cfg(not(target_os = "windows"))] CLIOptions::SelfUpdate => update::self_update(), #[cfg(target_os = "windows")] diff --git a/src/installer/wasmer.iss b/src/installer/wasmer.iss index 308802efa..0fd5cf639 100644 --- a/src/installer/wasmer.iss +++ b/src/installer/wasmer.iss @@ -1,6 +1,6 @@ [Setup] AppName=Wasmer -AppVersion=0.6.0 +AppVersion=0.11.0 DefaultDirName={pf}\Wasmer DefaultGroupName=Wasmer Compression=lzma2 diff --git a/src/lib.rs b/src/lib.rs index 3d0b047ea..f4d0e03bb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,9 @@ unused_unsafe, unreachable_patterns )] +#![doc(html_favicon_url = "https://wasmer.io/static/icons/favicon.ico")] +#![doc(html_logo_url = "https://avatars3.githubusercontent.com/u/44205449?s=200&v=4")] + #[macro_use] extern crate wasmer_runtime_core; // extern crate wasmer_emscripten; diff --git a/wapm-cli b/wapm-cli index b15715356..3562d6dda 160000 --- a/wapm-cli +++ b/wapm-cli @@ -1 +1 @@ -Subproject commit b157153568fc45f0d01ba8443c54fc3c2ce0cb23 +Subproject commit 3562d6dda52df526e6e1917dd33bb2454917ab9c