diff --git a/.azure/install-llvm.yml b/.azure/install-llvm.yml index 0a2a674b11a..5b01ee7b9d9 100644 --- a/.azure/install-llvm.yml +++ b/.azure/install-llvm.yml @@ -31,20 +31,21 @@ steps: - bash: | set -ex - if [ -x "`command -v llvm-config`" ]; then - echo `command -v cmake` `llvm-config --version` installed - else - 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_PATH=`pwd`/llvm-8.0.0-install - LLVM_PATH_WIN=$SYSTEM_DEFAULTWORKINGDIRECTORY\\llvm-8.0.0-install - echo "##vso[task.prependpath]$LLVM_PATH/bin" - echo "##vso[task.setvariable variable=LLVM_SYS_80_PREFIX]$LLVM_PATH_WIN" - # chocolatey install cmake --installargs 'ADD_CMAKE_TO_PATH=System' - fi + 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 diff --git a/.azure/install-rust.yml b/.azure/install-rust.yml index 4a7a340c01a..243cb7bf9d7 100644 --- a/.azure/install-rust.yml +++ b/.azure/install-rust.yml @@ -6,6 +6,15 @@ # 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 @@ -15,24 +24,24 @@ steps: echo "##vso[task.prependpath]$HOME/.cargo/bin" fi displayName: "Install Rust (Linux, macOS)" - condition: not(eq(variables['Agent.OS'], 'Windows_NT')) + 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 + # 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 $RUST_TOOLCHAIN + rustup update --no-self-update $RUST_TOOLCHAIN rustup default $RUST_TOOLCHAIN rustc -Vv diff --git a/.azure/install-sccache.yml b/.azure/install-sccache.yml index dffbb13d335..fa46bf6b0b6 100644 --- a/.azure/install-sccache.yml +++ b/.azure/install-sccache.yml @@ -28,6 +28,7 @@ steps: - 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 @@ -35,3 +36,9 @@ steps: env: SCCACHE_AZURE_CONNECTION_STRING: $(SCCACHE_AZURE_CONNECTION_STRING) SCCACHE_AZURE_BLOB_CONTAINER: $(SCCACHE_AZURE_BLOB_CONTAINER) + SCCACHE_DIR: $(Pipeline.Workspace)/.sccache + - task: CacheBeta@0 + inputs: + key: sccache | $(Agent.OS) | Cargo.lock + path: $(Pipeline.Workspace)/.sccache + displayName: Cache Cargo Target diff --git a/Cargo.lock b/Cargo.lock index ebcfbc88217..9746f992237 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -13,7 +13,7 @@ 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]] @@ -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]] @@ -173,16 +173,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] name = "cbindgen" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.8 (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)", + "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)", "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)", - "syn 0.15.44 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "toml 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -491,7 +491,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]] @@ -719,7 +719,7 @@ 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)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -764,7 +764,7 @@ 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)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -773,7 +773,7 @@ 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]] @@ -873,7 +873,7 @@ 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]] @@ -946,7 +946,7 @@ dependencies = [ "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)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1009,7 +1009,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]] @@ -1095,7 +1095,7 @@ 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]] @@ -1325,7 +1325,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)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1359,7 +1359,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]] @@ -1481,7 +1481,7 @@ 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)", ] @@ -1538,7 +1538,7 @@ dependencies = [ "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)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1610,6 +1610,7 @@ dependencies = [ name = "wasmer-llvm-backend" version = "0.6.0" dependencies = [ + "byteorder 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "capstone 0.6.0 (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)", @@ -1624,7 +1625,7 @@ dependencies = [ "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)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1666,7 +1667,7 @@ dependencies = [ name = "wasmer-runtime-c-api" version = "0.6.0" dependencies = [ - "cbindgen 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "cbindgen 0.9.1 (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", @@ -1697,7 +1698,7 @@ dependencies = [ "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)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1742,7 +1743,7 @@ dependencies = [ "time 0.1.42 (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.6.0", - "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]] @@ -1768,7 +1769,7 @@ dependencies = [ "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)", + "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -1792,7 +1793,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)", @@ -1814,7 +1815,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]] @@ -1827,7 +1828,7 @@ name = "wincolor" version = "1.0.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)", "winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1839,7 +1840,7 @@ dependencies = [ "cgmath 0.16.1 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.3.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.3.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [metadata] @@ -1863,7 +1864,7 @@ dependencies = [ "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 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 cbindgen 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9daec6140ab4dcd38c3dd57e580b59a621172a526ac79f1527af760a55afeafd" "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" @@ -2024,7 +2025,7 @@ dependencies = [ "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 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" diff --git a/Cargo.toml b/Cargo.toml index 03e224abbec..bbd596087c7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ backend-singlepass = [ "wasmer-wasi-tests/singlepass" ] wasi = ["wasmer-wasi"] +managed = ["backend-singlepass", "wasmer-runtime-core/managed"] # vfs = ["wasmer-runtime-abi"] [[example]] diff --git a/Makefile b/Makefile index c5a357e6f52..007e89853b0 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: spectests emtests clean build install lint precommit +.PHONY: spectests emtests clean build install lint precommit docs # Generate files generate-spectests: @@ -97,9 +97,11 @@ llvm: spectests-llvm emtests-llvm wasitests-llvm capi: cargo build --release cargo build -p wasmer-runtime-c-api --release + +test-capi: capi cargo test -p wasmer-runtime-c-api --release -test-rest: 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 circleci-clean: @@ -182,3 +184,6 @@ publish-release: # 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-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-llvm,wasi,managed diff --git a/README.md b/README.md index a023b1a2cea..e4fa07e059d 100644 --- a/README.md +++ b/README.md @@ -35,19 +35,19 @@ curl https://get.wasmer.io -sSfL | sh Wasmer runtime can be used as a library embedded in different languages, so you can use WebAssembly anywhere: -|   | Language | Author(s) | Maintenance | Release | -|-|-|-|-|-| -| ![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) | -| ![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 maintened | ![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=) | | | +|   | 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) | +| ![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 maintened | | ![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 @@ -177,7 +177,7 @@ nginx and Lua do not work on Windows - you can track the progress on [this issue ## 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.36+](https://img.shields.io/badge/rustc-1.36+-red.svg?style=flat-square)](https://blog.rust-lang.org/2019/07/04/Rust-1.36.0.html) Wasmer is built with [Cargo](https://crates.io/), the Rust package manager. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 9ae585715a1..aad922544fa 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -29,10 +29,14 @@ jobs: matrix: linux: imageName: "ubuntu-16.04" - rust_toolchain: nightly-2019-06-10 + rust_toolchain: nightly-2019-08-15 mac: imageName: "macos-10.14" - rust_toolchain: nightly-2019-06-10 + 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: stable @@ -43,9 +47,9 @@ jobs: - 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-llvm.yml - bash: make test displayName: Tests (*nix) condition: and(succeeded(), not(eq(variables['Agent.OS'], 'Windows_NT'))) @@ -55,17 +59,17 @@ jobs: - job: Check pool: - vmImage: "macos-10.14" + vmImage: "ubuntu-16.04" variables: - rust_toolchain: nightly-2019-06-10 + 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 - - template: .azure/install-llvm.yml - bash: make check displayName: Check with Flags condition: and(succeeded(), not(eq(variables['Agent.OS'], 'Windows_NT'))) @@ -75,10 +79,10 @@ jobs: matrix: linux: imageName: "ubuntu-16.04" - rust_toolchain: nightly-2019-06-10 + rust_toolchain: nightly-2019-08-15 mac: imageName: "macos-10.14" - rust_toolchain: nightly-2019-06-10 + rust_toolchain: nightly-2019-08-15 MACOSX_DEPLOYMENT_TARGET: 10.10 windows: imageName: "vs2017-win2016" @@ -91,10 +95,10 @@ jobs: - 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 - - template: .azure/install-llvm.yml - bash: | mkdir -p artifacts displayName: Create Artifacts Dir @@ -116,6 +120,7 @@ jobs: condition: | and( succeeded(), + eq(variables['Build.SourceBranch'], 'refs/heads/master'), not(eq(variables['Agent.OS'], 'Windows_NT')) ) - bash: | @@ -137,10 +142,10 @@ jobs: matrix: linux: imageName: "ubuntu-16.04" - rust_toolchain: nightly-2019-06-10 + rust_toolchain: nightly-2019-08-15 mac: imageName: "macos-10.14" - rust_toolchain: nightly-2019-06-10 + rust_toolchain: nightly-2019-08-15 MACOSX_DEPLOYMENT_TARGET: 10.10 windows: imageName: "vs2017-win2016" @@ -153,26 +158,29 @@ jobs: - 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-llvm.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: | - cargo build --release - cargo build -p wasmer-runtime-c-api --release + 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')) @@ -203,6 +211,22 @@ jobs: tag: dev 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 diff --git a/docs/debugging.md b/docs/debugging.md new file mode 100644 index 00000000000..8d49d52175b --- /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/examples/iterative_hash/src/main.rs b/examples/iterative_hash/src/main.rs index f25672047f4..043bc02a1d4 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 f64 / diff as f64); + last_millis = millis; + round_count = i; + } } } } diff --git a/examples/many_params.wat b/examples/many_params.wat new file mode 100644 index 00000000000..db5e4bb9be6 --- /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/fuzz/Cargo.toml b/fuzz/Cargo.toml index 5c37cb47ed3..229a36e7f89 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -10,6 +10,8 @@ cargo-fuzz = true [dependencies] wasmer-runtime = { path = "../lib/runtime" } +wasmer-runtime-core = { path = "../lib/runtime-core" } +wasmer = { path = "../" } libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } # Prevent this from interfering with workspaces @@ -19,3 +21,7 @@ members = ["."] [[bin]] name = "simple_instantiate" path = "fuzz_targets/simple_instantiate.rs" + +[[bin]] +name = "validate_wasm" +path = "fuzz_targets/validate_wasm.rs" diff --git a/fuzz/README.md b/fuzz/README.md index cac0a320a54..dda80ce7a6c 100644 --- a/fuzz/README.md +++ b/fuzz/README.md @@ -10,12 +10,16 @@ $ 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) 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 +``` You should see output that looks something like this: diff --git a/fuzz/fuzz_targets/simple_instantiate.rs b/fuzz/fuzz_targets/simple_instantiate.rs index 831bbb1a510..e4912546da6 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 00000000000..f386105eccf --- /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/lib/clif-backend/Cargo.toml b/lib/clif-backend/Cargo.toml index d0bf64d7b5b..a07c86899e2 100644 --- a/lib/clif-backend/Cargo.toml +++ b/lib/clif-backend/Cargo.toml @@ -34,7 +34,7 @@ version = "0.11.2" version = "0.0.7" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.7", features = ["errhandlingapi", "minwindef", "minwinbase", "winnt"] } +winapi = { version = "0.3.8", features = ["errhandlingapi", "minwindef", "minwinbase", "winnt"] } wasmer-win-exception-handler = { path = "../win-exception-handler", version = "0.6.0" } [features] diff --git a/lib/clif-backend/src/lib.rs b/lib/clif-backend/src/lib.rs index eed22d39ea7..60f19a877ed 100644 --- a/lib/clif-backend/src/lib.rs +++ b/lib/clif-backend/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")] + mod cache; mod code; mod libcalls; diff --git a/lib/dev-utils/src/lib.rs b/lib/dev-utils/src/lib.rs index 76ae6804fc7..2a470bba40d 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/emtests/ignores.txt b/lib/emscripten-tests/emtests/ignores.txt index 16ff1a2a7f6..af8f21b0037 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/tests/emtests/test_indirectbr_many.rs b/lib/emscripten-tests/tests/emtests/test_indirectbr_many.rs index fba25e6b20e..a96fe213816 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/src/lib.rs b/lib/emscripten/src/lib.rs index c09f9b772c3..ba25b4be82a 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; diff --git a/lib/kernel-loader/src/lib.rs b/lib/kernel-loader/src/lib.rs index 51ca6c7da54..37f51a424ce 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 4686867a7ce..cc6f918204b 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/Cargo.toml b/lib/llvm-backend/Cargo.toml index 886df698bfc..56adae97b4d 100644 --- a/lib/llvm-backend/Cargo.toml +++ b/lib/llvm-backend/Cargo.toml @@ -12,6 +12,7 @@ smallvec = "0.6.10" 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" @@ -23,7 +24,7 @@ features = ["llvm8-0", "target-x86"] nix = "0.15.0" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.7", features = ["memoryapi"] } +winapi = { version = "0.3.8", features = ["memoryapi"] } [build-dependencies] cc = "1.0" diff --git a/lib/llvm-backend/cpp/object_loader.cpp b/lib/llvm-backend/cpp/object_loader.cpp index bf034a30875..c4fc6d93628 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) {} - - 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); +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)) { + + } else { + func(userdata); } +} + +[[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)(); +} - 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); +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(); } +} - 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 *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 *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 *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 *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; +} - /* Turn on the `reserveAllocationSpace` callback. */ - virtual bool needsToReserveAllocationSpace() override { return true; } +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; + + 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 *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; +} - virtual void registerEHFrames(uint8_t *addr, uint64_t LoadAddr, - size_t size) override { +bool MemoryManager::needsToReserveAllocationSpace() { return true; } + +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 {} + auto read_result = callbacks.protect_memory( + read_section.base, read_section.size, mem_protect_t::PROTECT_READ); + if (read_result != RESULT_OK) { + return false; + } -private: - struct Section { - uint8_t *base; - size_t size; - }; + // The readwrite section is already mapped as read-write. - 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); - }; + return false; +} - // Align the bump pointer to the requires alignment. - aligner(bump_ptr, align); +void MemoryManager::notifyObjectLoaded(llvm::RuntimeDyld &RTDyld, + const llvm::object::ObjectFile &Obj) {} - auto ret_ptr = bump_ptr; - bump_ptr += size; +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); + }; - assert(bump_ptr <= (uintptr_t)section.base + section.size); + // Align the bump pointer to the requires alignment. + aligner(bump_ptr, align); - return (uint8_t *)ret_ptr; - } + auto ret_ptr = bump_ptr; + bump_ptr += size; - 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; + assert(bump_ptr <= (uintptr_t)section.base + section.size); - callbacks_t callbacks; -}; + 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 7a410b2dc07..bc9b9ab6717 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 { +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: - virtual std::string description() const noexcept = 0; + 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 WasmTrap : UncatchableException { +struct WasmModule { public: - enum Type { - Unreachable = 0, - IncorrectCallIndirectSignature = 1, - MemoryOutOfBounds = 2, - CallIndirectOOB = 3, - IllegalArithmetic = 4, - Unknown, - }; + 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; - WasmTrap(Type type) : type(type) {} + bool _init_failed = false; + +private: + std::unique_ptr memory_manager; + std::unique_ptr object_file; + std::unique_ptr runtime_dyld; +}; + +struct WasmTrap : UncatchableException { +public: + 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 92f0f06a134..a0fb80c0352 100644 --- a/lib/llvm-backend/src/backend.rs +++ b/lib/llvm-backend/src/backend.rs @@ -1,3 +1,4 @@ +use super::stackmap::StackmapRegistry; use crate::intrinsics::Intrinsics; use crate::structs::{Callbacks, LLVMModule, LLVMResult, MemProtect}; use inkwell::{ @@ -25,6 +26,7 @@ use wasmer_runtime_core::{ }, cache::Error as CacheError, module::ModuleInfo, + state::ModuleStateMap, structures::TypedIndex, typed_func::{Wasm, WasmTrapInfo}, types::{LocalFuncIndex, SigIndex}, @@ -40,6 +42,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) -> !; @@ -63,6 +69,8 @@ extern "C" { ) -> bool; } +static SIGNAL_HANDLER_INSTALLED: Once = Once::new(); + fn get_callbacks() -> Callbacks { extern "C" fn alloc_memory( size: usize, @@ -154,10 +162,17 @@ 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) { + pub fn new( + module: Module, + _intrinsics: Intrinsics, + _stackmaps: &StackmapRegistry, + _module_info: &ModuleInfo, + ) -> (Self, LLVMCache) { Target::initialize_x86(&InitializationConfig { asm_parser: true, asm_printer: true, @@ -204,22 +219,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 +364,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 +374,8 @@ impl LLVMBackend { Self { module, buffer: Arc::clone(&buffer), + msm: None, + local_func_id_to_offset: vec![], }, LLVMCache { buffer }, )) @@ -300,9 +427,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)) } diff --git a/lib/llvm-backend/src/code.rs b/lib/llvm-backend/src/code.rs index 1f78ccc5bf0..206e6f37a07 100644 --- a/lib/llvm-backend/src/code.rs +++ b/lib/llvm-backend/src/code.rs @@ -11,6 +11,8 @@ use inkwell::{ AddressSpace, AtomicOrdering, AtomicRMWBinOp, FloatPredicate, IntPredicate, }; use smallvec::SmallVec; +use std::cell::RefCell; +use std::rc::Rc; use std::sync::{Arc, RwLock}; use wasmer_runtime_core::{ backend::{Backend, CacheGen, Token}, @@ -28,10 +30,16 @@ 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::stackmap::{StackmapEntry, StackmapEntryKind, StackmapRegistry, ValueSemantic}; 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( + context: &Context, + intrinsics: &Intrinsics, + sig: &FuncSig, + type_to_llvm: fn(intrinsics: &Intrinsics, ty: Type) -> BasicTypeEnum, +) -> FunctionType { 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()) @@ -64,6 +72,14 @@ fn type_to_llvm(intrinsics: &Intrinsics, ty: Type) -> BasicTypeEnum { } } +fn type_to_llvm_int_only(intrinsics: &Intrinsics, ty: Type) -> BasicTypeEnum { + 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, @@ -491,6 +507,90 @@ fn resolve_memory_ptr( Ok(builder.build_int_to_ptr(effective_address_int, ptr_ty, &state.var_name())) } +fn emit_stack_map( + _module_info: &ModuleInfo, + intrinsics: &Intrinsics, + builder: &Builder, + local_function_id: usize, + target: &mut StackmapRegistry, + kind: StackmapEntryKind, + locals: &[PointerValue], + state: &State, + _ctx: &mut CtxType, + 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_from_slice(&state.stack); + 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( + intrinsics: &Intrinsics, + builder: &Builder, + 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( builder: &Builder, intrinsics: &Intrinsics, @@ -575,6 +675,7 @@ pub struct LLVMModuleCodeGenerator { func_import_count: usize, personality_func: FunctionValue, module: Module, + stackmaps: Rc>, } pub struct LLVMFunctionCodeGenerator { @@ -589,6 +690,9 @@ pub struct LLVMFunctionCodeGenerator { num_params: usize, ctx: Option>, unreachable_depth: usize, + stackmaps: Rc>, + index: usize, + opcode_offset: usize, } impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { @@ -658,6 +762,35 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let ctx = CtxType::new(module_info, 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(); + + 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 +805,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 => { @@ -709,6 +846,7 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { } return Ok(()); } + Event::WasmOwned(ref x) => x, }; if !state.reachable { @@ -779,6 +917,35 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { }; builder.position_at_end(&loop_body); + + 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)); + iv.set_volatile(true); + 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 } => { @@ -1040,6 +1207,33 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { // If llvm cannot prove that this is never touched, // 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 { + 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], @@ -1228,26 +1422,59 @@ 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) { + let (params, func_ptr) = 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: Vec<_> = std::iter::once(ctx.basic()) + .chain( + state + .peekn(func_sig.params().len())? + .iter() + .enumerate() + .map(|(i, &v)| match func_sig.params()[i] { + Type::F32 => builder.build_bitcast( + v, + intrinsics.i32_ty, + &state.var_name(), + ), + Type::F64 => builder.build_bitcast( + v, + intrinsics.i64_ty, + &state.var_name(), + ), + _ => v, + }), + ) .collect(); let func_ptr = ctx.local_func(local_func_index, llvm_sig, intrinsics, builder); - builder.build_call(func_ptr, ¶ms, &state.var_name()) + (params, func_ptr) } 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) + + let params: Vec<_> = std::iter::once(ctx_ptr.as_basic_value_enum()) + .chain( + state + .peekn(func_sig.params().len())? + .iter() + .enumerate() + .map(|(i, &v)| match func_sig.params()[i] { + Type::F32 => builder.build_bitcast( + v, + intrinsics.i32_ty, + &state.var_name(), + ), + Type::F64 => builder.build_bitcast( + v, + intrinsics.i64_ty, + &state.var_name(), + ), + _ => v, + }), + ) .collect(); let func_ptr_ty = llvm_sig.ptr_type(AddressSpace::Generic); @@ -1258,15 +1485,50 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { "typed_func_ptr", ); - builder.build_call(func_ptr, ¶ms, &state.var_name()) + (params, func_ptr) } }; state.popn(func_sig.params().len())?; + 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 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(); @@ -1418,7 +1680,17 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { let pushed_args = state.popn_save(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)| { + match wasmer_fn_sig.params()[i] { + Type::F32 => { + builder.build_bitcast(v, intrinsics.i32_ty, &state.var_name()) + } + Type::F64 => { + builder.build_bitcast(v, intrinsics.i64_ty, &state.var_name()) + } + _ => v, + } + })) .collect(); let typed_func_ptr = builder.build_pointer_cast( @@ -1427,13 +1699,47 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { "typed_func_ptr", ); + 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 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, + }); } _ => unimplemented!("multi-value returns"), } @@ -6538,7 +6844,13 @@ impl FunctionCodeGenerator for LLVMFunctionCodeGenerator { self.builder.as_ref().unwrap().build_return(None); } [one_value] => { - self.builder.as_ref().unwrap().build_return(Some(one_value)); + let builder = self.builder.as_ref().unwrap(); + let intrinsics = self.intrinsics.as_ref().unwrap(); + 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", + ))); } _ => unimplemented!("multi-value returns not yet implemented"), } @@ -6583,6 +6895,7 @@ impl ModuleCodeGenerator function_signatures: None, func_import_count: 0, personality_func, + stackmaps: Rc::new(RefCell::new(StackmapRegistry::default())), } } @@ -6646,15 +6959,27 @@ impl ModuleCodeGenerator .skip(1) .enumerate() .map(|(index, param)| { - let ty = param.get_type(); + //let ty = param.get_type(); + let real_ty = func_sig.params()[index]; + let real_ty_llvm = type_to_llvm(&intrinsics, real_ty); - let alloca = builder.build_alloca(ty, &format!("local{}", index)); - builder.build_store(alloca, param); + let alloca = builder.build_alloca(real_ty_llvm, &format!("local{}", index)); + + //if real_ty_llvm != ty { + builder.build_store( + alloca, + builder.build_bitcast(param, real_ty_llvm, &state.var_name()), + ); + /*} else { + builder.build_store(alloca, param); + }*/ alloca }), ); let num_params = locals.len(); + let local_func_index = self.functions.len(); + let code = LLVMFunctionCodeGenerator { state, context: Some(context), @@ -6667,6 +6992,9 @@ impl ModuleCodeGenerator num_params, ctx: None, unreachable_depth: 0, + stackmaps: self.stackmaps.clone(), + index: local_func_index, + opcode_offset: 0, }; self.functions.push(code); Ok(self.functions.last_mut().unwrap()) @@ -6728,7 +7056,14 @@ impl ModuleCodeGenerator self.module.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, + self.intrinsics.take().unwrap(), + &*stackmaps, + module_info, + ); Ok((backend, Box::new(cache_gen))) } @@ -6740,6 +7075,7 @@ impl ModuleCodeGenerator self.context.as_ref().unwrap(), self.intrinsics.as_ref().unwrap(), sig, + type_to_llvm_int_only, ) }) .collect(); diff --git a/lib/llvm-backend/src/intrinsics.rs b/lib/llvm-backend/src/intrinsics.rs index 22f1b3cfd5d..278e087bb41 100644 --- a/lib/llvm-backend/src/intrinsics.rs +++ b/lib/llvm-backend/src/intrinsics.rs @@ -151,6 +151,8 @@ pub struct Intrinsics { pub throw_trap: FunctionValue, pub throw_breakpoint: FunctionValue, + pub experimental_stackmap: FunctionValue, + pub ctx_ptr_ty: PointerType, } @@ -528,6 +530,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), @@ -606,6 +619,8 @@ pub struct CtxType<'a> { info: &'a ModuleInfo, cache_builder: Builder, + cached_signal_mem: Option, + cached_memories: HashMap, cached_tables: HashMap, cached_sigindices: HashMap, @@ -631,6 +646,8 @@ impl<'a> CtxType<'a> { info, cache_builder, + cached_signal_mem: None, + cached_memories: HashMap::new(), cached_tables: HashMap::new(), cached_sigindices: HashMap::new(), @@ -645,6 +662,27 @@ impl<'a> CtxType<'a> { self.ctx_ptr_value.as_basic_value_enum() } + pub fn signal_mem(&mut self) -> PointerValue { + 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) -> MemoryCache { let (cached_memories, info, ctx_ptr_value, cache_builder) = ( &mut self.cached_memories, @@ -718,12 +756,11 @@ impl<'a> CtxType<'a> { }) } - pub fn table( + pub fn table_prepare( &mut self, index: TableIndex, intrinsics: &Intrinsics, - builder: &Builder, - ) -> (PointerValue, IntValue) { + ) -> (PointerValue, PointerValue) { let (cached_tables, info, ctx_ptr_value, cache_builder) = ( &mut self.cached_tables, self.info, @@ -782,6 +819,16 @@ impl<'a> CtxType<'a> { } }); + (ptr_to_base_ptr, ptr_to_bounds) + } + + pub fn table( + &mut self, + index: TableIndex, + intrinsics: &Intrinsics, + builder: &Builder, + ) -> (PointerValue, IntValue) { + let (ptr_to_base_ptr, ptr_to_bounds) = self.table_prepare(index, intrinsics); ( builder .build_load(ptr_to_base_ptr, "base_ptr") diff --git a/lib/llvm-backend/src/lib.rs b/lib/llvm-backend/src/lib.rs index 454fad1003d..8194f797a45 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; diff --git a/lib/llvm-backend/src/platform/unix.rs b/lib/llvm-backend/src/platform/unix.rs index a07afa13042..88ceb685438 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 00000000000..a56c3c6a38d --- /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_vmctx() 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 47da54a6d79..20f915ebce7 100644 --- a/lib/llvm-backend/src/state.rs +++ b/lib/llvm-backend/src/state.rs @@ -69,7 +69,7 @@ impl ControlFrame { #[derive(Debug)] pub struct State { - stack: Vec, + pub stack: Vec, control_stack: Vec, value_counter: Cell, diff --git a/lib/llvm-backend/src/trampolines.rs b/lib/llvm-backend/src/trampolines.rs index 95fd676b952..cebfa9d4671 100644 --- a/lib/llvm-backend/src/trampolines.rs +++ b/lib/llvm-backend/src/trampolines.rs @@ -68,10 +68,8 @@ fn generate_trampoline( }; 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, }; diff --git a/lib/middleware-common-tests/src/lib.rs b/lib/middleware-common-tests/src/lib.rs index 31b3e1065f9..0b6754c5f65 100644 --- a/lib/middleware-common-tests/src/lib.rs +++ b/lib/middleware-common-tests/src/lib.rs @@ -148,5 +148,4 @@ mod tests { // 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/src/lib.rs b/lib/middleware-common/src/lib.rs index 929558f252e..c7900c83a73 100644 --- a/lib/middleware-common/src/lib.rs +++ b/lib/middleware-common/src/lib.rs @@ -7,5 +7,8 @@ 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")] + pub mod call_trace; pub mod metering; diff --git a/lib/runtime-abi/src/lib.rs b/lib/runtime-abi/src/lib.rs index 237e351b148..84923b43782 100644 --- a/lib/runtime-abi/src/lib.rs +++ b/lib/runtime-abi/src/lib.rs @@ -1,4 +1,8 @@ #![deny(dead_code, unused_imports, unused_variables, 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(not(target_os = "windows"))] #[macro_use] extern crate failure; diff --git a/lib/runtime-c-api/Cargo.toml b/lib/runtime-c-api/Cargo.toml index c95442c625a..7744118534c 100644 --- a/lib/runtime-c-api/Cargo.toml +++ b/lib/runtime-c-api/Cargo.toml @@ -32,4 +32,4 @@ llvm-backend = ["wasmer-runtime/llvm", "wasmer-runtime/default-backend-llvm"] singlepass-backend = ["wasmer-runtime/singlepass", "wasmer-runtime/default-backend-singlepass"] [build-dependencies] -cbindgen = "0.9.0" +cbindgen = "0.9.1" diff --git a/lib/runtime-c-api/src/lib.rs b/lib/runtime-c-api/src/lib.rs index 0fde6d33094..4dae18cd590 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 diff --git a/lib/runtime-c-api/wasmer.h b/lib/runtime-c-api/wasmer.h index 297428ee275..816dbfeac9e 100644 --- a/lib/runtime-c-api/wasmer.h +++ b/lib/runtime-c-api/wasmer.h @@ -6,6 +6,9 @@ #include #include +/** + * List of export/import kinds. + */ enum wasmer_import_export_kind { WASM_FUNCTION, WASM_GLOBAL, @@ -31,6 +34,9 @@ typedef struct { } wasmer_module_t; +/** + * Opaque pointer to `NamedExportDescriptor`. + */ typedef struct { } wasmer_export_descriptor_t; @@ -40,10 +46,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 +72,9 @@ typedef struct { wasmer_value value; } wasmer_value_t; +/** + * Opaque pointer to `NamedExport`. + */ typedef struct { } wasmer_export_t; @@ -68,6 +83,9 @@ typedef struct { } wasmer_memory_t; +/** + * Opaque pointer to `NamedExports`. + */ typedef struct { } wasmer_exports_t; @@ -101,6 +119,9 @@ typedef struct { } wasmer_table_t; +/** + * Union of import/export value. + */ typedef union { const wasmer_import_func_t *func; const wasmer_table_t *table; diff --git a/lib/runtime-c-api/wasmer.hh b/lib/runtime-c-api/wasmer.hh index cf7a1c7b321..afe82536466 100644 --- a/lib/runtime-c-api/wasmer.hh +++ b/lib/runtime-c-api/wasmer.hh @@ -6,6 +6,7 @@ #include #include +/// List of export/import kinds. enum class wasmer_import_export_kind : uint32_t { WASM_FUNCTION, WASM_GLOBAL, @@ -29,6 +30,7 @@ struct wasmer_module_t { }; +/// Opaque pointer to `NamedExportDescriptor`. struct wasmer_export_descriptor_t { }; @@ -38,10 +40,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 +62,7 @@ struct wasmer_value_t { wasmer_value value; }; +/// Opaque pointer to `NamedExport`. struct wasmer_export_t { }; @@ -66,6 +71,7 @@ struct wasmer_memory_t { }; +/// Opaque pointer to `NamedExports`. struct wasmer_exports_t { }; @@ -99,6 +105,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; diff --git a/lib/runtime-core/Cargo.toml b/lib/runtime-core/Cargo.toml index 5f13706233b..6c31cf5f5cf 100644 --- a/lib/runtime-core/Cargo.toml +++ b/lib/runtime-core/Cargo.toml @@ -41,7 +41,7 @@ version = "0.5.6" version = "0.8.1" [target.'cfg(windows)'.dependencies] -winapi = { version = "0.3.7", features = ["memoryapi"] } +winapi = { version = "0.3.8", features = ["memoryapi"] } [dev-dependencies] field-offset = "0.1.1" @@ -58,3 +58,4 @@ trace = ["debug"] "backend-cranelift" = [] "backend-singlepass" = [] "backend-llvm" = [] +managed = [] \ No newline at end of file diff --git a/lib/runtime-core/build.rs b/lib/runtime-core/build.rs index 35aafc83511..81884f0e18b 100644 --- a/lib/runtime-core/build.rs +++ b/lib/runtime-core/build.rs @@ -38,6 +38,5 @@ fn main() { .file("image-loading-macos-x86-64.s") .compile("image-loading"); } else { - } } diff --git a/lib/runtime-core/image-loading-linux-x86-64.s b/lib/runtime-core/image-loading-linux-x86-64.s index 37ed0f98683..1d86bab0806 100644 --- a/lib/runtime-core/image-loading-linux-x86-64.s +++ b/lib/runtime-core/image-loading-linux-x86-64.s @@ -67,3 +67,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/image-loading-macos-x86-64.s b/lib/runtime-core/image-loading-macos-x86-64.s index a6a307f1fd4..ef6f9451060 100644 --- a/lib/runtime-core/image-loading-macos-x86-64.s +++ b/lib/runtime-core/image-loading-macos-x86-64.s @@ -67,3 +67,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 2bb2303180d..0a062ba5f5c 100644 --- a/lib/runtime-core/src/backend.rs +++ b/lib/runtime-core/src/backend.rs @@ -159,6 +159,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 +179,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/fault.rs b/lib/runtime-core/src/fault.rs index b44978b1b9e..2d46d8ca25c 100644 --- a/lib/runtime-core/src/fault.rs +++ b/lib/runtime-core/src/fault.rs @@ -1,8 +1,9 @@ -mod raw { +pub mod raw { use std::ffi::c_void; extern "C" { pub fn run_on_alternative_stack(stack_end: *mut u64, stack_begin: *mut u64) -> u64; + pub fn register_preservation_trampoline(); // NOT safe to call directly pub fn setjmp(env: *mut c_void) -> i32; pub fn longjmp(env: *mut c_void, val: i32) -> !; } @@ -10,6 +11,7 @@ mod raw { use crate::codegen::{BreakpointInfo, BreakpointMap}; use crate::state::x64::{build_instance_image, read_stack, X64Register, GPR, XMM}; +use crate::state::CodeVersion; use crate::vm; use libc::{mmap, mprotect, siginfo_t, MAP_ANON, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE}; use nix::sys::signal::{ @@ -17,7 +19,7 @@ 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}; @@ -38,8 +40,27 @@ struct UnwindInfo { payload: Option>, // out } +#[repr(packed)] +#[derive(Default, Copy, Clone)] +pub struct BoundaryRegisterPreservation { + pub r15: u64, + pub r14: u64, + pub r13: u64, + pub r12: u64, + 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()); +} + +#[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 +89,42 @@ lazy_static! { } static INTERRUPT_SIGNAL_DELIVERED: AtomicBool = AtomicBool::new(false); +pub fn was_sigint_triggered_fault() -> bool { + WAS_SIGINT_TRIGGERED.with(|x| x.get()) +} + +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 +} + +pub fn push_code_version(version: CodeVersion) { + CURRENT_CODE_VERSIONS.with(|x| x.borrow_mut().push(version)); +} + +pub fn pop_code_version() -> Option { + CURRENT_CODE_VERSIONS.with(|x| x.borrow_mut().pop()) +} + pub unsafe fn get_wasm_interrupt_signal_mem() -> *mut u8 { INTERRUPT_SIGNAL_MEM.0 } +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"); + } +} + 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 { @@ -178,6 +231,8 @@ extern "C" fn signal_trap_handler( let 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 @@ -203,29 +258,26 @@ 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 ctx: &mut vm::Ctx = &mut **CURRENT_CTX.with(|x| x.get()); 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 es_image = CURRENT_CODE_VERSIONS.with(|versions| { + let versions = versions.borrow(); + read_stack( + || versions.iter(), + rsp as usize as *const u64, + fault.known_registers, + Some(fault.ip as usize as u64), + ) + }); if is_suspend_signal { let image = build_instance_image(ctx, es_image); diff --git a/lib/runtime-core/src/lib.rs b/lib/runtime-core/src/lib.rs index c286cd67e02..fa4450693ee 100644 --- a/lib/runtime-core/src/lib.rs +++ b/lib/runtime-core/src/lib.rs @@ -8,6 +8,8 @@ unreachable_patterns )] #![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")] #[cfg(test)] #[macro_use] @@ -54,6 +56,8 @@ pub use trampoline_x64 as trampoline; #[cfg(all(unix, target_arch = "x86_64"))] pub mod fault; pub mod state; +#[cfg(feature = "managed")] +pub mod tiering; use self::error::CompileResult; #[doc(inline)] diff --git a/lib/runtime-core/src/loader.rs b/lib/runtime-core/src/loader.rs index 9a790c6a40d..4f843a54da8 100644 --- a/lib/runtime-core/src/loader.rs +++ b/lib/runtime-core/src/loader.rs @@ -116,13 +116,20 @@ pub struct CodeMemory { size: usize, } +unsafe impl Send for CodeMemory {} +unsafe impl Sync for CodeMemory {} + #[cfg(not(unix))] impl CodeMemory { pub fn new(_size: usize) -> CodeMemory { unimplemented!(); } - pub fn make_executable(&mut self) { + pub fn make_executable(&self) { + unimplemented!(); + } + + pub fn make_writable(&self) { unimplemented!(); } } @@ -130,13 +137,20 @@ impl CodeMemory { #[cfg(unix)] impl CodeMemory { 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 +167,17 @@ impl CodeMemory { } } - pub fn make_executable(&mut self) { + 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"); } } + + 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 +192,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/memory/mod.rs b/lib/runtime-core/src/memory/mod.rs index a7510b818a3..75e9ea007dc 100644 --- a/lib/runtime-core/src/memory/mod.rs +++ b/lib/runtime-core/src/memory/mod.rs @@ -363,5 +363,4 @@ mod memory_tests { "Max number of pages is required for shared memory" ) } - } diff --git a/lib/runtime-core/src/state.rs b/lib/runtime-core/src/state.rs index 51bc3066e71..b6bbb95f50d 100644 --- a/lib/runtime-core/src/state.rs +++ b/lib/runtime-core/src/state.rs @@ -15,6 +15,8 @@ pub struct MachineState { pub stack_values: Vec, pub register_values: Vec, + pub prev_frame: BTreeMap, + pub wasm_stack: Vec, pub wasm_stack_private_depth: usize, @@ -28,6 +30,8 @@ pub struct MachineStateDiff { pub stack_pop: usize, pub reg_diff: Vec<(RegisterIndex, MachineValue)>, + pub prev_frame_diff: BTreeMap>, // None for removal + pub wasm_stack_push: Vec, pub wasm_stack_pop: usize, pub wasm_stack_private_depth: usize, // absolute value; not a diff. @@ -35,15 +39,17 @@ pub struct MachineStateDiff { pub wasm_inst_offset: usize, // absolute value; not a diff. } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Clone, Debug, Eq, PartialEq, Hash)] pub enum MachineValue { Undefined, Vmctx, + VmctxDeref(Vec), PreserveRegister(RegisterIndex), CopyStackBPRelative(i32), // relative to Base Pointer, in byte offset ExplicitShadow, // indicates that all values above this are above the shadow region WasmStack(usize), WasmLocal(usize), + TwoHalves(Box<(MachineValue, MachineValue)>), // 32-bit values. TODO: optimize: add another type for inner "half" value to avoid boxing? } #[derive(Clone, Debug)] @@ -69,6 +75,7 @@ pub enum SuspendOffset { #[derive(Clone, Debug)] pub struct OffsetInfo { + pub end_offset: usize, // excluded bound pub diff_id: usize, pub activate_offset: usize, } @@ -99,9 +106,20 @@ pub struct InstanceImage { pub execution_state: ExecutionStateImage, } +#[derive(Debug, Clone)] +pub struct CodeVersion { + pub baseline: bool, + pub msm: ModuleStateMap, + pub base: usize, +} + impl ModuleStateMap { - #[warn(dead_code)] - fn lookup_call_ip(&self, ip: usize, base: usize) -> Option<(&FunctionStateMap, MachineState)> { + 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 } else { @@ -111,9 +129,14 @@ impl ModuleStateMap { .last() .unwrap(); - match fsm.call_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 @@ -123,57 +146,28 @@ impl ModuleStateMap { } } } - - #[warn(dead_code)] - fn lookup_trappable_ip( + pub 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.trappable_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, - } - } + 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(); + 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, - } - } + pub fn lookup_loop_ip( + &self, + ip: usize, + base: usize, + ) -> Option<(&FunctionStateMap, MachineState)> { + self.lookup_ip(ip, base, |fsm| &fsm.loop_offsets) } } @@ -206,7 +200,7 @@ impl MachineState { .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 +209,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, @@ -258,10 +272,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, ref v) in &x.reg_diff { + state.register_values[index.0] = v.clone(); } - for &(index, v) in &x.reg_diff { - state.register_values[index.0] = v; + 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(); @@ -381,16 +402,27 @@ impl InstanceImage { pub mod x64 { 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 + } + 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, @@ -453,6 +485,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 +527,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 +606,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); @@ -620,11 +726,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(); @@ -655,7 +758,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, ) @@ -682,15 +785,15 @@ pub mod x64 { } #[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], mut initial_address: Option, ) -> ExecutionStateImage { let mut known_registers: [Option; 24] = initially_known_registers; let mut results: Vec = vec![]; + let mut was_baseline = true; for _ in 0.. { let ret_addr = initial_address.take().unwrap_or_else(|| { @@ -698,15 +801,57 @@ pub mod x64 { 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() { + 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 +874,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 +919,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 +937,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)"), + } + } + } + } + + 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); // RBP + stack = stack.offset(1); // saved_rbp wasm_stack.truncate( wasm_stack @@ -845,6 +1033,7 @@ pub mod x64 { XMM7, } + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum X64Register { GPR(GPR), XMM(XMM), @@ -857,5 +1046,36 @@ pub mod x64 { X64Register::XMM(x) => RegisterIndex(x as usize + 16), } } + + 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, + }) + } } } diff --git a/lib/runtime-core/src/table/mod.rs b/lib/runtime-core/src/table/mod.rs index 1c97b89f374..2e11507b1de 100644 --- a/lib/runtime-core/src/table/mod.rs +++ b/lib/runtime-core/src/table/mod.rs @@ -165,5 +165,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 00000000000..21a9ea2d377 --- /dev/null +++ b/lib/runtime-core/src/tiering.rs @@ -0,0 +1,233 @@ +use crate::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(); + } + } +} + +pub enum ShellExitOperation { + ContinueWith(InstanceImage), +} + +pub struct InteractiveShellContext { + pub image: Option, + pub patched: bool, +} + +struct OptimizationState { + outcome: Mutex>, +} + +struct OptimizationOutcome { + module: Module, +} + +#[repr(transparent)] +struct CtxWrapper(*mut Ctx); +unsafe impl Send for CtxWrapper {} +unsafe impl Sync for CtxWrapper {} + +unsafe fn do_optimize( + binary: &[u8], + 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 { module }); + set_wasm_interrupt_on_ctx(ctx_inner.0); + } +} + +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, + optimized_backends: Vec 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 in optimized_backends { + if !ctx_box.lock().unwrap().0.is_null() { + do_optimize(&wasm_binary, 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, + }); + 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<&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() + } else { + None + } + }; + if let Some(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, + }); + 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/types.rs b/lib/runtime-core/src/types.rs index ad00a1588c0..ab7c023a84b 100644 --- a/lib/runtime-core/src/types.rs +++ b/lib/runtime-core/src/types.rs @@ -590,5 +590,4 @@ mod tests { f64::from_native(f64::from_binary((yf64).to_native().to_binary())) ); } - } diff --git a/lib/runtime/src/cache.rs b/lib/runtime/src/cache.rs index 8fd5219a14b..fe39b512aea 100644 --- a/lib/runtime/src/cache.rs +++ b/lib/runtime/src/cache.rs @@ -173,5 +173,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 5b3ba852689..9495dcb346a 100644 --- a/lib/runtime/src/lib.rs +++ b/lib/runtime/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")] + //! Wasmer-runtime is a library that makes embedding WebAssembly //! in your application easy, efficient, and safe. //! diff --git a/lib/singlepass-backend/src/codegen_x64.rs b/lib/singlepass-backend/src/codegen_x64.rs index 0d808a3e029..e069dfeeb0a 100644 --- a/lib/singlepass-backend/src/codegen_x64.rs +++ b/lib/singlepass-backend/src/codegen_x64.rs @@ -4,9 +4,7 @@ use crate::emitter_x64::*; use crate::machine::*; use crate::protect_unix; -use dynasmrt::{ - x64::Assembler, AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi, ExecutableBuffer, -}; +use dynasmrt::{x64::Assembler, AssemblyOffset, DynamicLabel, DynasmApi, DynasmLabelApi}; use smallvec::SmallVec; use std::ptr::NonNull; use std::{ @@ -20,6 +18,8 @@ use wasmer_runtime_core::{ }, cache::{Artifact, Error as CacheError}, codegen::*, + fault::raw::register_preservation_trampoline, + loader::CodeMemory, memory::MemoryType, module::{ModuleInfo, ModuleInner}, state::{ @@ -172,7 +172,7 @@ unsafe impl Sync for FuncPtr {} pub struct X64ExecutionContext { #[allow(dead_code)] - code: ExecutableBuffer, + code: CodeMemory, #[allow(dead_code)] functions: Vec, function_pointers: Vec, @@ -220,6 +220,40 @@ impl RunnableModule for X64ExecutionContext { Some(self.breakpoints.clone()) } + 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 Trampoline { + 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 Trampoline as *mut Trampoline); + 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 { use std::ffi::c_void; use wasmer_runtime_core::typed_func::WasmTrapInfo; @@ -243,7 +277,7 @@ impl RunnableModule for X64ExecutionContext { let execution_context = ::std::mem::transmute_copy::<&dyn RunnableModule, &X64ExecutionContext>(&&**rm); - let args = ::std::slice::from_raw_parts( + let args = std::slice::from_raw_parts( args, num_params_plus_one.unwrap().as_ptr() as usize - 1, ); @@ -306,6 +340,15 @@ 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)] @@ -418,7 +461,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![]; @@ -440,14 +486,21 @@ impl ModuleCodeGenerator }); } }; - 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(), ); @@ -557,6 +610,7 @@ impl X64FunctionCode { fsm.trappable_offsets.insert( offset, OffsetInfo { + end_offset: offset + 1, activate_offset: offset, diff_id: state_diff_id, }, @@ -1179,7 +1233,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 +1258,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 +1298,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 +1307,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 +1318,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 +1392,7 @@ impl X64FunctionCode { fsm.call_offsets.insert( offset, OffsetInfo { + end_offset: offset + 1, activate_offset: offset, diff_id: state_diff_id, }, @@ -1630,6 +1688,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 +1760,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, }, @@ -3958,6 +4025,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, }, diff --git a/lib/singlepass-backend/src/emitter_x64.rs b/lib/singlepass-backend/src/emitter_x64.rs index 3476bd289a7..24e01231ab5 100644 --- a/lib/singlepass-backend/src/emitter_x64.rs +++ b/lib/singlepass-backend/src/emitter_x64.rs @@ -61,6 +61,8 @@ pub trait Emitter { 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); @@ -468,6 +470,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, { diff --git a/lib/singlepass-backend/src/lib.rs b/lib/singlepass-backend/src/lib.rs index e749e5a22ff..ccea7502ee9 100644 --- a/lib/singlepass-backend/src/lib.rs +++ b/lib/singlepass-backend/src/lib.rs @@ -8,6 +8,8 @@ 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"), diff --git a/lib/singlepass-backend/src/machine.rs b/lib/singlepass-backend/src/machine.rs index b09d0bf859f..a0793f37983 100644 --- a/lib/singlepass-backend/src/machine.rs +++ b/lib/singlepass-backend/src/machine.rs @@ -157,12 +157,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 +494,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/spectests/tests/excludes.txt b/lib/spectests/tests/excludes.txt index 5402157ede3..2f077f55981 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: # ::: @@ -860,6 +861,11 @@ 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:355: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 diff --git a/lib/spectests/tests/spectest.rs b/lib/spectests/tests/spectest.rs index b85b9b0fe8c..9cd18d636a8 100644 --- a/lib/spectests/tests/spectest.rs +++ b/lib/spectests/tests/spectest.rs @@ -1265,5 +1265,4 @@ mod tests { self.to_bits() == 0x7FF8_0000_0000_0000 || self.to_bits() == 0xFFF8_0000_0000_0000 } } - } diff --git a/lib/wasi/Cargo.toml b/lib/wasi/Cargo.toml index da72bb3af06..cbe4d8a6924 100644 --- a/lib/wasi/Cargo.toml +++ b/lib/wasi/Cargo.toml @@ -21,4 +21,4 @@ serde = { version = "1", features = ["derive"] } wasmer-runtime-core = { path = "../runtime-core", version = "0.6.0" } [target.'cfg(windows)'.dependencies] -winapi = "0.3.7" +winapi = "0.3.8" diff --git a/lib/wasi/src/lib.rs b/lib/wasi/src/lib.rs index ce2059c6f82..c447980255b 100644 --- a/lib/wasi/src/lib.rs +++ b/lib/wasi/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(target = "windows")] extern crate winapi; diff --git a/lib/win-exception-handler/Cargo.toml b/lib/win-exception-handler/Cargo.toml index 17d1bfc3f09..b076088b35a 100644 --- a/lib/win-exception-handler/Cargo.toml +++ b/lib/win-exception-handler/Cargo.toml @@ -9,7 +9,7 @@ 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"] } +winapi = { version = "0.3.8", features = ["winbase", "errhandlingapi", "minwindef", "minwinbase", "winnt"] } libc = "0.2.60" [build-dependencies] diff --git a/lib/win-exception-handler/src/lib.rs b/lib/win-exception-handler/src/lib.rs index cf5e8396fc8..459fbe9914a 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/src/bin/wasmer.rs b/src/bin/wasmer.rs index eae0548e89e..651981b3bbe 100644 --- a/src/bin/wasmer.rs +++ b/src/bin/wasmer.rs @@ -23,11 +23,13 @@ use structopt::StructOpt; use wasmer::*; use wasmer_clif_backend::CraneliftCompiler; #[cfg(feature = "backend-llvm")] -use wasmer_llvm_backend::{LLVMCompiler, LLVMOptions}; +use wasmer_llvm_backend::LLVMCompiler; use wasmer_runtime::{ cache::{Cache as BaseCache, FileSystemCache, WasmHash}, Func, 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}, @@ -157,10 +159,19 @@ struct Run { 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, + raw(possible_values = "Backend::variants()", case_insensitive = "true") + )] + 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")] @@ -386,30 +397,9 @@ fn execute_wasm(options: &Run) -> Result<(), String> { .map_err(|e| format!("Can't convert from wast to wasm: {:?}", e))?; } - #[cfg(feature = "backend-llvm")] - { - if options.backend == Backend::LLVM { - let options = options.backend_llvm_options.clone(); - unsafe { - wasmer_llvm_backend::GLOBAL_OPTIONS = LLVMOptions { - pre_opt_ir: options.pre_opt_ir, - post_opt_ir: options.post_opt_ir, - obj_file: options.obj_file, - } - } - } - } - - 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 compiler: Box = match get_compiler_by_backend(options.backend) { + Some(x) => x, + None => return Err("the requested backend is not enabled".into()), }; let track_state = !options.no_track_state; @@ -428,7 +418,7 @@ 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, @@ -444,7 +434,7 @@ 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, @@ -465,7 +455,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| { @@ -492,7 +482,7 @@ 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, @@ -602,81 +592,51 @@ fn execute_wasm(options: &Run) -> Result<(), String> { 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(), + #[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"), ) - }; - 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(()); - } - } - } + None + }, + &import_object, + start_raw, + &mut instance, + options + .optimized_backends + .iter() + .map(|&backend| -> Box Box + Send> { + Box::new(move || get_compiler_by_backend(backend).unwrap()) + }) + .collect(), + interactive_shell, + )? + }; } + #[cfg(not(feature = "managed"))] { 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), + RuntimeError::Trap { msg } => { + return Err(format!("wasm trap occured: {}", msg)) + } #[cfg(feature = "wasi")] RuntimeError::Error { data } => { if let Some(error_code) = data.downcast_ref::() { @@ -686,7 +646,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> { #[cfg(not(feature = "wasi"))] RuntimeError::Error { .. } => (), } - panic!("error: {:?}", err) + return Err(format!("error: {:?}", err)); } } } else { @@ -712,18 +672,7 @@ fn execute_wasm(options: &Run) -> Result<(), String> { 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; @@ -848,6 +797,20 @@ fn validate(validate: Validate) { } } +fn get_compiler_by_backend(backend: Backend) -> Option> { + Some(match backend { + #[cfg(feature = "backend-singlepass")] + Backend::Singlepass => Box::new(SinglePassCompiler::new()), + #[cfg(not(feature = "backend-singlepass"))] + Backend::Singlepass => return None, + Backend::Cranelift => Box::new(CraneliftCompiler::new()), + #[cfg(feature = "backend-llvm")] + Backend::LLVM => Box::new(LLVMCompiler::new()), + #[cfg(not(feature = "backend-llvm"))] + Backend::LLVM => return None, + }) +} + fn main() { let options = CLIOptions::from_args(); match options { diff --git a/src/lib.rs b/src/lib.rs index 3d0b047ead6..f4d0e03bb31 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;