Merge branch 'master' into feat-runtime-c-api-strict-c-cpp

This commit is contained in:
Brandon Fish
2019-03-07 23:06:01 -06:00
47 changed files with 6057 additions and 329 deletions

View File

@ -17,14 +17,26 @@ cache:
- target - target
install: install:
# # Install LLVM
# - mkdir C:\projects\deps
# - cd C:\projects\deps
# - appveyor DownloadFile http://prereleases.llvm.org/win-snapshots/LLVM-7.0.0-r336178-win64.exe -FileName llvm.exe
# - 7z x llvm.exe -oC:\projects\deps\llvm
# # - set "PATH=%PATH%;C:\projects\deps\llvm\bin"
# - set "LLD_LINK=C:\projects\deps\llvm\bin\lld-link.exe"
# - set "LLVM_SYS_70_PREFIX=C:\projects\deps\llvm"
# - cd "%APPVEYOR_BUILD_FOLDER%"
# Install Rust
# uncomment these lines if the cache is cleared, or if we must re-install rust for some reason # uncomment these lines if the cache is cleared, or if we must re-install rust for some reason
# - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe # - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
# - rustup-init.exe -yv --default-host %target% # - rustup-init.exe -yv --default-host %target%
- set PATH=%PATH%;%USERPROFILE%\.cargo\bin - set PATH=%PATH%;C:\\Libraries\\llvm-5.0.0\\bin;%USERPROFILE%\.cargo\bin
- rustup default stable-%target% - rustup default stable-%target%
- rustup update - rustup update
- rustc -vV - rustc -vV
- cargo -vV - cargo -vV
# Install InnoSetup # Install InnoSetup
- appveyor-retry appveyor DownloadFile https://s3-us-west-1.amazonaws.com/rust-lang-ci2/rust-ci-mirror/2017-08-22-is.exe - appveyor-retry appveyor DownloadFile https://s3-us-west-1.amazonaws.com/rust-lang-ci2/rust-ci-mirror/2017-08-22-is.exe
- 2017-08-22-is.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP- - 2017-08-22-is.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-
@ -36,7 +48,7 @@ build_script:
- cargo build --release --verbose - cargo build --release --verbose
test_script: test_script:
- cargo test --package wasmer-spectests - cargo test --manifest-path lib/spectests/Cargo.toml --features clif
after_build: after_build:
- cd ./src/installer - cd ./src/installer

View File

@ -13,6 +13,8 @@ jobs:
name: Install dependencies name: Install dependencies
command: | command: |
sudo apt-get install -y cmake sudo apt-get install -y cmake
curl -O https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
tar xf clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
- run: - run:
name: Install lint deps name: Install lint deps
command: | command: |
@ -20,7 +22,9 @@ jobs:
rustup component add clippy rustup component add clippy
- run: - run:
name: Execute lints name: Execute lints
command: make lint command: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make lint
- save_cache: - save_cache:
paths: paths:
- /usr/local/cargo/registry - /usr/local/cargo/registry
@ -41,8 +45,23 @@ jobs:
name: Install dependencies name: Install dependencies
command: | command: |
sudo apt-get install -y cmake sudo apt-get install -y cmake
- run: make test curl -O https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
- run: make integration-tests tar xf clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
- run:
name: Tests
command: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make test
- run:
name: Emscripten Tests
command: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make test-emscripten
- run:
name: Integration Tests
command: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make integration-tests
- save_cache: - save_cache:
paths: paths:
- /usr/local/cargo/registry - /usr/local/cargo/registry
@ -66,6 +85,9 @@ jobs:
curl -O https://cmake.org/files/v3.4/cmake-3.4.1-Darwin-x86_64.tar.gz curl -O https://cmake.org/files/v3.4/cmake-3.4.1-Darwin-x86_64.tar.gz
tar xf cmake-3.4.1-Darwin-x86_64.tar.gz tar xf cmake-3.4.1-Darwin-x86_64.tar.gz
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
# Installing LLVM outside of brew
curl -O https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz
tar xf clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz
- run: - run:
name: Install Rust name: Install Rust
command: | command: |
@ -73,19 +95,31 @@ jobs:
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
cargo --version cargo --version
- run: - run:
name: Execute tests name: Tests
command: | command: |
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-apple-darwin/"
# We increase the ulimit for fixing cargo unclosed files in mac # We increase the ulimit for fixing cargo unclosed files in mac
ulimit -n 8000 ulimit -n 8000
sudo sysctl -w kern.maxfiles=655360 kern.maxfilesperproc=327680 sudo sysctl -w kern.maxfiles=655360 kern.maxfilesperproc=327680
make test make test
- run: - run:
name: Execute integration tests name: Emscripten Tests
command: | command: |
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-apple-darwin/"
# We increase the ulimit for fixing cargo unclosed files in mac
ulimit -n 8000
sudo sysctl -w kern.maxfiles=655360 kern.maxfilesperproc=327680
make test-emscripten
- run:
name: Integration Tests
command: |
export PATH="$HOME/.cargo/bin:$PATH"
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-apple-darwin/"
make integration-tests make integration-tests
- save_cache: - save_cache:
paths: paths:
@ -110,12 +144,22 @@ jobs:
name: Install dependencies name: Install dependencies
command: | command: |
sudo apt-get install -y cmake sudo apt-get install -y cmake
curl -O https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
tar xf clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
- run: - run:
name: Execute tests name: Tests
command: make test
- run:
name: Make release build
command: | command: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make test
- run:
name: Emscripten Tests
command: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make test-emscripten
- run:
name: Release Build
command: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make release make release
mkdir -p artifacts mkdir -p artifacts
VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2) VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
@ -153,6 +197,9 @@ jobs:
curl -O https://cmake.org/files/v3.4/cmake-3.4.1-Darwin-x86_64.tar.gz curl -O https://cmake.org/files/v3.4/cmake-3.4.1-Darwin-x86_64.tar.gz
tar xf cmake-3.4.1-Darwin-x86_64.tar.gz tar xf cmake-3.4.1-Darwin-x86_64.tar.gz
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
# Installing LLVM outside of brew
curl -O https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz
tar xf clang+llvm-7.0.0-x86_64-apple-darwin.tar.xz
- run: - run:
name: Install Rust name: Install Rust
command: | command: |
@ -160,19 +207,31 @@ jobs:
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
cargo --version cargo --version
- run: - run:
name: Execute tests name: Tests
command: | command: |
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-apple-darwin/"
# We increase the ulimit for fixing cargo unclosed files in mac # We increase the ulimit for fixing cargo unclosed files in mac
ulimit -n 8000 ulimit -n 8000
sudo sysctl -w kern.maxfiles=655360 kern.maxfilesperproc=327680 sudo sysctl -w kern.maxfiles=655360 kern.maxfilesperproc=327680
make test make test
- run: - run:
name: Make release build name: Emscripten Tests
command: | command: |
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH" export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH" export PATH="$HOME/.cargo/bin:$PATH"
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-apple-darwin/"
# We increase the ulimit for fixing cargo unclosed files in mac
ulimit -n 8000
sudo sysctl -w kern.maxfiles=655360 kern.maxfilesperproc=327680
make test-emscripten
- run:
name: Release Build
command: |
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH"
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-apple-darwin/"
make release make release
mkdir -p artifacts mkdir -p artifacts
# VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2) # VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
@ -205,8 +264,12 @@ jobs:
name: Install dependencies name: Install dependencies
command: | command: |
sudo apt-get install -y cmake sudo apt-get install -y cmake
curl -O https://releases.llvm.org/7.0.0/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
tar xf clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04.tar.xz
- run: rustup default nightly - run: rustup default nightly
- run: make test - run: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make test
- save_cache: - save_cache:
paths: paths:
- /usr/local/cargo/registry - /usr/local/cargo/registry

View File

@ -1,35 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: "\U0001F6A8 \U0001F41E bug"
assignees: ''
---
### Describe the Bug
A clear and concise description of what the bug is.
### Steps to Reproduce
1. Go to '…'
2. Compile with '…'
3. Run '…'
4. See error
If applicable, add a link to a test case (as a zip file or link to a repository we can clone).
### Expected Behavior
A clear and concise description of what you expected to happen.
### Actual Behavior
A clear and concise description of what actually happened.
If applicable, add screenshots to help explain your problem.
### Additional Context
Add any other context about the problem here.

3
.gitignore vendored
View File

@ -3,5 +3,4 @@
/artifacts /artifacts
.DS_Store .DS_Store
.idea .idea
**/.vscode
\.vscode

478
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -26,9 +26,15 @@ wasmer-runtime = { path = "lib/runtime" }
wasmer-runtime-core = { path = "lib/runtime-core" } wasmer-runtime-core = { path = "lib/runtime-core" }
wasmer-emscripten = { path = "lib/emscripten" } wasmer-emscripten = { path = "lib/emscripten" }
[target.'cfg(not(windows))'.dependencies]
wasmer-llvm-backend = { path = "lib/llvm-backend" }
[workspace] [workspace]
members = ["lib/clif-backend", "lib/runtime", "lib/runtime-core", "lib/emscripten", "lib/spectests", "lib/win-exception-handler", "lib/runtime-c-api"] members = ["lib/clif-backend", "lib/runtime", "lib/runtime-core", "lib/emscripten", "lib/spectests", "lib/win-exception-handler", "lib/runtime-c-api"]
[target.'cfg(not(windows))'.workspace]
members = ["lib/clif-backend", "lib/runtime", "lib/runtime-core", "lib/emscripten", "lib/spectests", "lib/win-exception-handler", "lib/runtime-c-api", "lib/llvm-backend"]
[build-dependencies] [build-dependencies]
wabt = "0.7.2" wabt = "0.7.2"
glob = "0.2.11" glob = "0.2.11"

View File

@ -37,11 +37,17 @@ precommit: lint test
test: test:
# We use one thread so the emscripten stdouts doesn't collide # We use one thread so the emscripten stdouts doesn't collide
cargo test --all --exclude wasmer-runtime-c-api -- --test-threads=1 $(runargs) cargo test --all --exclude wasmer-runtime-c-api --exclude wasmer-emscripten --exclude wasmer-spectests -- $(runargs)
# cargo test --all --exclude wasmer-emscripten -- --test-threads=1 $(runargs) # cargo test --all --exclude wasmer-emscripten -- --test-threads=1 $(runargs)
cargo test --manifest-path lib/spectests/Cargo.toml --features clif
cargo test --manifest-path lib/spectests/Cargo.toml --features llvm
cargo build -p wasmer-runtime-c-api cargo build -p wasmer-runtime-c-api
cargo test -p wasmer-runtime-c-api -- --nocapture cargo test -p wasmer-runtime-c-api -- --nocapture
test-emscripten:
cargo test --manifest-path lib/emscripten/Cargo.toml --features clif -- --test-threads=1 $(runargs)
cargo test --manifest-path lib/emscripten/Cargo.toml --features llvm -- --test-threads=1 $(runargs)
release: release:
# If you are in OS-X, you will need mingw-w64 for cross compiling to windows # If you are in OS-X, you will need mingw-w64 for cross compiling to windows
# brew install mingw-w64 # brew install mingw-w64

View File

@ -97,13 +97,19 @@ sudo apt install cmake
Windows support is _highly experimental_. Only simple Wasm programs may be run, and no syscalls are allowed. This means Windows support is _highly experimental_. Only simple Wasm programs may be run, and no syscalls are allowed. This means
nginx and Lua do not work on Windows. See [this issue](https://github.com/wasmerio/wasmer/issues/176) regarding Emscripten syscall polyfills for Windows. nginx and Lua do not work on Windows. See [this issue](https://github.com/wasmerio/wasmer/issues/176) regarding Emscripten syscall polyfills for Windows.
1. Install [Python for Windows](https://www.python.org/downloads/release/python-2714/). The Windows x86-64 MSI installer is fine. 1. Install [Visual Studio](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=Community&rel=15)
2. Install [Rust for Windows](https://win.rustup.rs)
3. Install [Python for Windows](https://www.python.org/downloads/release/python-2714/). The Windows x86-64 MSI installer is fine.
Make sure to enable "Add python.exe to Path" during installation. Make sure to enable "Add python.exe to Path" during installation.
2. Install [Git for Windows](https://git-scm.com/download/win). Allow it to add `git.exe` to your PATH (default 4. Install [Git for Windows](https://git-scm.com/download/win). Allow it to add `git.exe` to your PATH (default
settings for the installer are fine). settings for the installer are fine).
3. Install [CMake](https://cmake.org/download/). Ensure CMake is in your PATH. 5. Install [CMake](https://cmake.org/download/). Ensure CMake is in your PATH.
6. Install [LLVM 7.0](https://prereleases.llvm.org/win-snapshots/LLVM-7.0.0-r336178-win64.exe)
## Building ## Building

View File

@ -1,7 +1,6 @@
#! /bin/bash #! /bin/bash
nohup ./target/release/wasmer run examples/lua.wasm & nohup ./target/release/wasmer run examples/lua.wasm --disable-cache -- -v
sleep 3s
if grep "Lua 5.4.0 Copyright (C) 1994-2018 Lua.org, PUC-Rio" ./nohup.out if grep "Lua 5.4.0 Copyright (C) 1994-2018 Lua.org, PUC-Rio" ./nohup.out
then then

View File

@ -1,22 +1,14 @@
#! /bin/bash #! /bin/bash
nohup ./target/release/wasmer run examples/nginx/nginx.wasm -- -p integration_tests/nginx/ -c nginx.conf & nohup ./target/release/wasmer run examples/nginx/nginx.wasm --disable-cache -- -v
sleep 3s
curl localhost:8080 > ./nginx.out if grep "nginx version: nginx/1.15.3" ./nohup.out
if grep "wasmer" ./nginx.out
then then
echo "nginx integration test succeeded" echo "nginx integration test succeeded"
rm ./nohup.out rm ./nohup.out
rm ./nginx.out
rm -rf ./integration_tests/nginx/*_temp
exit 0 exit 0
else else
echo "nginx integration test failed" echo "nginx integration test failed"
rm ./nohup.out rm ./nohup.out
rm ./nginx.out
rm -rf ./integration_tests/nginx/*_temp
exit -1 exit -1
fi fi

View File

@ -31,6 +31,7 @@ Wasmer intends to support different integrations:
The Wasmer [runtime](./runtime) is designed to support multiple compiler backends, allowing the user The Wasmer [runtime](./runtime) is designed to support multiple compiler backends, allowing the user
to tune the codegen properties (compile speed, performance, etc) to best fit their use case. to tune the codegen properties (compile speed, performance, etc) to best fit their use case.
Currently, we support a Cranelift compiler backend: Currently, we support multiple backends for compiling WebAssembly to machine code:
- [clif-backend](./clif-backend/): The integration of Wasmer with Cranelift - [clif-backend](./clif-backend/): Cranelift backend
- [llvm-backend](./llvm-backend/): LLVM backend

View File

@ -4,7 +4,6 @@ use crate::{
}; };
use cranelift_codegen::{ir, isa}; use cranelift_codegen::{ir, isa};
use cranelift_wasm::{self, translate_module, FuncTranslator, ModuleEnvironment}; use cranelift_wasm::{self, translate_module, FuncTranslator, ModuleEnvironment};
use std::sync::Arc;
use wasmer_runtime_core::{ use wasmer_runtime_core::{
error::{CompileError, CompileResult}, error::{CompileError, CompileResult},
module::{ module::{
@ -62,10 +61,7 @@ impl<'module, 'isa, 'data> ModuleEnvironment<'data> for ModuleEnv<'module, 'isa>
/// Declares a function signature to the environment. /// Declares a function signature to the environment.
fn declare_signature(&mut self, sig: &ir::Signature) { fn declare_signature(&mut self, sig: &ir::Signature) {
self.signatures.push(sig.clone()); self.signatures.push(sig.clone());
self.module self.module.info.signatures.push(Converter(sig).into());
.info
.signatures
.push(Arc::new(Converter(sig).into()));
} }
/// Return the signature with the given index. /// Return the signature with the given index.

View File

@ -221,7 +221,7 @@ impl FuncResolverBuilder {
pub fn finalize( pub fn finalize(
mut self, mut self,
signatures: &SliceMap<SigIndex, Arc<FuncSig>>, signatures: &SliceMap<SigIndex, FuncSig>,
trampolines: Arc<Trampolines>, trampolines: Arc<Trampolines>,
handler_data: HandlerData, handler_data: HandlerData,
) -> CompileResult<(FuncResolver, BackendCache)> { ) -> CompileResult<(FuncResolver, BackendCache)> {
@ -288,8 +288,8 @@ impl FuncResolverBuilder {
}, },
}, },
RelocationType::Signature(sig_index) => { RelocationType::Signature(sig_index) => {
let sig_index = let signature = SigRegistry.lookup_signature_ref(&signatures[sig_index]);
SigRegistry.lookup_sig_index(Arc::clone(&signatures[sig_index])); let sig_index = SigRegistry.lookup_sig_index(signature);
sig_index.index() as _ sig_index.index() as _
} }
}; };

View File

@ -153,11 +153,11 @@ impl ProtectedCaller for Caller {
} }
} }
fn get_func_from_index( fn get_func_from_index<'a>(
module: &ModuleInner, module: &'a ModuleInner,
import_backing: &ImportBacking, import_backing: &ImportBacking,
func_index: FuncIndex, func_index: FuncIndex,
) -> (*const vm::Func, Context, Arc<FuncSig>, SigIndex) { ) -> (*const vm::Func, Context, &'a FuncSig, SigIndex) {
let sig_index = *module let sig_index = *module
.info .info
.func_assoc .func_assoc
@ -183,7 +183,7 @@ fn get_func_from_index(
} }
}; };
let signature = Arc::clone(&module.info.signatures[sig_index]); let signature = &module.info.signatures[sig_index];
(func_ptr, ctx, signature, sig_index) (func_ptr, ctx, signature, sig_index)
} }

View File

@ -22,5 +22,12 @@ rand = "0.6"
wasmer-clif-backend = { path = "../clif-backend", version = "0.2.0" } wasmer-clif-backend = { path = "../clif-backend", version = "0.2.0" }
wabt = "0.7.2" wabt = "0.7.2"
[target.'cfg(not(windows))'.dev-dependencies]
wasmer-llvm-backend = { path = "../llvm-backend", version = "0.1.0" }
[build-dependencies] [build-dependencies]
glob = "0.2.11" glob = "0.2.11"
[features]
clif = []
llvm = []

View File

@ -343,7 +343,7 @@ impl EmscriptenGlobals {
if name == "abortOnCannotGrowMemory" && namespace == "env" { if name == "abortOnCannotGrowMemory" && namespace == "env" {
let sig_index = module.info().func_assoc[index.convert_up(module.info())]; let sig_index = module.info().func_assoc[index.convert_up(module.info())];
let expected_sig = &module.info().signatures[sig_index]; let expected_sig = &module.info().signatures[sig_index];
if **expected_sig == *OLD_ABORT_ON_CANNOT_GROW_MEMORY_SIG { if *expected_sig == *OLD_ABORT_ON_CANNOT_GROW_MEMORY_SIG {
use_old_abort_on_cannot_grow_memory = true; use_old_abort_on_cannot_grow_memory = true;
} }
break; break;

View File

@ -169,15 +169,34 @@ mod tests {
use super::is_emscripten_module; use super::is_emscripten_module;
use std::sync::Arc; use std::sync::Arc;
use wabt::wat2wasm; use wabt::wat2wasm;
use wasmer_clif_backend::CraneliftCompiler; use wasmer_runtime_core::backend::Compiler;
use wasmer_runtime_core::compile_with; use wasmer_runtime_core::compile_with;
#[cfg(feature = "clif")]
fn get_compiler() -> impl Compiler {
use wasmer_clif_backend::CraneliftCompiler;
CraneliftCompiler::new()
}
#[cfg(feature = "llvm")]
fn get_compiler() -> impl Compiler {
use wasmer_llvm_backend::LLVMCompiler;
LLVMCompiler::new()
}
#[cfg(not(any(feature = "llvm", feature = "clif")))]
fn get_compiler() -> impl Compiler {
panic!("compiler not specified, activate a compiler via features");
use wasmer_clif_backend::CraneliftCompiler;
CraneliftCompiler::new()
}
#[test] #[test]
fn should_detect_emscripten_files() { fn should_detect_emscripten_files() {
const WAST_BYTES: &[u8] = include_bytes!("tests/is_emscripten_true.wast"); const WAST_BYTES: &[u8] = include_bytes!("tests/is_emscripten_true.wast");
let wasm_binary = wat2wasm(WAST_BYTES.to_vec()).expect("Can't convert to wasm"); let wasm_binary = wat2wasm(WAST_BYTES.to_vec()).expect("Can't convert to wasm");
let module = compile_with(&wasm_binary[..], &CraneliftCompiler::new()) let module =
.expect("WASM can't be compiled"); compile_with(&wasm_binary[..], &get_compiler()).expect("WASM can't be compiled");
let module = Arc::new(module); let module = Arc::new(module);
assert!(is_emscripten_module(&module)); assert!(is_emscripten_module(&module));
} }
@ -186,8 +205,8 @@ mod tests {
fn should_detect_non_emscripten_files() { fn should_detect_non_emscripten_files() {
const WAST_BYTES: &[u8] = include_bytes!("tests/is_emscripten_false.wast"); const WAST_BYTES: &[u8] = include_bytes!("tests/is_emscripten_false.wast");
let wasm_binary = wat2wasm(WAST_BYTES.to_vec()).expect("Can't convert to wasm"); let wasm_binary = wat2wasm(WAST_BYTES.to_vec()).expect("Can't convert to wasm");
let module = compile_with(&wasm_binary[..], &CraneliftCompiler::new()) let module =
.expect("WASM can't be compiled"); compile_with(&wasm_binary[..], &get_compiler()).expect("WASM can't be compiled");
let module = Arc::new(module); let module = Arc::new(module);
assert!(!is_emscripten_module(&module)); assert!(!is_emscripten_module(&module));
} }

View File

@ -1,16 +1,35 @@
macro_rules! assert_emscripten_output { macro_rules! assert_emscripten_output {
($file:expr, $name:expr, $args:expr, $expected:expr) => {{ ($file:expr, $name:expr, $args:expr, $expected:expr) => {{
use wasmer_clif_backend::CraneliftCompiler;
use wasmer_emscripten::{ use wasmer_emscripten::{
EmscriptenGlobals, EmscriptenGlobals,
generate_emscripten_env, generate_emscripten_env,
stdio::StdioCapturer stdio::StdioCapturer
}; };
use wasmer_runtime_core::backend::Compiler;
#[cfg(feature = "clif")]
fn get_compiler() -> impl Compiler {
use wasmer_clif_backend::CraneliftCompiler;
CraneliftCompiler::new()
}
#[cfg(feature = "llvm")]
fn get_compiler() -> impl Compiler {
use wasmer_llvm_backend::LLVMCompiler;
LLVMCompiler::new()
}
#[cfg(not(any(feature = "llvm", feature = "clif")))]
fn get_compiler() -> impl Compiler {
panic!("compiler not specified, activate a compiler via features");
use wasmer_clif_backend::CraneliftCompiler;
CraneliftCompiler::new()
}
let wasm_bytes = include_bytes!($file); let wasm_bytes = include_bytes!($file);
let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &CraneliftCompiler::new()) let module = wasmer_runtime_core::compile_with(&wasm_bytes[..], &get_compiler())
.expect("WASM can't be compiled"); .expect("WASM can't be compiled");
// let module = compile(&wasm_bytes[..]) // let module = compile(&wasm_bytes[..])

View File

@ -0,0 +1,29 @@
[package]
name = "wasmer-llvm-backend"
version = "0.1.0"
authors = ["Lachlan Sneff <lachlan.sneff@gmail.com>"]
edition = "2018"
[dependencies]
wasmer-runtime-core = { path = "../runtime-core", version = "0.2.1" }
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm7-0" }
wasmparser = "0.28.0"
hashbrown = "0.1.8"
smallvec = "0.6.8"
goblin = "0.0.20"
libc = "0.2.49"
nix = "0.13.0"
capstone = { version = "0.5.0", optional = true }
[build-dependencies]
cc = "1.0"
lazy_static = "1.2.0"
regex = "1.1.0"
semver = "0.9"
[dev-dependencies]
wabt = "0.7.4"
[features]
debug = ["wasmer-runtime-core/debug"]
disasm = ["capstone"]

215
lib/llvm-backend/build.rs Normal file
View File

@ -0,0 +1,215 @@
//! This file was mostly taken from the llvm-sys crate.
//! (https://bitbucket.org/tari/llvm-sys.rs/src/21ab524ec4df1450035df895209c3f8fbeb8775f/build.rs?at=default&fileviewer=file-view-default)
use lazy_static::lazy_static;
use regex::Regex;
use semver::Version;
use std::env;
use std::ffi::OsStr;
use std::io::{self, ErrorKind};
use std::path::PathBuf;
use std::process::Command;
lazy_static! {
/// LLVM version used by this version of the crate.
static ref CRATE_VERSION: Version = {
let crate_version = Version::parse(env!("CARGO_PKG_VERSION"))
.expect("Crate version is somehow not valid semver");
Version {
major: crate_version.major / 10,
minor: crate_version.major % 10,
.. crate_version
}
};
static ref LLVM_CONFIG_BINARY_NAMES: Vec<String> = {
vec![
"llvm-config".into(),
// format!("llvm-config-{}", CRATE_VERSION.major),
// format!("llvm-config-{}.{}", CRATE_VERSION.major, CRATE_VERSION.minor),
]
};
/// Filesystem path to an llvm-config binary for the correct version.
static ref LLVM_CONFIG_PATH: PathBuf = {
// Try llvm-config via PATH first.
if let Some(name) = locate_system_llvm_config() {
return name.into();
} else {
println!("Didn't find usable system-wide LLVM.");
}
// Did the user give us a binary path to use? If yes, try
// to use that and fail if it doesn't work.
let binary_prefix_var = "LLVM_SYS_70_PREFIX";
let path = if let Some(path) = env::var_os(&binary_prefix_var) {
Some(path.to_str().unwrap().to_owned())
} else if let Ok(mut file) = std::fs::File::open(".llvmenv") {
use std::io::Read;
let mut s = String::new();
file.read_to_string(&mut s).unwrap();
s.truncate(s.len() - 4);
Some(s)
} else {
None
};
if let Some(path) = path {
for binary_name in LLVM_CONFIG_BINARY_NAMES.iter() {
let mut pb: PathBuf = path.clone().into();
pb.push("bin");
pb.push(binary_name);
let ver = llvm_version(&pb)
.expect(&format!("Failed to execute {:?}", &pb));
if is_compatible_llvm(&ver) {
return pb;
} else {
println!("LLVM binaries specified by {} are the wrong version.
(Found {}, need {}.)", binary_prefix_var, ver, *CRATE_VERSION);
}
}
}
println!("No suitable version of LLVM was found system-wide or pointed
to by {}.
Consider using `llvmenv` to compile an appropriate copy of LLVM, and
refer to the llvm-sys documentation for more information.
llvm-sys: https://crates.io/crates/llvm-sys
llvmenv: https://crates.io/crates/llvmenv", binary_prefix_var);
panic!("Could not find a compatible version of LLVM");
};
}
/// Try to find a system-wide version of llvm-config that is compatible with
/// this crate.
///
/// Returns None on failure.
fn locate_system_llvm_config() -> Option<&'static str> {
for binary_name in LLVM_CONFIG_BINARY_NAMES.iter() {
match llvm_version(binary_name) {
Ok(ref version) if is_compatible_llvm(version) => {
// Compatible version found. Nice.
return Some(binary_name);
}
Ok(version) => {
// Version mismatch. Will try further searches, but warn that
// we're not using the system one.
println!(
"Found LLVM version {} on PATH, but need {}.",
version, *CRATE_VERSION
);
}
Err(ref e) if e.kind() == ErrorKind::NotFound => {
// Looks like we failed to execute any llvm-config. Keep
// searching.
}
// Some other error, probably a weird failure. Give up.
Err(e) => panic!("Failed to search PATH for llvm-config: {}", e),
}
}
None
}
/// Check whether the given LLVM version is compatible with this version of
/// the crate.
fn is_compatible_llvm(llvm_version: &Version) -> bool {
let strict = env::var_os(format!(
"LLVM_SYS_{}_STRICT_VERSIONING",
env!("CARGO_PKG_VERSION_MAJOR")
))
.is_some()
|| cfg!(feature = "strict-versioning");
if strict {
llvm_version.major == CRATE_VERSION.major && llvm_version.minor == CRATE_VERSION.minor
} else {
llvm_version.major >= CRATE_VERSION.major
|| (llvm_version.major == CRATE_VERSION.major
&& llvm_version.minor >= CRATE_VERSION.minor)
}
}
/// Get the output from running `llvm-config` with the given argument.
///
/// Lazily searches for or compiles LLVM as configured by the environment
/// variables.
fn llvm_config(arg: &str) -> String {
llvm_config_ex(&*LLVM_CONFIG_PATH, arg).expect("Surprising failure from llvm-config")
}
/// Invoke the specified binary as llvm-config.
///
/// Explicit version of the `llvm_config` function that bubbles errors
/// up.
fn llvm_config_ex<S: AsRef<OsStr>>(binary: S, arg: &str) -> io::Result<String> {
Command::new(binary)
.arg(arg)
.arg("--link-static") // Don't use dylib for >= 3.9
.output()
.map(|output| {
String::from_utf8(output.stdout).expect("Output from llvm-config was not valid UTF-8")
})
}
/// Get the LLVM version using llvm-config.
fn llvm_version<S: AsRef<OsStr>>(binary: S) -> io::Result<Version> {
let version_str = llvm_config_ex(binary.as_ref(), "--version")?;
// LLVM isn't really semver and uses version suffixes to build
// version strings like '3.8.0svn', so limit what we try to parse
// to only the numeric bits.
let re = Regex::new(r"^(?P<major>\d+)\.(?P<minor>\d+)(?:\.(?P<patch>\d+))??").unwrap();
let c = re
.captures(&version_str)
.expect("Could not determine LLVM version from llvm-config.");
// some systems don't have a patch number but Version wants it so we just append .0 if it isn't
// there
let s = match c.name("patch") {
None => format!("{}.0", &c[0]),
Some(_) => c[0].to_string(),
};
Ok(Version::parse(&s).unwrap())
}
fn get_llvm_cxxflags() -> String {
let output = llvm_config("--cxxflags");
// llvm-config includes cflags from its own compilation with --cflags that
// may not be relevant to us. In particularly annoying cases, these might
// include flags that aren't understood by the default compiler we're
// using. Unless requested otherwise, clean CFLAGS of options that are
// known to be possibly-harmful.
let no_clean = env::var_os(format!(
"LLVM_SYS_{}_NO_CLEAN_CFLAGS",
env!("CARGO_PKG_VERSION_MAJOR")
))
.is_some();
if no_clean || cfg!(target_env = "msvc") {
// MSVC doesn't accept -W... options, so don't try to strip them and
// possibly strip something that should be retained. Also do nothing if
// the user requests it.
return output;
}
output
.split(&[' ', '\n'][..])
.filter(|word| !word.starts_with("-W"))
.filter(|word| word != &"-fno-exceptions")
.collect::<Vec<_>>()
.join(" ")
}
fn main() {
std::env::set_var("CXXFLAGS", get_llvm_cxxflags());
cc::Build::new()
.cpp(true)
.file("cpp/object_loader.cpp")
.compile("llvm-backend");
println!("cargo:rustc-link-lib=static=llvm-backend");
}

View File

@ -0,0 +1,199 @@
#include "object_loader.hh"
#include <iostream>
#include <memory>
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);
}
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);
}
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);
}
}
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;
}
/* Turn on the `reserveAllocationSpace` callback. */
virtual bool needsToReserveAllocationSpace() override {
return true;
}
virtual void registerEHFrames(uint8_t* addr, uint64_t LoadAddr, size_t size) override {
eh_frame_ptr = addr;
eh_frame_size = size;
eh_frames_registered = true;
callbacks.visit_fde(addr, size, __register_frame);
}
virtual void deregisterEHFrames() override {
if (eh_frames_registered) {
callbacks.visit_fde(eh_frame_ptr, eh_frame_size, __deregister_frame);
}
}
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.
return false;
}
virtual void notifyObjectLoaded(llvm::RuntimeDyld &RTDyld, const llvm::object::ObjectFile &Obj) override {}
private:
struct Section {
uint8_t* base;
size_t size;
};
uint8_t* allocate_bump(Section& section, uintptr_t& bump_ptr, size_t size, size_t align) {
auto aligner = [](uintptr_t& ptr, size_t align) {
ptr = (ptr + align - 1) & ~(align - 1);
};
// Align the bump pointer to the requires alignment.
aligner(bump_ptr, align);
auto ret_ptr = bump_ptr;
bump_ptr += size;
assert(bump_ptr <= (uintptr_t)section.base + section.size);
return (uint8_t*)ret_ptr;
}
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;
callbacks_t callbacks;
};
struct SymbolLookup : llvm::JITSymbolResolver {
public:
SymbolLookup(callbacks_t callbacks) : callbacks(callbacks) {}
virtual llvm::Expected<LookupResult> lookup(const LookupSet& symbols) override {
LookupResult result;
for (auto symbol : symbols) {
result.emplace(symbol, symbol_lookup(symbol));
}
return result;
}
virtual llvm::Expected<LookupFlagsResult> lookupFlags(const LookupSet& symbols) override {
LookupFlagsResult result;
for (auto symbol : symbols) {
result.emplace(symbol, symbol_lookup(symbol).getFlags());
}
return result;
}
private:
llvm::JITEvaluatedSymbol symbol_lookup(llvm::StringRef name) {
uint64_t addr = callbacks.lookup_vm_symbol(name.data(), name.size());
return llvm::JITEvaluatedSymbol(addr, llvm::JITSymbolFlags::None);
}
callbacks_t callbacks;
};
WasmModule::WasmModule(
const uint8_t *object_start,
size_t object_size,
callbacks_t callbacks
) : memory_manager(std::unique_ptr<MemoryManager>(new MemoryManager(callbacks)))
{
object_file = llvm::cantFail(llvm::object::ObjectFile::createObjectFile(llvm::MemoryBufferRef(
llvm::StringRef((const char *)object_start, object_size), "object"
)));
SymbolLookup symbol_resolver(callbacks);
runtime_dyld = std::unique_ptr<llvm::RuntimeDyld>(new llvm::RuntimeDyld(*memory_manager, symbol_resolver));
runtime_dyld->setProcessAllSections(true);
runtime_dyld->loadObject(*object_file);
runtime_dyld->finalizeWithMemoryManagerLocking();
if (runtime_dyld->hasError()) {
std::cout << "RuntimeDyld error: " << (std::string)runtime_dyld->getErrorString() << std::endl;
abort();
}
}
void* WasmModule::get_func(llvm::StringRef name) const {
auto symbol = runtime_dyld->getSymbol(name);
return (void*)symbol.getAddress();
}

View File

@ -0,0 +1,185 @@
#include <cstddef>
#include <cstdint>
#include <llvm/ExecutionEngine/RuntimeDyld.h>
#include <iostream>
#include <sstream>
#include <exception>
typedef enum {
PROTECT_NONE,
PROTECT_READ,
PROTECT_READ_WRITE,
PROTECT_READ_EXECUTE,
} mem_protect_t;
typedef enum {
RESULT_OK,
RESULT_ALLOCATE_FAILURE,
RESULT_PROTECT_FAILURE,
RESULT_DEALLOC_FAILURE,
RESULT_OBJECT_LOAD_FAILURE,
} result_t;
typedef result_t (*alloc_memory_t)(size_t size, mem_protect_t protect, uint8_t** ptr_out, size_t* size_out);
typedef result_t (*protect_memory_t)(uint8_t* ptr, size_t size, mem_protect_t protect);
typedef result_t (*dealloc_memory_t)(uint8_t* ptr, size_t size);
typedef uintptr_t (*lookup_vm_symbol_t)(const char* name_ptr, size_t length);
typedef void (*fde_visitor_t)(uint8_t *fde);
typedef result_t (*visit_fde_t)(uint8_t *fde, size_t size, fde_visitor_t visitor);
typedef void (*trampoline_t)(void*, void*, void*, void*);
typedef struct {
/* Memory management. */
alloc_memory_t alloc_memory;
protect_memory_t protect_memory;
dealloc_memory_t dealloc_memory;
lookup_vm_symbol_t lookup_vm_symbol;
visit_fde_t visit_fde;
} callbacks_t;
struct WasmException {
public:
virtual std::string description() const noexcept = 0;
};
struct UncatchableException : WasmException {
public:
virtual std::string description() const noexcept override {
return "Uncatchable exception";
}
};
struct UserException : UncatchableException {
public:
UserException(std::string msg) : msg(msg) {}
virtual std::string description() const noexcept override {
return std::string("user exception: ") + msg;
}
private:
std::string msg;
};
struct WasmTrap : UncatchableException {
public:
enum Type {
Unreachable = 0,
IncorrectCallIndirectSignature = 1,
MemoryOutOfBounds = 2,
CallIndirectOOB = 3,
IllegalArithmetic = 4,
Unknown,
};
WasmTrap(Type type) : type(type) {}
virtual std::string description() const noexcept override {
std::ostringstream ss;
ss
<< "WebAssembly trap:" << '\n'
<< " - type: " << type << '\n';
return ss.str();
}
Type type;
private:
friend std::ostream& operator<<(std::ostream& out, const Type& ty) {
switch (ty) {
case Type::Unreachable:
out << "unreachable";
break;
case Type::IncorrectCallIndirectSignature:
out << "incorrect call_indirect signature";
break;
case Type::MemoryOutOfBounds:
out << "memory access out-of-bounds";
break;
case Type::CallIndirectOOB:
out << "call_indirect out-of-bounds";
break;
case Type::IllegalArithmetic:
out << "illegal arithmetic operation";
break;
case Type::Unknown:
default:
out << "unknown";
break;
}
return out;
}
};
struct CatchableException : WasmException {
public:
CatchableException(uint32_t type_id, uint32_t value_num) : type_id(type_id), value_num(value_num) {}
virtual std::string description() const noexcept override {
return "catchable exception";
}
uint32_t type_id, value_num;
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;
private:
std::unique_ptr<llvm::RuntimeDyld::MemoryManager> memory_manager;
std::unique_ptr<llvm::object::ObjectFile> object_file;
std::unique_ptr<llvm::RuntimeDyld> runtime_dyld;
};
extern "C" {
result_t module_load(const uint8_t* mem_ptr, size_t mem_size, callbacks_t callbacks, WasmModule** module_out) {
*module_out = new WasmModule(mem_ptr, mem_size, callbacks);
return RESULT_OK;
}
[[noreturn]] void throw_trap(WasmTrap::Type ty) {
throw WasmTrap(ty);
}
void module_delete(WasmModule* module) {
delete module;
}
bool invoke_trampoline(
trampoline_t trampoline,
void* ctx,
void* func,
void* params,
void* results,
WasmTrap::Type* trap_out
) throw() {
try {
trampoline(ctx, func, params, results);
return true;
} catch(const WasmTrap& e) {
*trap_out = e.type;
return false;
} catch(const WasmException& e) {
*trap_out = WasmTrap::Type::Unknown;
return false;
} catch (...) {
*trap_out = WasmTrap::Type::Unknown;
return false;
}
}
void* get_func_symbol(WasmModule* module, const char* name) {
return module->get_func(llvm::StringRef(name));
}
}

View File

@ -0,0 +1,502 @@
use crate::intrinsics::Intrinsics;
use inkwell::{
memory_buffer::MemoryBuffer,
module::Module,
targets::{CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine},
OptimizationLevel,
};
use libc::{
c_char, mmap, mprotect, munmap, MAP_ANON, MAP_PRIVATE, PROT_EXEC, PROT_NONE, PROT_READ,
PROT_WRITE,
};
use std::{
any::Any,
ffi::CString,
mem,
ptr::{self, NonNull},
slice, str,
sync::Once,
};
use wasmer_runtime_core::{
backend::{FuncResolver, ProtectedCaller, Token, UserTrapper},
error::{RuntimeError, RuntimeResult},
export::Context,
module::{ModuleInfo, ModuleInner},
structures::TypedIndex,
types::{
FuncIndex, FuncSig, LocalFuncIndex, LocalOrImport, MemoryIndex, SigIndex, TableIndex, Type,
Value,
},
vm::{self, ImportBacking},
vmcalls,
};
#[repr(C)]
struct LLVMModule {
_private: [u8; 0],
}
#[allow(non_camel_case_types, dead_code)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
enum MemProtect {
NONE,
READ,
READ_WRITE,
READ_EXECUTE,
}
#[allow(non_camel_case_types, dead_code)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
#[repr(C)]
enum LLVMResult {
OK,
ALLOCATE_FAILURE,
PROTECT_FAILURE,
DEALLOC_FAILURE,
OBJECT_LOAD_FAILURE,
}
#[repr(C)]
enum WasmTrapType {
Unreachable = 0,
IncorrectCallIndirectSignature = 1,
MemoryOutOfBounds = 2,
CallIndirectOOB = 3,
IllegalArithmetic = 4,
Unknown,
}
#[repr(C)]
struct Callbacks {
alloc_memory: extern "C" fn(usize, MemProtect, &mut *mut u8, &mut usize) -> LLVMResult,
protect_memory: extern "C" fn(*mut u8, usize, MemProtect) -> LLVMResult,
dealloc_memory: extern "C" fn(*mut u8, usize) -> LLVMResult,
lookup_vm_symbol: extern "C" fn(*const c_char, usize) -> *const vm::Func,
visit_fde: extern "C" fn(*mut u8, usize, extern "C" fn(*mut u8)),
}
extern "C" {
fn module_load(
mem_ptr: *const u8,
mem_size: usize,
callbacks: Callbacks,
module_out: &mut *mut LLVMModule,
) -> LLVMResult;
fn module_delete(module: *mut LLVMModule);
fn get_func_symbol(module: *mut LLVMModule, name: *const c_char) -> *const vm::Func;
fn throw_trap(ty: i32);
fn invoke_trampoline(
trampoline: unsafe extern "C" fn(*mut vm::Ctx, *const vm::Func, *const u64, *mut u64),
vmctx_ptr: *mut vm::Ctx,
func_ptr: *const vm::Func,
params: *const u64,
results: *mut u64,
trap_out: *mut WasmTrapType,
) -> bool;
}
fn get_callbacks() -> Callbacks {
fn round_up_to_page_size(size: usize) -> usize {
(size + (4096 - 1)) & !(4096 - 1)
}
extern "C" fn alloc_memory(
size: usize,
protect: MemProtect,
ptr_out: &mut *mut u8,
size_out: &mut usize,
) -> LLVMResult {
let size = round_up_to_page_size(size);
let ptr = unsafe {
mmap(
ptr::null_mut(),
size,
match protect {
MemProtect::NONE => PROT_NONE,
MemProtect::READ => PROT_READ,
MemProtect::READ_WRITE => PROT_READ | PROT_WRITE,
MemProtect::READ_EXECUTE => PROT_READ | PROT_EXEC,
},
MAP_PRIVATE | MAP_ANON,
-1,
0,
)
};
if ptr as isize == -1 {
return LLVMResult::ALLOCATE_FAILURE;
}
*ptr_out = ptr as _;
*size_out = size;
LLVMResult::OK
}
extern "C" fn protect_memory(ptr: *mut u8, size: usize, protect: MemProtect) -> LLVMResult {
let res = unsafe {
mprotect(
ptr as _,
round_up_to_page_size(size),
match protect {
MemProtect::NONE => PROT_NONE,
MemProtect::READ => PROT_READ,
MemProtect::READ_WRITE => PROT_READ | PROT_WRITE,
MemProtect::READ_EXECUTE => PROT_READ | PROT_EXEC,
},
)
};
if res == 0 {
LLVMResult::OK
} else {
LLVMResult::PROTECT_FAILURE
}
}
extern "C" fn dealloc_memory(ptr: *mut u8, size: usize) -> LLVMResult {
let res = unsafe { munmap(ptr as _, round_up_to_page_size(size)) };
if res == 0 {
LLVMResult::OK
} else {
LLVMResult::DEALLOC_FAILURE
}
}
extern "C" fn lookup_vm_symbol(name_ptr: *const c_char, length: usize) -> *const vm::Func {
#[cfg(target_os = "macos")]
macro_rules! fn_name {
($s:literal) => {
concat!("_", $s)
};
}
#[cfg(not(target_os = "macos"))]
macro_rules! fn_name {
($s:literal) => {
$s
};
}
let name_slice = unsafe { slice::from_raw_parts(name_ptr as *const u8, length) };
let name = str::from_utf8(name_slice).unwrap();
match name {
fn_name!("vm.memory.grow.dynamic.local") => vmcalls::local_dynamic_memory_grow as _,
fn_name!("vm.memory.size.dynamic.local") => vmcalls::local_dynamic_memory_size as _,
fn_name!("vm.memory.grow.static.local") => vmcalls::local_static_memory_grow as _,
fn_name!("vm.memory.size.static.local") => vmcalls::local_static_memory_size as _,
fn_name!("vm.exception.trap") => throw_trap as _,
_ => ptr::null(),
}
}
extern "C" fn visit_fde(fde: *mut u8, size: usize, visitor: extern "C" fn(*mut u8)) {
unsafe {
crate::platform::visit_fde(fde, size, visitor);
}
}
Callbacks {
alloc_memory,
protect_memory,
dealloc_memory,
lookup_vm_symbol,
visit_fde,
}
}
unsafe impl Send for LLVMBackend {}
unsafe impl Sync for LLVMBackend {}
pub struct LLVMBackend {
module: *mut LLVMModule,
#[allow(dead_code)]
memory_buffer: MemoryBuffer,
}
impl LLVMBackend {
pub fn new(module: Module, intrinsics: Intrinsics) -> (Self, LLVMProtectedCaller) {
Target::initialize_x86(&InitializationConfig {
asm_parser: true,
asm_printer: true,
base: true,
disassembler: true,
info: true,
machine_code: true,
});
let triple = TargetMachine::get_default_triple().to_string();
let target = Target::from_triple(&triple).unwrap();
let target_machine = target
.create_target_machine(
&triple,
&TargetMachine::get_host_cpu_name().to_string(),
&TargetMachine::get_host_cpu_features().to_string(),
OptimizationLevel::Aggressive,
RelocMode::PIC,
CodeModel::Default,
)
.unwrap();
let memory_buffer = target_machine
.write_to_memory_buffer(&module, FileType::Object)
.unwrap();
let mem_buf_slice = memory_buffer.as_slice();
let callbacks = get_callbacks();
let mut module: *mut LLVMModule = ptr::null_mut();
let res = unsafe {
module_load(
mem_buf_slice.as_ptr(),
mem_buf_slice.len(),
callbacks,
&mut module,
)
};
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")
}
(
Self {
module,
memory_buffer,
},
LLVMProtectedCaller { module },
)
}
pub fn get_func(
&self,
info: &ModuleInfo,
local_func_index: LocalFuncIndex,
) -> Option<NonNull<vm::Func>> {
let index = info.imported_functions.len() + local_func_index.index();
let name = if cfg!(target_os = "macos") {
format!("_fn{}", index)
} else {
format!("fn{}", index)
};
let c_str = CString::new(name).ok()?;
let ptr = unsafe { get_func_symbol(self.module, c_str.as_ptr()) };
NonNull::new(ptr as _)
}
}
impl Drop for LLVMBackend {
fn drop(&mut self) {
unsafe { module_delete(self.module) }
}
}
impl FuncResolver for LLVMBackend {
fn get(
&self,
module: &ModuleInner,
local_func_index: LocalFuncIndex,
) -> Option<NonNull<vm::Func>> {
self.get_func(&module.info, local_func_index)
}
}
struct Placeholder;
unsafe impl Send for LLVMProtectedCaller {}
unsafe impl Sync for LLVMProtectedCaller {}
pub struct LLVMProtectedCaller {
module: *mut LLVMModule,
}
impl ProtectedCaller for LLVMProtectedCaller {
fn call(
&self,
module: &ModuleInner,
func_index: FuncIndex,
params: &[Value],
import_backing: &ImportBacking,
vmctx: *mut vm::Ctx,
_: Token,
) -> RuntimeResult<Vec<Value>> {
let (func_ptr, ctx, signature, sig_index) =
get_func_from_index(&module, import_backing, func_index);
let vmctx_ptr = match ctx {
Context::External(external_vmctx) => external_vmctx,
Context::Internal => vmctx,
};
assert!(
signature.returns().len() <= 1,
"multi-value returns not yet supported"
);
assert!(
signature.check_param_value_types(params),
"incorrect signature"
);
let param_vec: Vec<u64> = params
.iter()
.map(|val| match val {
Value::I32(x) => *x as u64,
Value::I64(x) => *x as u64,
Value::F32(x) => x.to_bits() as u64,
Value::F64(x) => x.to_bits(),
})
.collect();
let mut return_vec = vec![0; signature.returns().len()];
let trampoline: unsafe extern "C" fn(*mut vm::Ctx, *const vm::Func, *const u64, *mut u64) = unsafe {
let name = if cfg!(target_os = "macos") {
format!("_trmp{}", sig_index.index())
} else {
format!("trmp{}", sig_index.index())
};
let c_str = CString::new(name).unwrap();
let symbol = get_func_symbol(self.module, c_str.as_ptr());
assert!(!symbol.is_null());
mem::transmute(symbol)
};
let mut trap_out = WasmTrapType::Unknown;
// Here we go.
let success = unsafe {
invoke_trampoline(
trampoline,
vmctx_ptr,
func_ptr,
param_vec.as_ptr(),
return_vec.as_mut_ptr(),
&mut trap_out,
)
};
if success {
Ok(return_vec
.iter()
.zip(signature.returns().iter())
.map(|(&x, ty)| match ty {
Type::I32 => Value::I32(x as i32),
Type::I64 => Value::I64(x as i64),
Type::F32 => Value::F32(f32::from_bits(x as u32)),
Type::F64 => Value::F64(f64::from_bits(x as u64)),
})
.collect())
} else {
Err(match trap_out {
WasmTrapType::Unreachable => RuntimeError::Trap {
msg: "unreachable".into(),
},
WasmTrapType::IncorrectCallIndirectSignature => RuntimeError::Trap {
msg: "uncorrect call_indirect signature".into(),
},
WasmTrapType::MemoryOutOfBounds => RuntimeError::Trap {
msg: "memory out-of-bounds access".into(),
},
WasmTrapType::CallIndirectOOB => RuntimeError::Trap {
msg: "call_indirect out-of-bounds".into(),
},
WasmTrapType::IllegalArithmetic => RuntimeError::Trap {
msg: "illegal arithmetic operation".into(),
},
WasmTrapType::Unknown => RuntimeError::Trap {
msg: "unknown trap".into(),
},
})
}
}
fn get_early_trapper(&self) -> Box<dyn UserTrapper> {
Box::new(Placeholder)
}
}
impl UserTrapper for Placeholder {
unsafe fn do_early_trap(&self, _data: Box<dyn Any>) -> ! {
unimplemented!("do early trap")
}
}
fn get_func_from_index<'a>(
module: &'a ModuleInner,
import_backing: &ImportBacking,
func_index: FuncIndex,
) -> (*const vm::Func, Context, &'a FuncSig, SigIndex) {
let sig_index = *module
.info
.func_assoc
.get(func_index)
.expect("broken invariant, incorrect func index");
let (func_ptr, ctx) = match func_index.local_or_import(&module.info) {
LocalOrImport::Local(local_func_index) => (
module
.func_resolver
.get(&module, local_func_index)
.expect("broken invariant, func resolver not synced with module.exports")
.cast()
.as_ptr() as *const _,
Context::Internal,
),
LocalOrImport::Import(imported_func_index) => {
let imported_func = import_backing.imported_func(imported_func_index);
(
imported_func.func as *const _,
Context::External(imported_func.vmctx),
)
}
};
let signature = &module.info.signatures[sig_index];
(func_ptr, ctx, signature, sig_index)
}
#[cfg(feature = "disasm")]
unsafe fn disass_ptr(ptr: *const u8, size: usize, inst_count: usize) {
use capstone::arch::BuildsCapstone;
let mut cs = capstone::Capstone::new() // Call builder-pattern
.x86() // X86 architecture
.mode(capstone::arch::x86::ArchMode::Mode64) // 64-bit mode
.detail(true) // Generate extra instruction details
.build()
.expect("Failed to create Capstone object");
// Get disassembled instructions
let insns = cs
.disasm_count(
std::slice::from_raw_parts(ptr, size),
ptr as u64,
inst_count,
)
.expect("Failed to disassemble");
println!("count = {}", insns.len());
for insn in insns.iter() {
println!(
"0x{:x}: {:6} {}",
insn.address(),
insn.mnemonic().unwrap_or(""),
insn.op_str().unwrap_or("")
);
}
}

2465
lib/llvm-backend/src/code.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,61 @@
use inkwell::OptimizationLevel;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::execution_engine::{ExecutionEngine, JitFunction};
use inkwell::module::Module;
use inkwell::targets::{InitializationConfig, Target};
use std::error::Error;
/// Convenience type alias for the `sum` function.
///
/// Calling this is innately `unsafe` because there's no guarantee it doesn't
/// do `unsafe` operations internally.
type SumFunc = unsafe extern "C" fn(u64, u64, u64) -> u64;
#[test]
fn test_sum() -> Result<(), Box<Error>> {
let context = Context::create();
let module = context.create_module("sum");
let builder = context.create_builder();
let execution_engine = module.create_jit_execution_engine(OptimizationLevel::Aggressive)?;
let sum = jit_compile_sum(&context, &module, &builder, &execution_engine)
.ok_or("Unable to JIT compile `sum`")?;
let x = 1u64;
let y = 2u64;
let z = 3u64;
unsafe {
println!("{} + {} + {} = {}", x, y, z, sum.call(x, y, z));
assert_eq!(sum.call(x, y, z), x + y + z);
}
Ok(())
}
fn jit_compile_sum(
context: &Context,
module: &Module,
builder: &Builder,
execution_engine: &ExecutionEngine,
) -> Option<JitFunction<SumFunc>> {
let i64_type = context.i64_type();
let fn_type = i64_type.fn_type(&[i64_type.into(), i64_type.into(), i64_type.into()], false);
let function = module.add_function("sum", fn_type, None);
let basic_block = context.append_basic_block(&function, "entry");
builder.position_at_end(&basic_block);
let x = function.get_nth_param(0)?.into_int_value();
let y = function.get_nth_param(1)?.into_int_value();
let z = function.get_nth_param(2)?.into_int_value();
let sum = builder.build_int_add(x, y, "sum");
let sum = builder.build_int_add(sum, z, "sum");
builder.build_return(Some(&sum));
unsafe { execution_engine.get_function("sum").ok() }
}

View File

@ -0,0 +1,772 @@
use hashbrown::HashMap;
use inkwell::{
builder::Builder,
context::Context,
module::Module,
types::{BasicType, FloatType, IntType, PointerType, StructType, VoidType},
values::{
BasicValue, BasicValueEnum, FloatValue, FunctionValue, InstructionValue, IntValue,
PointerValue,
},
AddressSpace,
};
use std::marker::PhantomData;
use wasmer_runtime_core::{
memory::MemoryType,
module::ModuleInfo,
structures::TypedIndex,
types::{
GlobalIndex, ImportedFuncIndex, LocalOrImport, MemoryIndex, SigIndex, TableIndex, Type,
},
};
fn type_to_llvm_ptr(intrinsics: &Intrinsics, ty: Type) -> PointerType {
match 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,
}
}
pub struct Intrinsics {
pub ctlz_i32: FunctionValue,
pub ctlz_i64: FunctionValue,
pub cttz_i32: FunctionValue,
pub cttz_i64: FunctionValue,
pub ctpop_i32: FunctionValue,
pub ctpop_i64: FunctionValue,
pub sqrt_f32: FunctionValue,
pub sqrt_f64: FunctionValue,
pub minimum_f32: FunctionValue,
pub minimum_f64: FunctionValue,
pub maximum_f32: FunctionValue,
pub maximum_f64: FunctionValue,
pub ceil_f32: FunctionValue,
pub ceil_f64: FunctionValue,
pub floor_f32: FunctionValue,
pub floor_f64: FunctionValue,
pub trunc_f32: FunctionValue,
pub trunc_f64: FunctionValue,
pub nearbyint_f32: FunctionValue,
pub nearbyint_f64: FunctionValue,
pub fabs_f32: FunctionValue,
pub fabs_f64: FunctionValue,
pub copysign_f32: FunctionValue,
pub copysign_f64: FunctionValue,
pub expect_i1: FunctionValue,
pub trap: FunctionValue,
pub void_ty: VoidType,
pub i1_ty: IntType,
pub i8_ty: IntType,
pub i16_ty: IntType,
pub i32_ty: IntType,
pub i64_ty: IntType,
pub f32_ty: FloatType,
pub f64_ty: FloatType,
pub i8_ptr_ty: PointerType,
pub i16_ptr_ty: PointerType,
pub i32_ptr_ty: PointerType,
pub i64_ptr_ty: PointerType,
pub f32_ptr_ty: PointerType,
pub f64_ptr_ty: PointerType,
pub anyfunc_ty: StructType,
pub i1_zero: IntValue,
pub i32_zero: IntValue,
pub i64_zero: IntValue,
pub f32_zero: FloatValue,
pub f64_zero: FloatValue,
pub trap_unreachable: BasicValueEnum,
pub trap_call_indirect_sig: BasicValueEnum,
pub trap_call_indirect_oob: BasicValueEnum,
pub trap_memory_oob: BasicValueEnum,
pub trap_illegal_arithmetic: BasicValueEnum,
// VM intrinsics.
pub memory_grow_dynamic_local: FunctionValue,
pub memory_grow_static_local: FunctionValue,
pub memory_grow_shared_local: FunctionValue,
pub memory_grow_dynamic_import: FunctionValue,
pub memory_grow_static_import: FunctionValue,
pub memory_grow_shared_import: FunctionValue,
pub memory_size_dynamic_local: FunctionValue,
pub memory_size_static_local: FunctionValue,
pub memory_size_shared_local: FunctionValue,
pub memory_size_dynamic_import: FunctionValue,
pub memory_size_static_import: FunctionValue,
pub memory_size_shared_import: FunctionValue,
pub throw_trap: FunctionValue,
ctx_ty: StructType,
pub ctx_ptr_ty: PointerType,
}
impl Intrinsics {
pub fn declare(module: &Module, context: &Context) -> Self {
let void_ty = context.void_type();
let i1_ty = context.bool_type();
let i8_ty = context.i8_type();
let i16_ty = context.i16_type();
let i32_ty = context.i32_type();
let i64_ty = context.i64_type();
let f32_ty = context.f32_type();
let f64_ty = context.f64_type();
let i8_ptr_ty = i8_ty.ptr_type(AddressSpace::Generic);
let i16_ptr_ty = i16_ty.ptr_type(AddressSpace::Generic);
let i32_ptr_ty = i32_ty.ptr_type(AddressSpace::Generic);
let i64_ptr_ty = i64_ty.ptr_type(AddressSpace::Generic);
let f32_ptr_ty = f32_ty.ptr_type(AddressSpace::Generic);
let f64_ptr_ty = f64_ty.ptr_type(AddressSpace::Generic);
let i1_zero = i1_ty.const_int(0, false);
let i32_zero = i32_ty.const_int(0, false);
let i64_zero = i64_ty.const_int(0, false);
let f32_zero = f32_ty.const_float(0.0);
let f64_zero = f64_ty.const_float(0.0);
let i1_ty_basic = i1_ty.as_basic_type_enum();
let i32_ty_basic = i32_ty.as_basic_type_enum();
let i64_ty_basic = i64_ty.as_basic_type_enum();
let f32_ty_basic = f32_ty.as_basic_type_enum();
let f64_ty_basic = f64_ty.as_basic_type_enum();
let i8_ptr_ty_basic = i8_ptr_ty.as_basic_type_enum();
let ctx_ty = context.opaque_struct_type("ctx");
let ctx_ptr_ty = ctx_ty.ptr_type(AddressSpace::Generic);
let local_memory_ty =
context.struct_type(&[i8_ptr_ty_basic, i64_ty_basic, i8_ptr_ty_basic], false);
let local_table_ty = local_memory_ty;
let local_global_ty = i64_ty;
let imported_func_ty =
context.struct_type(&[i8_ptr_ty_basic, ctx_ptr_ty.as_basic_type_enum()], false);
let sigindex_ty = i32_ty;
let anyfunc_ty = context.struct_type(
&[
i8_ptr_ty_basic,
ctx_ptr_ty.as_basic_type_enum(),
sigindex_ty.as_basic_type_enum(),
],
false,
);
ctx_ty.set_body(
&[
local_memory_ty
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
local_table_ty
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
local_global_ty
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
local_memory_ty
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
local_table_ty
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
local_global_ty
.ptr_type(AddressSpace::Generic)
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
imported_func_ty
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
sigindex_ty
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(),
],
false,
);
let ret_i32_take_i32_i1 = i32_ty.fn_type(&[i32_ty_basic, i1_ty_basic], false);
let ret_i64_take_i64_i1 = i64_ty.fn_type(&[i64_ty_basic, i1_ty_basic], false);
let ret_i32_take_i32 = i32_ty.fn_type(&[i32_ty_basic], false);
let ret_i64_take_i64 = i64_ty.fn_type(&[i64_ty_basic], false);
let ret_f32_take_f32 = f32_ty.fn_type(&[f32_ty_basic], false);
let ret_f64_take_f64 = f64_ty.fn_type(&[f64_ty_basic], false);
let ret_f32_take_f32_f32 = f32_ty.fn_type(&[f32_ty_basic, f32_ty_basic], false);
let ret_f64_take_f64_f64 = f64_ty.fn_type(&[f64_ty_basic, f64_ty_basic], false);
let ret_i32_take_ctx_i32_i32 = i32_ty.fn_type(
&[ctx_ptr_ty.as_basic_type_enum(), i32_ty_basic, i32_ty_basic],
false,
);
let ret_i32_take_ctx_i32 =
i32_ty.fn_type(&[ctx_ptr_ty.as_basic_type_enum(), i32_ty_basic], false);
let ret_i1_take_i1_i1 = i1_ty.fn_type(&[i1_ty_basic, i1_ty_basic], false);
Self {
ctlz_i32: module.add_function("llvm.ctlz.i32", ret_i32_take_i32_i1, None),
ctlz_i64: module.add_function("llvm.ctlz.i64", ret_i64_take_i64_i1, None),
cttz_i32: module.add_function("llvm.cttz.i32", ret_i32_take_i32_i1, None),
cttz_i64: module.add_function("llvm.cttz.i64", ret_i64_take_i64_i1, None),
ctpop_i32: module.add_function("llvm.ctpop.i32", ret_i32_take_i32, None),
ctpop_i64: module.add_function("llvm.ctpop.i64", ret_i64_take_i64, None),
sqrt_f32: module.add_function("llvm.sqrt.f32", ret_f32_take_f32, None),
sqrt_f64: module.add_function("llvm.sqrt.f64", ret_f64_take_f64, None),
minimum_f32: module.add_function("llvm.minnum.f32", ret_f32_take_f32_f32, None),
minimum_f64: module.add_function("llvm.minnum.f64", ret_f64_take_f64_f64, None),
maximum_f32: module.add_function("llvm.maxnum.f32", ret_f32_take_f32_f32, None),
maximum_f64: module.add_function("llvm.maxnum.f64", ret_f64_take_f64_f64, None),
ceil_f32: module.add_function("llvm.ceil.f32", ret_f32_take_f32, None),
ceil_f64: module.add_function("llvm.ceil.f64", ret_f64_take_f64, None),
floor_f32: module.add_function("llvm.floor.f32", ret_f32_take_f32, None),
floor_f64: module.add_function("llvm.floor.f64", ret_f64_take_f64, None),
trunc_f32: module.add_function("llvm.trunc.f32", ret_f32_take_f32, None),
trunc_f64: module.add_function("llvm.trunc.f64", ret_f64_take_f64, None),
nearbyint_f32: module.add_function("llvm.nearbyint.f32", ret_f32_take_f32, None),
nearbyint_f64: module.add_function("llvm.nearbyint.f64", ret_f64_take_f64, None),
fabs_f32: module.add_function("llvm.fabs.f32", ret_f32_take_f32, None),
fabs_f64: module.add_function("llvm.fabs.f64", ret_f64_take_f64, None),
copysign_f32: module.add_function("llvm.copysign.f32", ret_f32_take_f32_f32, None),
copysign_f64: module.add_function("llvm.copysign.f64", ret_f64_take_f64_f64, None),
expect_i1: module.add_function("llvm.expect.i1", ret_i1_take_i1_i1, None),
trap: module.add_function("llvm.trap", void_ty.fn_type(&[], false), None),
void_ty,
i1_ty,
i8_ty,
i16_ty,
i32_ty,
i64_ty,
f32_ty,
f64_ty,
i8_ptr_ty,
i16_ptr_ty,
i32_ptr_ty,
i64_ptr_ty,
f32_ptr_ty,
f64_ptr_ty,
anyfunc_ty,
i1_zero,
i32_zero,
i64_zero,
f32_zero,
f64_zero,
trap_unreachable: i32_zero.as_basic_value_enum(),
trap_call_indirect_sig: i32_ty.const_int(1, false).as_basic_value_enum(),
trap_call_indirect_oob: i32_ty.const_int(3, false).as_basic_value_enum(),
trap_memory_oob: i32_ty.const_int(2, false).as_basic_value_enum(),
trap_illegal_arithmetic: i32_ty.const_int(4, false).as_basic_value_enum(),
// VM intrinsics.
memory_grow_dynamic_local: module.add_function(
"vm.memory.grow.dynamic.local",
ret_i32_take_ctx_i32_i32,
None,
),
memory_grow_static_local: module.add_function(
"vm.memory.grow.static.local",
ret_i32_take_ctx_i32_i32,
None,
),
memory_grow_shared_local: module.add_function(
"vm.memory.grow.shared.local",
ret_i32_take_ctx_i32_i32,
None,
),
memory_grow_dynamic_import: module.add_function(
"vm.memory.grow.dynamic.import",
ret_i32_take_ctx_i32_i32,
None,
),
memory_grow_static_import: module.add_function(
"vm.memory.grow.static.import",
ret_i32_take_ctx_i32_i32,
None,
),
memory_grow_shared_import: module.add_function(
"vm.memory.grow.shared.import",
ret_i32_take_ctx_i32_i32,
None,
),
memory_size_dynamic_local: module.add_function(
"vm.memory.size.dynamic.local",
ret_i32_take_ctx_i32,
None,
),
memory_size_static_local: module.add_function(
"vm.memory.size.static.local",
ret_i32_take_ctx_i32,
None,
),
memory_size_shared_local: module.add_function(
"vm.memory.size.shared.local",
ret_i32_take_ctx_i32,
None,
),
memory_size_dynamic_import: module.add_function(
"vm.memory.size.dynamic.import",
ret_i32_take_ctx_i32,
None,
),
memory_size_static_import: module.add_function(
"vm.memory.size.static.import",
ret_i32_take_ctx_i32,
None,
),
memory_size_shared_import: module.add_function(
"vm.memory.size.shared.import",
ret_i32_take_ctx_i32,
None,
),
throw_trap: module.add_function(
"vm.exception.trap",
void_ty.fn_type(&[i32_ty_basic], false),
None,
),
ctx_ty,
ctx_ptr_ty,
}
}
pub fn ctx<'a>(
&'a self,
info: &'a ModuleInfo,
builder: &'a Builder,
func_value: &'a FunctionValue,
cache_builder: Builder,
) -> CtxType<'a> {
CtxType {
ctx_ty: self.ctx_ty,
ctx_ptr_ty: self.ctx_ptr_ty,
ctx_ptr_value: func_value.get_nth_param(0).unwrap().into_pointer_value(),
builder,
intrinsics: self,
info,
cache_builder,
cached_memories: HashMap::new(),
cached_tables: HashMap::new(),
cached_sigindices: HashMap::new(),
cached_globals: HashMap::new(),
cached_imported_functions: HashMap::new(),
_phantom: PhantomData,
}
}
}
#[derive(Clone, Copy)]
pub enum MemoryCache {
/// The memory moves around.
Dynamic {
ptr_to_base_ptr: PointerValue,
ptr_to_bounds: PointerValue,
},
/// The memory is always in the same place.
Static {
base_ptr: PointerValue,
bounds: IntValue,
},
}
struct TableCache {
ptr_to_base_ptr: PointerValue,
ptr_to_bounds: PointerValue,
}
#[derive(Clone, Copy)]
pub enum GlobalCache {
Mut { ptr_to_value: PointerValue },
Const { value: BasicValueEnum },
}
struct ImportedFuncCache {
func_ptr: PointerValue,
ctx_ptr: PointerValue,
}
pub struct CtxType<'a> {
ctx_ty: StructType,
ctx_ptr_ty: PointerType,
ctx_ptr_value: PointerValue,
builder: &'a Builder,
intrinsics: &'a Intrinsics,
info: &'a ModuleInfo,
cache_builder: Builder,
cached_memories: HashMap<MemoryIndex, MemoryCache>,
cached_tables: HashMap<TableIndex, TableCache>,
cached_sigindices: HashMap<SigIndex, IntValue>,
cached_globals: HashMap<GlobalIndex, GlobalCache>,
cached_imported_functions: HashMap<ImportedFuncIndex, ImportedFuncCache>,
_phantom: PhantomData<&'a FunctionValue>,
}
impl<'a> CtxType<'a> {
pub fn basic(&self) -> BasicValueEnum {
self.ctx_ptr_value.as_basic_value_enum()
}
pub fn memory(&mut self, index: MemoryIndex) -> MemoryCache {
let (cached_memories, builder, info, ctx_ptr_value, intrinsics, cache_builder) = (
&mut self.cached_memories,
self.builder,
self.info,
self.ctx_ptr_value,
self.intrinsics,
&self.cache_builder,
);
*cached_memories.entry(index).or_insert_with(|| {
let (memory_array_ptr_ptr, index, memory_type) = match index.local_or_import(info) {
LocalOrImport::Local(local_mem_index) => (
unsafe {
cache_builder.build_struct_gep(ctx_ptr_value, 0, "memory_array_ptr_ptr")
},
local_mem_index.index() as u64,
info.memories[local_mem_index].memory_type(),
),
LocalOrImport::Import(import_mem_index) => (
unsafe {
cache_builder.build_struct_gep(ctx_ptr_value, 3, "memory_array_ptr_ptr")
},
import_mem_index.index() as u64,
info.imported_memories[import_mem_index].1.memory_type(),
),
};
let memory_array_ptr = cache_builder
.build_load(memory_array_ptr_ptr, "memory_array_ptr")
.into_pointer_value();
let const_index = intrinsics.i32_ty.const_int(index, false);
let memory_ptr_ptr = unsafe {
cache_builder.build_in_bounds_gep(
memory_array_ptr,
&[const_index],
"memory_ptr_ptr",
)
};
let memory_ptr = cache_builder
.build_load(memory_ptr_ptr, "memory_ptr")
.into_pointer_value();
let (ptr_to_base_ptr, ptr_to_bounds) = unsafe {
(
cache_builder.build_struct_gep(memory_ptr, 0, "base_ptr"),
cache_builder.build_struct_gep(memory_ptr, 1, "bounds_ptr"),
)
};
match memory_type {
MemoryType::Dynamic => MemoryCache::Dynamic {
ptr_to_base_ptr,
ptr_to_bounds,
},
MemoryType::Static | MemoryType::SharedStatic => MemoryCache::Static {
base_ptr: cache_builder
.build_load(ptr_to_base_ptr, "base")
.into_pointer_value(),
bounds: cache_builder
.build_load(ptr_to_bounds, "bounds")
.into_int_value(),
},
}
})
}
pub fn table(&mut self, index: TableIndex) -> (PointerValue, IntValue) {
let (cached_tables, builder, info, ctx_ptr_value, intrinsics, cache_builder) = (
&mut self.cached_tables,
self.builder,
self.info,
self.ctx_ptr_value,
self.intrinsics,
&self.cache_builder,
);
let TableCache {
ptr_to_base_ptr,
ptr_to_bounds,
} = *cached_tables.entry(index).or_insert_with(|| {
let (table_array_ptr_ptr, index) = match index.local_or_import(info) {
LocalOrImport::Local(local_table_index) => (
unsafe {
cache_builder.build_struct_gep(ctx_ptr_value, 1, "table_array_ptr_ptr")
},
local_table_index.index() as u64,
),
LocalOrImport::Import(import_table_index) => (
unsafe {
cache_builder.build_struct_gep(ctx_ptr_value, 4, "table_array_ptr_ptr")
},
import_table_index.index() as u64,
),
};
let table_array_ptr = cache_builder
.build_load(table_array_ptr_ptr, "table_array_ptr")
.into_pointer_value();
let const_index = intrinsics.i32_ty.const_int(index, false);
let table_ptr_ptr = unsafe {
cache_builder.build_in_bounds_gep(table_array_ptr, &[const_index], "table_ptr_ptr")
};
let table_ptr = cache_builder
.build_load(table_ptr_ptr, "table_ptr")
.into_pointer_value();
let (ptr_to_base_ptr, ptr_to_bounds) = unsafe {
(
cache_builder.build_struct_gep(table_ptr, 0, "base_ptr"),
cache_builder.build_struct_gep(table_ptr, 1, "bounds_ptr"),
)
};
TableCache {
ptr_to_base_ptr,
ptr_to_bounds,
}
});
(
builder
.build_load(ptr_to_base_ptr, "base_ptr")
.into_pointer_value(),
builder.build_load(ptr_to_bounds, "bounds").into_int_value(),
)
}
pub fn dynamic_sigindex(&mut self, index: SigIndex) -> IntValue {
let (cached_sigindices, builder, info, ctx_ptr_value, intrinsics, cache_builder) = (
&mut self.cached_sigindices,
self.builder,
self.info,
self.ctx_ptr_value,
self.intrinsics,
&self.cache_builder,
);
*cached_sigindices.entry(index).or_insert_with(|| {
let sigindex_array_ptr_ptr = unsafe {
cache_builder.build_struct_gep(ctx_ptr_value, 7, "sigindex_array_ptr_ptr")
};
let sigindex_array_ptr = cache_builder
.build_load(sigindex_array_ptr_ptr, "sigindex_array_ptr")
.into_pointer_value();
let const_index = intrinsics.i32_ty.const_int(index.index() as u64, false);
let sigindex_ptr = unsafe {
cache_builder.build_in_bounds_gep(
sigindex_array_ptr,
&[const_index],
"sigindex_ptr",
)
};
cache_builder
.build_load(sigindex_ptr, "sigindex")
.into_int_value()
})
}
pub fn global_cache(&mut self, index: GlobalIndex) -> GlobalCache {
let (cached_globals, builder, ctx_ptr_value, info, intrinsics, cache_builder) = (
&mut self.cached_globals,
self.builder,
self.ctx_ptr_value,
self.info,
self.intrinsics,
&self.cache_builder,
);
*cached_globals.entry(index).or_insert_with(|| {
let (globals_array_ptr_ptr, index, mutable, wasmer_ty) =
match index.local_or_import(info) {
LocalOrImport::Local(local_global_index) => {
let desc = info.globals[local_global_index].desc;
(
unsafe {
cache_builder.build_struct_gep(
ctx_ptr_value,
2,
"globals_array_ptr_ptr",
)
},
local_global_index.index() as u64,
desc.mutable,
desc.ty,
)
}
LocalOrImport::Import(import_global_index) => {
let desc = info.imported_globals[import_global_index].1;
(
unsafe {
cache_builder.build_struct_gep(
ctx_ptr_value,
5,
"globals_array_ptr_ptr",
)
},
import_global_index.index() as u64,
desc.mutable,
desc.ty,
)
}
};
let llvm_ptr_ty = type_to_llvm_ptr(intrinsics, wasmer_ty);
let global_array_ptr = cache_builder
.build_load(globals_array_ptr_ptr, "global_array_ptr")
.into_pointer_value();
let const_index = intrinsics.i32_ty.const_int(index, false);
let global_ptr_ptr = unsafe {
cache_builder.build_in_bounds_gep(
global_array_ptr,
&[const_index],
"global_ptr_ptr",
)
};
let global_ptr = cache_builder
.build_load(global_ptr_ptr, "global_ptr")
.into_pointer_value();
let global_ptr_typed =
cache_builder.build_pointer_cast(global_ptr, llvm_ptr_ty, "global_ptr_typed");
if mutable {
GlobalCache::Mut {
ptr_to_value: global_ptr_typed,
}
} else {
GlobalCache::Const {
value: cache_builder.build_load(global_ptr_typed, "global_value"),
}
}
})
}
pub fn imported_func(&mut self, index: ImportedFuncIndex) -> (PointerValue, PointerValue) {
let (cached_imported_functions, builder, ctx_ptr_value, intrinsics, cache_builder) = (
&mut self.cached_imported_functions,
self.builder,
self.ctx_ptr_value,
self.intrinsics,
&self.cache_builder,
);
let imported_func_cache = cached_imported_functions.entry(index).or_insert_with(|| {
let func_array_ptr_ptr = unsafe {
cache_builder.build_struct_gep(ctx_ptr_value, 6, "imported_func_array_ptr_ptr")
};
let func_array_ptr = cache_builder
.build_load(func_array_ptr_ptr, "func_array_ptr")
.into_pointer_value();
let const_index = intrinsics.i32_ty.const_int(index.index() as u64, false);
let imported_func_ptr = unsafe {
cache_builder.build_in_bounds_gep(
func_array_ptr,
&[const_index],
"imported_func_ptr",
)
};
let (func_ptr_ptr, ctx_ptr_ptr) = unsafe {
(
cache_builder.build_struct_gep(imported_func_ptr, 0, "func_ptr_ptr"),
cache_builder.build_struct_gep(imported_func_ptr, 1, "ctx_ptr_ptr"),
)
};
let func_ptr = cache_builder
.build_load(func_ptr_ptr, "func_ptr")
.into_pointer_value();
let ctx_ptr = cache_builder
.build_load(ctx_ptr_ptr, "ctx_ptr")
.into_pointer_value();
ImportedFuncCache { func_ptr, ctx_ptr }
});
(imported_func_cache.func_ptr, imported_func_cache.ctx_ptr)
}
pub fn build_trap(&self) {
self.builder.build_call(self.intrinsics.trap, &[], "trap");
}
}
// pub struct Ctx {
// /// A pointer to an array of locally-defined memories, indexed by `MemoryIndex`.
// pub(crate) memories: *mut *mut LocalMemory,
// /// A pointer to an array of locally-defined tables, indexed by `TableIndex`.
// pub(crate) tables: *mut *mut LocalTable,
// /// A pointer to an array of locally-defined globals, indexed by `GlobalIndex`.
// pub(crate) globals: *mut *mut LocalGlobal,
// /// A pointer to an array of imported memories, indexed by `MemoryIndex,
// pub(crate) imported_memories: *mut *mut LocalMemory,
// /// A pointer to an array of imported tables, indexed by `TableIndex`.
// pub(crate) imported_tables: *mut *mut LocalTable,
// /// A pointer to an array of imported globals, indexed by `GlobalIndex`.
// pub(crate) imported_globals: *mut *mut LocalGlobal,
// /// A pointer to an array of imported functions, indexed by `FuncIndex`.
// pub(crate) imported_funcs: *mut ImportedFunc,
// local_backing: *mut LocalBacking,
// import_backing: *mut ImportBacking,
// module: *const ModuleInner,
// pub data: *mut c_void,
// pub data_finalizer: Option<extern "C" fn(data: *mut c_void)>,
// }

137
lib/llvm-backend/src/lib.rs Normal file
View File

@ -0,0 +1,137 @@
use inkwell::{
execution_engine::JitFunction,
targets::{CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetMachine},
OptimizationLevel,
};
use wasmer_runtime_core::{
backend::{Compiler, Token},
cache::{Artifact, Error as CacheError},
error::CompileError,
module::ModuleInner,
};
use wasmparser::{self, WasmDecoder};
mod backend;
mod code;
mod intrinsics;
mod platform;
mod read_info;
mod state;
mod trampolines;
pub struct LLVMCompiler {
_private: (),
}
impl LLVMCompiler {
pub fn new() -> Self {
Self { _private: () }
}
}
impl Compiler for LLVMCompiler {
fn compile(&self, wasm: &[u8], _: Token) -> Result<ModuleInner, CompileError> {
validate(wasm)?;
let (info, code_reader) = read_info::read_module(wasm).unwrap();
let (module, intrinsics) = code::parse_function_bodies(&info, code_reader).unwrap();
let (backend, protected_caller) = backend::LLVMBackend::new(module, intrinsics);
// Create placeholder values here.
let cache_gen = {
use wasmer_runtime_core::backend::{
sys::Memory, CacheGen, ProtectedCaller, UserTrapper,
};
use wasmer_runtime_core::cache::Error as CacheError;
use wasmer_runtime_core::error::RuntimeResult;
use wasmer_runtime_core::module::ModuleInfo;
use wasmer_runtime_core::types::{FuncIndex, Value};
use wasmer_runtime_core::vm;
struct Placeholder;
impl CacheGen for Placeholder {
fn generate_cache(
&self,
module: &ModuleInner,
) -> Result<(Box<ModuleInfo>, Box<[u8]>, Memory), CacheError> {
unimplemented!()
}
}
Box::new(Placeholder)
};
Ok(ModuleInner {
func_resolver: Box::new(backend),
protected_caller: Box::new(protected_caller),
cache_gen,
info,
})
}
unsafe fn from_cache(&self, _artifact: Artifact, _: Token) -> Result<ModuleInner, CacheError> {
unimplemented!("the llvm backend doesn't support caching yet")
}
}
fn validate(bytes: &[u8]) -> Result<(), CompileError> {
let mut parser = wasmparser::ValidatingParser::new(
bytes,
Some(wasmparser::ValidatingParserConfig {
operator_config: wasmparser::OperatorValidatorConfig {
enable_threads: false,
enable_reference_types: false,
enable_simd: false,
enable_bulk_memory: false,
},
mutable_global_imports: false,
}),
);
loop {
let state = parser.read();
match *state {
wasmparser::ParserState::EndWasm => break Ok(()),
wasmparser::ParserState::Error(err) => Err(CompileError::ValidationError {
msg: err.message.to_string(),
})?,
_ => {}
}
}
}
#[test]
fn test_read_module() {
use std::mem::transmute;
use wabt::wat2wasm;
use wasmer_runtime_core::{structures::TypedIndex, types::LocalFuncIndex, vm, vmcalls};
// let wasm = include_bytes!("../../spectests/examples/simple/simple.wasm") as &[u8];
let wat = r#"
(module
(type $t0 (func (param i32) (result i32)))
(type $t1 (func (result i32)))
(memory 1)
(global $g0 (mut i32) (i32.const 0))
(func $foo (type $t0) (param i32) (result i32)
get_local 0
))
"#;
let wasm = wat2wasm(wat).unwrap();
let (info, code_reader) = read_info::read_module(&wasm).unwrap();
let (module, intrinsics) = code::parse_function_bodies(&info, code_reader).unwrap();
let (backend, _caller) = backend::LLVMBackend::new(module, intrinsics);
let func_ptr = backend.get_func(&info, LocalFuncIndex::new(0)).unwrap();
println!("func_ptr: {:p}", func_ptr.as_ptr());
unsafe {
let func: unsafe extern "C" fn(*mut vm::Ctx, i32) -> i32 = transmute(func_ptr);
let result = func(0 as _, 42);
println!("result: {}", result);
}
}

View File

@ -0,0 +1,7 @@
#[cfg(unix)]
mod unix;
#[cfg(unix)]
pub use self::unix::*;
#[cfg(target_family = "windows")]
compile_error!("windows not yet supported for the llvm-based compiler backend");

View File

@ -0,0 +1,72 @@
use libc::{c_void, siginfo_t};
use nix::sys::signal::{
sigaction, SaFlags, SigAction, SigHandler, SigSet, Signal, SIGBUS, SIGFPE, SIGILL, SIGSEGV,
};
/// `__register_frame` and `__deregister_frame` on macos take a single fde as an
/// argument, so we need to parse the fde table here.
///
/// This is a pretty direct port of llvm's fde handling code:
/// https://llvm.org/doxygen/RTDyldMemoryManager_8cpp_source.html.
#[allow(clippy::cast_ptr_alignment)]
#[cfg(target_os = "macos")]
pub unsafe fn visit_fde(addr: *mut u8, size: usize, visitor: extern "C" fn(*mut u8)) {
unsafe fn process_fde(entry: *mut u8, visitor: extern "C" fn(*mut u8)) -> *mut u8 {
let mut p = entry;
let length = (p as *const u32).read_unaligned();
p = p.add(4);
let offset = (p as *const u32).read_unaligned();
if offset != 0 {
visitor(entry);
}
p.add(length as usize)
}
let mut p = addr;
let end = p.add(size);
loop {
if p >= end {
break;
}
p = process_fde(p, visitor);
}
}
#[cfg(not(target_os = "macos"))]
pub unsafe fn visit_fde(addr: *mut u8, size: usize, visitor: extern "C" fn(*mut u8)) {
visitor(addr);
}
extern "C" {
fn throw_trap(ty: i32) -> !;
}
pub unsafe fn install_signal_handler() {
let sa = SigAction::new(
SigHandler::SigAction(signal_trap_handler),
SaFlags::SA_ONSTACK | SaFlags::SA_SIGINFO,
SigSet::empty(),
);
sigaction(SIGFPE, &sa).unwrap();
sigaction(SIGILL, &sa).unwrap();
sigaction(SIGSEGV, &sa).unwrap();
sigaction(SIGBUS, &sa).unwrap();
}
extern "C" fn signal_trap_handler(
signum: ::nix::libc::c_int,
siginfo: *mut siginfo_t,
ucontext: *mut c_void,
) {
unsafe {
/// Apparently, we can unwind from arbitary instructions, as long
/// as we don't need to catch the exception inside the function that
/// was interrupted.
///
/// This works on macos, not sure about linux.
throw_trap(2);
}
}

View File

@ -0,0 +1,341 @@
use wasmer_runtime_core::{
backend::Backend,
module::{
DataInitializer, ExportIndex, ImportName, ModuleInfo, StringTable, StringTableBuilder,
TableInitializer,
},
structures::{Map, TypedIndex},
types::{
ElementType, FuncIndex, FuncSig, GlobalDescriptor, GlobalIndex, GlobalInit,
ImportedGlobalIndex, Initializer, MemoryDescriptor, MemoryIndex, SigIndex, TableDescriptor,
TableIndex, Type, Value,
},
units::Pages,
};
use wasmparser::{
BinaryReaderError, CodeSectionReader, Data, DataKind, Element, ElementKind, Export,
ExternalKind, FuncType, Import, ImportSectionEntryType, InitExpr, ModuleReader, Operator,
SectionCode, Type as WpType,
};
pub fn read_module(wasm: &[u8]) -> Result<(ModuleInfo, CodeSectionReader), BinaryReaderError> {
let mut info = ModuleInfo {
memories: Map::new(),
globals: Map::new(),
tables: Map::new(),
imported_functions: Map::new(),
imported_memories: Map::new(),
imported_tables: Map::new(),
imported_globals: Map::new(),
exports: Default::default(),
data_initializers: Vec::new(),
elem_initializers: Vec::new(),
start_func: None,
func_assoc: Map::new(),
signatures: Map::new(),
backend: Backend::LLVM,
namespace_table: StringTable::new(),
name_table: StringTable::new(),
};
let mut reader = ModuleReader::new(wasm)?;
let mut code_reader = None;
loop {
if reader.eof() {
return Ok((info, code_reader.unwrap()));
}
let section = reader.read()?;
match section.code {
SectionCode::Type => {
let type_reader = section.get_type_section_reader()?;
for ty in type_reader {
let ty = ty?;
info.signatures.push(func_type_to_func_sig(ty)?);
}
}
SectionCode::Import => {
let import_reader = section.get_import_section_reader()?;
let mut namespace_builder = StringTableBuilder::new();
let mut name_builder = StringTableBuilder::new();
for import in import_reader {
let Import { module, field, ty } = import?;
let namespace_index = namespace_builder.register(module);
let name_index = name_builder.register(field);
let import_name = ImportName {
namespace_index,
name_index,
};
match ty {
ImportSectionEntryType::Function(sigindex) => {
let sigindex = SigIndex::new(sigindex as usize);
info.imported_functions.push(import_name);
info.func_assoc.push(sigindex);
}
ImportSectionEntryType::Table(table_ty) => {
assert_eq!(table_ty.element_type, WpType::AnyFunc);
let table_desc = TableDescriptor {
element: ElementType::Anyfunc,
minimum: table_ty.limits.initial,
maximum: table_ty.limits.maximum,
};
info.imported_tables.push((import_name, table_desc));
}
ImportSectionEntryType::Memory(memory_ty) => {
let mem_desc = MemoryDescriptor {
minimum: Pages(memory_ty.limits.initial),
maximum: memory_ty.limits.maximum.map(|max| Pages(max)),
shared: memory_ty.shared,
};
info.imported_memories.push((import_name, mem_desc));
}
ImportSectionEntryType::Global(global_ty) => {
let global_desc = GlobalDescriptor {
mutable: global_ty.mutable,
ty: type_to_type(global_ty.content_type)?,
};
info.imported_globals.push((import_name, global_desc));
}
}
}
info.namespace_table = namespace_builder.finish();
info.name_table = name_builder.finish();
}
SectionCode::Function => {
let func_decl_reader = section.get_function_section_reader()?;
for sigindex in func_decl_reader {
let sigindex = sigindex?;
let sigindex = SigIndex::new(sigindex as usize);
info.func_assoc.push(sigindex);
}
}
SectionCode::Table => {
let table_decl_reader = section.get_table_section_reader()?;
for table_ty in table_decl_reader {
let table_ty = table_ty?;
let table_desc = TableDescriptor {
element: ElementType::Anyfunc,
minimum: table_ty.limits.initial,
maximum: table_ty.limits.maximum,
};
info.tables.push(table_desc);
}
}
SectionCode::Memory => {
let mem_decl_reader = section.get_memory_section_reader()?;
for memory_ty in mem_decl_reader {
let memory_ty = memory_ty?;
let mem_desc = MemoryDescriptor {
minimum: Pages(memory_ty.limits.initial),
maximum: memory_ty.limits.maximum.map(|max| Pages(max)),
shared: memory_ty.shared,
};
info.memories.push(mem_desc);
}
}
SectionCode::Global => {
let global_decl_reader = section.get_global_section_reader()?;
for global in global_decl_reader {
let global = global?;
let desc = GlobalDescriptor {
mutable: global.ty.mutable,
ty: type_to_type(global.ty.content_type)?,
};
let global_init = GlobalInit {
desc,
init: eval_init_expr(&global.init_expr)?,
};
info.globals.push(global_init);
}
}
SectionCode::Export => {
let export_reader = section.get_export_section_reader()?;
for export in export_reader {
let Export { field, kind, index } = export?;
let export_index = match kind {
ExternalKind::Function => ExportIndex::Func(FuncIndex::new(index as usize)),
ExternalKind::Table => ExportIndex::Table(TableIndex::new(index as usize)),
ExternalKind::Memory => {
ExportIndex::Memory(MemoryIndex::new(index as usize))
}
ExternalKind::Global => {
ExportIndex::Global(GlobalIndex::new(index as usize))
}
};
info.exports.insert(field.to_string(), export_index);
}
}
SectionCode::Start => {
let start_index = section.get_start_section_content()?;
info.start_func = Some(FuncIndex::new(start_index as usize));
}
SectionCode::Element => {
let element_reader = section.get_element_section_reader()?;
for element in element_reader {
let Element { kind, items } = element?;
match kind {
ElementKind::Active {
table_index,
init_expr,
} => {
let table_index = TableIndex::new(table_index as usize);
let base = eval_init_expr(&init_expr)?;
let items_reader = items.get_items_reader()?;
let elements: Vec<_> = items_reader
.into_iter()
.map(|res| res.map(|index| FuncIndex::new(index as usize)))
.collect::<Result<_, _>>()?;
let table_init = TableInitializer {
table_index,
base,
elements,
};
info.elem_initializers.push(table_init);
}
ElementKind::Passive(_ty) => {
return Err(BinaryReaderError {
message: "passive tables are not yet supported",
offset: -1isize as usize,
});
}
}
}
}
SectionCode::Code => {
code_reader = Some(section.get_code_section_reader()?);
}
SectionCode::Data => {
let data_reader = section.get_data_section_reader()?;
for data in data_reader {
let Data { kind, data } = data?;
match kind {
DataKind::Active {
memory_index,
init_expr,
} => {
let memory_index = MemoryIndex::new(memory_index as usize);
let base = eval_init_expr(&init_expr)?;
let data_init = DataInitializer {
memory_index,
base,
data: data.to_vec(),
};
info.data_initializers.push(data_init);
}
DataKind::Passive => {
return Err(BinaryReaderError {
message: "passive memories are not yet supported",
offset: -1isize as usize,
});
}
}
}
}
SectionCode::DataCount => {}
SectionCode::Custom { .. } => {}
}
}
}
pub fn type_to_type(ty: WpType) -> Result<Type, BinaryReaderError> {
Ok(match ty {
WpType::I32 => Type::I32,
WpType::I64 => Type::I64,
WpType::F32 => Type::F32,
WpType::F64 => Type::F64,
WpType::V128 => {
return Err(BinaryReaderError {
message: "the wasmer llvm backend does not yet support the simd extension",
offset: -1isize as usize,
});
}
_ => {
return Err(BinaryReaderError {
message: "that type is not supported as a wasmer type",
offset: -1isize as usize,
});
}
})
}
fn func_type_to_func_sig(func_ty: FuncType) -> Result<FuncSig, BinaryReaderError> {
assert_eq!(func_ty.form, WpType::Func);
Ok(FuncSig::new(
func_ty
.params
.iter()
.cloned()
.map(type_to_type)
.collect::<Result<Vec<_>, _>>()?,
func_ty
.returns
.iter()
.cloned()
.map(type_to_type)
.collect::<Result<Vec<_>, _>>()?,
))
}
fn eval_init_expr(expr: &InitExpr) -> Result<Initializer, BinaryReaderError> {
let mut reader = expr.get_operators_reader();
let (op, offset) = reader.read_with_offset()?;
Ok(match op {
Operator::GetGlobal { global_index } => {
Initializer::GetGlobal(ImportedGlobalIndex::new(global_index as usize))
}
Operator::I32Const { value } => Initializer::Const(Value::I32(value)),
Operator::I64Const { value } => Initializer::Const(Value::I64(value)),
Operator::F32Const { value } => {
Initializer::Const(Value::F32(f32::from_bits(value.bits())))
}
Operator::F64Const { value } => {
Initializer::Const(Value::F64(f64::from_bits(value.bits())))
}
_ => {
return Err(BinaryReaderError {
message: "init expr evaluation failed: unsupported opcode",
offset,
});
}
})
}

View File

@ -0,0 +1,244 @@
use inkwell::{
basic_block::BasicBlock,
values::{BasicValue, BasicValueEnum, PhiValue},
};
use smallvec::SmallVec;
use std::cell::Cell;
use wasmparser::BinaryReaderError;
#[derive(Debug)]
pub enum ControlFrame {
Block {
next: BasicBlock,
phis: SmallVec<[PhiValue; 1]>,
stack_size_snapshot: usize,
},
Loop {
body: BasicBlock,
next: BasicBlock,
phis: SmallVec<[PhiValue; 1]>,
stack_size_snapshot: usize,
},
IfElse {
if_then: BasicBlock,
if_else: BasicBlock,
next: BasicBlock,
phis: SmallVec<[PhiValue; 1]>,
stack_size_snapshot: usize,
if_else_state: IfElseState,
},
}
#[derive(Debug)]
pub enum IfElseState {
If,
Else,
}
impl ControlFrame {
pub fn code_after(&self) -> &BasicBlock {
match self {
ControlFrame::Block { ref next, .. }
| ControlFrame::Loop { ref next, .. }
| ControlFrame::IfElse { ref next, .. } => next,
}
}
pub fn br_dest(&self) -> &BasicBlock {
match self {
ControlFrame::Block { ref next, .. } | ControlFrame::IfElse { ref next, .. } => next,
ControlFrame::Loop { ref body, .. } => body,
}
}
pub fn phis(&self) -> &[PhiValue] {
match self {
ControlFrame::Block { ref phis, .. }
| ControlFrame::Loop { ref phis, .. }
| ControlFrame::IfElse { ref phis, .. } => phis.as_slice(),
}
}
pub fn is_loop(&self) -> bool {
match self {
ControlFrame::Loop { .. } => true,
_ => false,
}
}
}
#[derive(Debug)]
pub struct State {
stack: Vec<BasicValueEnum>,
control_stack: Vec<ControlFrame>,
value_counter: Cell<usize>,
pub reachable: bool,
}
impl State {
pub fn new() -> Self {
Self {
stack: vec![],
control_stack: vec![],
value_counter: Cell::new(0),
reachable: true,
}
}
pub fn reset_stack(&mut self, frame: &ControlFrame) {
let stack_size_snapshot = match frame {
ControlFrame::Block {
stack_size_snapshot,
..
}
| ControlFrame::Loop {
stack_size_snapshot,
..
}
| ControlFrame::IfElse {
stack_size_snapshot,
..
} => *stack_size_snapshot,
};
self.stack.truncate(stack_size_snapshot);
}
pub fn outermost_frame(&self) -> Result<&ControlFrame, BinaryReaderError> {
self.control_stack.get(0).ok_or(BinaryReaderError {
message: "invalid control stack depth",
offset: -1isize as usize,
})
}
pub fn frame_at_depth(&self, depth: u32) -> Result<&ControlFrame, BinaryReaderError> {
let index = self.control_stack.len() - 1 - (depth as usize);
self.control_stack.get(index).ok_or(BinaryReaderError {
message: "invalid control stack depth",
offset: -1isize as usize,
})
}
pub fn frame_at_depth_mut(
&mut self,
depth: u32,
) -> Result<&mut ControlFrame, BinaryReaderError> {
let index = self.control_stack.len() - 1 - (depth as usize);
self.control_stack.get_mut(index).ok_or(BinaryReaderError {
message: "invalid control stack depth",
offset: -1isize as usize,
})
}
pub fn pop_frame(&mut self) -> Result<ControlFrame, BinaryReaderError> {
self.control_stack.pop().ok_or(BinaryReaderError {
message: "cannot pop from control stack",
offset: -1isize as usize,
})
}
pub fn var_name(&self) -> String {
let counter = self.value_counter.get();
let s = format!("s{}", counter);
self.value_counter.set(counter + 1);
s
}
pub fn push1<T: BasicValue>(&mut self, value: T) {
self.stack.push(value.as_basic_value_enum())
}
pub fn pop1(&mut self) -> Result<BasicValueEnum, BinaryReaderError> {
self.stack.pop().ok_or(BinaryReaderError {
message: "invalid value stack",
offset: -1isize as usize,
})
}
pub fn pop2(&mut self) -> Result<(BasicValueEnum, BasicValueEnum), BinaryReaderError> {
let v2 = self.pop1()?;
let v1 = self.pop1()?;
Ok((v1, v2))
}
pub fn pop3(
&mut self,
) -> Result<(BasicValueEnum, BasicValueEnum, BasicValueEnum), BinaryReaderError> {
let v3 = self.pop1()?;
let v2 = self.pop1()?;
let v1 = self.pop1()?;
Ok((v1, v2, v3))
}
pub fn peek1(&self) -> Result<BasicValueEnum, BinaryReaderError> {
self.stack
.get(self.stack.len() - 1)
.ok_or(BinaryReaderError {
message: "invalid value stack",
offset: -1isize as usize,
})
.map(|v| *v)
}
pub fn peekn(&self, n: usize) -> Result<&[BasicValueEnum], BinaryReaderError> {
self.stack
.get(self.stack.len() - n..)
.ok_or(BinaryReaderError {
message: "invalid value stack",
offset: -1isize as usize,
})
}
pub fn popn_save(&mut self, n: usize) -> Result<Vec<BasicValueEnum>, BinaryReaderError> {
let v = self.peekn(n)?.to_vec();
self.popn(n)?;
Ok(v)
}
pub fn popn(&mut self, n: usize) -> Result<(), BinaryReaderError> {
if self.stack.len() < n {
return Err(BinaryReaderError {
message: "invalid value stack",
offset: -1isize as usize,
});
}
let new_len = self.stack.len() - n;
self.stack.truncate(new_len);
Ok(())
}
pub fn push_block(&mut self, next: BasicBlock, phis: SmallVec<[PhiValue; 1]>) {
self.control_stack.push(ControlFrame::Block {
next,
phis,
stack_size_snapshot: self.stack.len(),
});
}
pub fn push_loop(&mut self, body: BasicBlock, next: BasicBlock, phis: SmallVec<[PhiValue; 1]>) {
self.control_stack.push(ControlFrame::Loop {
body,
next,
phis,
stack_size_snapshot: self.stack.len(),
});
}
pub fn push_if(
&mut self,
if_then: BasicBlock,
if_else: BasicBlock,
next: BasicBlock,
phis: SmallVec<[PhiValue; 1]>,
) {
self.control_stack.push(ControlFrame::IfElse {
if_then,
if_else,
next,
phis,
stack_size_snapshot: self.stack.len(),
if_else_state: IfElseState::If,
});
}
}

View File

@ -0,0 +1,120 @@
use crate::intrinsics::Intrinsics;
use inkwell::{
builder::Builder,
context::Context,
module::{Linkage, Module},
passes::PassManager,
types::{BasicType, BasicTypeEnum, FunctionType, PointerType},
values::{BasicValue, FunctionValue, PhiValue, PointerValue},
AddressSpace, FloatPredicate, IntPredicate,
};
use wasmer_runtime_core::{
module::ModuleInfo,
structures::{SliceMap, TypedIndex},
types::{FuncSig, SigIndex, Type},
};
pub fn generate_trampolines(
info: &ModuleInfo,
signatures: &SliceMap<SigIndex, FunctionType>,
module: &Module,
context: &Context,
builder: &Builder,
intrinsics: &Intrinsics,
) {
for (sig_index, sig) in info.signatures.iter() {
let func_type = signatures[sig_index];
let trampoline_sig = intrinsics.void_ty.fn_type(
&[
intrinsics.ctx_ptr_ty.as_basic_type_enum(), // vmctx ptr
func_type
.ptr_type(AddressSpace::Generic)
.as_basic_type_enum(), // func ptr
intrinsics.i64_ptr_ty.as_basic_type_enum(), // args ptr
intrinsics.i64_ptr_ty.as_basic_type_enum(), // returns ptr
],
false,
);
let trampoline_func = module.add_function(
&format!("trmp{}", sig_index.index()),
trampoline_sig,
Some(Linkage::External),
);
generate_trampoline(
trampoline_func,
func_type,
sig,
context,
builder,
intrinsics,
);
}
}
fn generate_trampoline(
trampoline_func: FunctionValue,
sig_type: FunctionType,
func_sig: &FuncSig,
context: &Context,
builder: &Builder,
intrinsics: &Intrinsics,
) {
let entry_block = context.append_basic_block(&trampoline_func, "entry");
builder.position_at_end(&entry_block);
let (vmctx_ptr, func_ptr, args_ptr, returns_ptr) = match trampoline_func.get_params().as_slice()
{
&[vmctx_ptr, func_ptr, args_ptr, returns_ptr] => (
vmctx_ptr,
func_ptr.into_pointer_value(),
args_ptr.into_pointer_value(),
returns_ptr.into_pointer_value(),
),
_ => unimplemented!(),
};
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,
};
let mut args_vec = Vec::with_capacity(func_sig.params().len() + 1);
args_vec.push(vmctx_ptr);
for (i, param_ty) in func_sig.params().iter().enumerate() {
let index = intrinsics.i32_ty.const_int(i as _, false);
let item_pointer = unsafe { builder.build_in_bounds_gep(args_ptr, &[index], "arg_ptr") };
let casted_pointer_type = cast_ptr_ty(*param_ty);
let typed_item_pointer =
builder.build_pointer_cast(item_pointer, casted_pointer_type, "typed_arg_pointer");
let arg = builder.build_load(typed_item_pointer, "arg");
args_vec.push(arg);
}
let call_site = builder.build_call(func_ptr, &args_vec, "call");
match func_sig.returns() {
&[] => {}
&[one_ret] => {
let ret_ptr_type = cast_ptr_ty(one_ret);
let typed_ret_ptr =
builder.build_pointer_cast(returns_ptr, ret_ptr_type, "typed_ret_ptr");
builder.build_store(
typed_ret_ptr,
call_site.try_as_basic_value().left().unwrap(),
);
}
_ => unimplemented!("multi-value returns"),
}
builder.build_return(None);
}

View File

@ -14,7 +14,7 @@ wasmer-runtime-core = { path = "../runtime-core", version = "0.2.1" }
libc = "0.2" libc = "0.2"
[lib] [lib]
crate-type = ["cdylib", "rlib"] crate-type = ["cdylib", "rlib", "staticlib"]
[build-dependencies] [build-dependencies]
cbindgen = "0.8" cbindgen = "0.8"

View File

@ -22,6 +22,7 @@ pub use crate::sig_registry::SigRegistry;
#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq, Eq)]
pub enum Backend { pub enum Backend {
Cranelift, Cranelift,
LLVM,
} }
/// This type cannot be constructed from /// This type cannot be constructed from

View File

@ -4,17 +4,18 @@ use crate::{
global::Global, global::Global,
import::ImportObject, import::ImportObject,
memory::Memory, memory::Memory,
module::{ImportName, ModuleInner}, module::{ImportName, ModuleInfo, ModuleInner},
sig_registry::SigRegistry, sig_registry::SigRegistry,
structures::{BoxedMap, Map, SliceMap, TypedIndex}, structures::{BoxedMap, Map, SliceMap, TypedIndex},
table::Table, table::Table,
types::{ types::{
ImportedFuncIndex, ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex, ImportedFuncIndex, ImportedGlobalIndex, ImportedMemoryIndex, ImportedTableIndex,
Initializer, LocalGlobalIndex, LocalMemoryIndex, LocalOrImport, LocalTableIndex, Value, Initializer, LocalGlobalIndex, LocalMemoryIndex, LocalOrImport, LocalTableIndex, SigIndex,
Value,
}, },
vm, vm,
}; };
use std::{slice, sync::Arc}; use std::slice;
#[derive(Debug)] #[derive(Debug)]
pub struct LocalBacking { pub struct LocalBacking {
@ -25,6 +26,8 @@ pub struct LocalBacking {
pub(crate) vm_memories: BoxedMap<LocalMemoryIndex, *mut vm::LocalMemory>, pub(crate) vm_memories: BoxedMap<LocalMemoryIndex, *mut vm::LocalMemory>,
pub(crate) vm_tables: BoxedMap<LocalTableIndex, *mut vm::LocalTable>, pub(crate) vm_tables: BoxedMap<LocalTableIndex, *mut vm::LocalTable>,
pub(crate) vm_globals: BoxedMap<LocalGlobalIndex, *mut vm::LocalGlobal>, pub(crate) vm_globals: BoxedMap<LocalGlobalIndex, *mut vm::LocalGlobal>,
pub(crate) dynamic_sigindices: BoxedMap<SigIndex, vm::SigId>,
} }
// impl LocalBacking { // impl LocalBacking {
@ -47,6 +50,8 @@ impl LocalBacking {
let vm_tables = Self::finalize_tables(module, imports, &mut tables, vmctx); let vm_tables = Self::finalize_tables(module, imports, &mut tables, vmctx);
let vm_globals = Self::finalize_globals(&mut globals); let vm_globals = Self::finalize_globals(&mut globals);
let dynamic_sigindices = Self::generate_sigindices(&module.info);
Self { Self {
memories, memories,
tables, tables,
@ -55,9 +60,23 @@ impl LocalBacking {
vm_memories, vm_memories,
vm_tables, vm_tables,
vm_globals, vm_globals,
dynamic_sigindices,
} }
} }
fn generate_sigindices(info: &ModuleInfo) -> BoxedMap<SigIndex, vm::SigId> {
info.signatures
.iter()
.map(|(_, signature)| {
let signature = SigRegistry.lookup_signature_ref(signature);
let sig_index = SigRegistry.lookup_sig_index(signature);
vm::SigId(sig_index.index() as u32)
})
.collect::<Map<_, _>>()
.into_boxed_map()
}
fn generate_memories(module: &ModuleInner) -> BoxedMap<LocalMemoryIndex, Memory> { fn generate_memories(module: &ModuleInner) -> BoxedMap<LocalMemoryIndex, Memory> {
let mut memories = Map::with_capacity(module.info.memories.len()); let mut memories = Map::with_capacity(module.info.memories.len());
for (_, &desc) in &module.info.memories { for (_, &desc) in &module.info.memories {
@ -172,10 +191,11 @@ impl LocalBacking {
table.anyfunc_direct_access_mut(|elements| { table.anyfunc_direct_access_mut(|elements| {
for (i, &func_index) in init.elements.iter().enumerate() { for (i, &func_index) in init.elements.iter().enumerate() {
let sig_index = module.info.func_assoc[func_index]; let sig_index = module.info.func_assoc[func_index];
let signature = &module.info.signatures[sig_index]; // let signature = &module.info.signatures[sig_index];
let sig_id = vm::SigId( let signature = SigRegistry
SigRegistry.lookup_sig_index(Arc::clone(&signature)).index() as u32, .lookup_signature_ref(&module.info.signatures[sig_index]);
); let sig_id =
vm::SigId(SigRegistry.lookup_sig_index(signature).index() as u32);
let (func, ctx) = match func_index.local_or_import(&module.info) { let (func, ctx) = match func_index.local_or_import(&module.info) {
LocalOrImport::Local(local_func_index) => ( LocalOrImport::Local(local_func_index) => (
@ -210,10 +230,11 @@ impl LocalBacking {
table.anyfunc_direct_access_mut(|elements| { table.anyfunc_direct_access_mut(|elements| {
for (i, &func_index) in init.elements.iter().enumerate() { for (i, &func_index) in init.elements.iter().enumerate() {
let sig_index = module.info.func_assoc[func_index]; let sig_index = module.info.func_assoc[func_index];
let signature = &module.info.signatures[sig_index]; let signature = SigRegistry
let sig_id = vm::SigId( .lookup_signature_ref(&module.info.signatures[sig_index]);
SigRegistry.lookup_sig_index(Arc::clone(&signature)).index() as u32, // let signature = &module.info.signatures[sig_index];
); let sig_id =
vm::SigId(SigRegistry.lookup_sig_index(signature).index() as u32);
let (func, ctx) = match func_index.local_or_import(&module.info) { let (func, ctx) = match func_index.local_or_import(&module.info) {
LocalOrImport::Local(local_func_index) => ( LocalOrImport::Local(local_func_index) => (
@ -379,7 +400,7 @@ fn import_functions(
ctx, ctx,
signature, signature,
}) => { }) => {
if *expected_sig == signature { if *expected_sig == *signature {
functions.push(vm::ImportedFunc { functions.push(vm::ImportedFunc {
func: func.inner(), func: func.inner(),
vmctx: match ctx { vmctx: match ctx {
@ -391,8 +412,8 @@ fn import_functions(
link_errors.push(LinkError::IncorrectImportSignature { link_errors.push(LinkError::IncorrectImportSignature {
namespace: namespace.to_string(), namespace: namespace.to_string(),
name: name.to_string(), name: name.to_string(),
expected: expected_sig.clone(), expected: (*expected_sig).clone(),
found: signature.clone(), found: (*signature).clone(),
}); });
} }
} }

View File

@ -57,8 +57,8 @@ pub enum LinkError {
IncorrectImportSignature { IncorrectImportSignature {
namespace: String, namespace: String,
name: String, name: String,
expected: Arc<FuncSig>, expected: FuncSig,
found: Arc<FuncSig>, found: FuncSig,
}, },
ImportNotFound { ImportNotFound {
namespace: String, namespace: String,
@ -156,16 +156,9 @@ impl std::error::Error for RuntimeError {}
/// Comparing two `ResolveError`s always evaluates to false. /// Comparing two `ResolveError`s always evaluates to false.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum ResolveError { pub enum ResolveError {
Signature { Signature { expected: FuncSig, found: Vec<Type> },
expected: Arc<FuncSig>, ExportNotFound { name: String },
found: Vec<Type>, ExportWrongType { name: String },
},
ExportNotFound {
name: String,
},
ExportWrongType {
name: String,
},
} }
impl PartialEq for ResolveError { impl PartialEq for ResolveError {

View File

@ -1,5 +1,9 @@
use crate::export::Export; use crate::export::Export;
use hashbrown::{hash_map::Entry, HashMap}; use hashbrown::{hash_map::Entry, HashMap};
use std::{
cell::{Ref, RefCell},
rc::Rc,
};
pub trait LikeNamespace { pub trait LikeNamespace {
fn get_export(&self, name: &str) -> Option<Export>; fn get_export(&self, name: &str) -> Option<Export>;
@ -37,14 +41,14 @@ impl IsExport for Export {
/// } /// }
/// ``` /// ```
pub struct ImportObject { pub struct ImportObject {
map: HashMap<String, Box<dyn LikeNamespace>>, map: Rc<RefCell<HashMap<String, Box<dyn LikeNamespace>>>>,
} }
impl ImportObject { impl ImportObject {
/// Create a new `ImportObject`. /// Create a new `ImportObject`.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
map: HashMap::new(), map: Rc::new(RefCell::new(HashMap::new())),
} }
} }
@ -67,7 +71,9 @@ impl ImportObject {
S: Into<String>, S: Into<String>,
N: LikeNamespace + 'static, N: LikeNamespace + 'static,
{ {
match self.map.entry(name.into()) { let mut map = self.map.borrow_mut();
match map.entry(name.into()) {
Entry::Vacant(empty) => { Entry::Vacant(empty) => {
empty.insert(Box::new(namespace)); empty.insert(Box::new(namespace));
None None
@ -76,8 +82,20 @@ impl ImportObject {
} }
} }
pub fn get_namespace(&self, namespace: &str) -> Option<&(dyn LikeNamespace + 'static)> { pub fn get_namespace(&self, namespace: &str) -> Option<Ref<dyn LikeNamespace + 'static>> {
self.map.get(namespace).map(|namespace| &**namespace) let map_ref = self.map.borrow();
if map_ref.contains_key(namespace) {
Some(Ref::map(map_ref, |map| &*map[namespace]))
} else {
None
}
}
pub fn clone_ref(&self) -> Self {
Self {
map: Rc::clone(&self.map),
}
} }
} }

View File

@ -7,6 +7,7 @@ use crate::{
import::{ImportObject, LikeNamespace}, import::{ImportObject, LikeNamespace},
memory::Memory, memory::Memory,
module::{ExportIndex, Module, ModuleInner}, module::{ExportIndex, Module, ModuleInner},
sig_registry::SigRegistry,
table::Table, table::Table,
typed_func::{Func, Safe, WasmTypeList}, typed_func::{Func, Safe, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value}, types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value},
@ -38,6 +39,8 @@ impl Drop for InstanceInner {
pub struct Instance { pub struct Instance {
module: Arc<ModuleInner>, module: Arc<ModuleInner>,
inner: Box<InstanceInner>, inner: Box<InstanceInner>,
#[allow(dead_code)]
import_object: ImportObject,
} }
impl Instance { impl Instance {
@ -63,7 +66,11 @@ impl Instance {
*inner.vmctx = vm::Ctx::new(&mut inner.backing, &mut inner.import_backing, &module) *inner.vmctx = vm::Ctx::new(&mut inner.backing, &mut inner.import_backing, &module)
}; };
let instance = Instance { module, inner }; let instance = Instance {
module,
inner,
import_object: imports.clone_ref(),
};
if let Some(start_index) = instance.module.info.start_func { if let Some(start_index) = instance.module.info.start_func {
instance.call_with_index(start_index, &[])?; instance.call_with_index(start_index, &[])?;
@ -112,11 +119,12 @@ impl Instance {
.func_assoc .func_assoc
.get(*func_index) .get(*func_index)
.expect("broken invariant, incorrect func index"); .expect("broken invariant, incorrect func index");
let signature = &self.module.info.signatures[sig_index]; let signature =
SigRegistry.lookup_signature_ref(&self.module.info.signatures[sig_index]);
if signature.params() != Args::types() || signature.returns() != Rets::types() { if signature.params() != Args::types() || signature.returns() != Rets::types() {
Err(ResolveError::Signature { Err(ResolveError::Signature {
expected: Arc::clone(&signature), expected: (*signature).clone(),
found: Args::types().to_vec(), found: Args::types().to_vec(),
})?; })?;
} }
@ -183,7 +191,8 @@ impl Instance {
.func_assoc .func_assoc
.get(*func_index) .get(*func_index)
.expect("broken invariant, incorrect func index"); .expect("broken invariant, incorrect func index");
let signature = Arc::clone(&self.module.info.signatures[sig_index]); let signature =
SigRegistry.lookup_signature_ref(&self.module.info.signatures[sig_index]);
Ok(DynFunc { Ok(DynFunc {
signature, signature,
@ -374,13 +383,10 @@ impl InstanceInner {
} }
}; };
let signature = &module.info.signatures[sig_index]; let signature = SigRegistry.lookup_signature_ref(&module.info.signatures[sig_index]);
// let signature = &module.info.signatures[sig_index];
( (unsafe { FuncPointer::new(func_ptr) }, ctx, signature)
unsafe { FuncPointer::new(func_ptr) },
ctx,
Arc::clone(signature),
)
} }
fn get_memory_from_index(&self, module: &ModuleInner, mem_index: MemoryIndex) -> Memory { fn get_memory_from_index(&self, module: &ModuleInner, mem_index: MemoryIndex) -> Memory {
@ -454,10 +460,10 @@ impl<'a> DynFunc<'a> {
/// # Ok(()) /// # Ok(())
/// # } /// # }
/// ``` /// ```
pub fn call(&mut self, params: &[Value]) -> CallResult<Vec<Value>> { pub fn call(&self, params: &[Value]) -> CallResult<Vec<Value>> {
if !self.signature.check_param_value_types(params) { if !self.signature.check_param_value_types(params) {
Err(ResolveError::Signature { Err(ResolveError::Signature {
expected: self.signature.clone(), expected: (*self.signature).clone(),
found: params.iter().map(|val| val.ty()).collect(), found: params.iter().map(|val| val.ty()).collect(),
})? })?
} }

View File

@ -51,7 +51,7 @@ pub struct ModuleInfo {
pub start_func: Option<FuncIndex>, pub start_func: Option<FuncIndex>,
pub func_assoc: Map<FuncIndex, SigIndex>, pub func_assoc: Map<FuncIndex, SigIndex>,
pub signatures: Map<SigIndex, Arc<FuncSig>>, pub signatures: Map<SigIndex, FuncSig>,
pub backend: Backend, pub backend: Backend,
pub namespace_table: StringTable<NamespaceIndex>, pub namespace_table: StringTable<NamespaceIndex>,

View File

@ -49,4 +49,20 @@ impl SigRegistry {
let global = (*GLOBAL_SIG_REGISTRY).read(); let global = (*GLOBAL_SIG_REGISTRY).read();
Arc::clone(&global.sig_assoc[sig_index]) Arc::clone(&global.sig_assoc[sig_index])
} }
pub fn lookup_signature_ref(&self, func_sig: &FuncSig) -> Arc<FuncSig> {
let mut global = (*GLOBAL_SIG_REGISTRY).write();
let global = &mut *global;
let func_table = &mut global.func_table;
let sig_assoc = &mut global.sig_assoc;
if func_table.contains_key(func_sig) {
Arc::clone(&sig_assoc[func_table[func_sig]])
} else {
let arc = Arc::new(func_sig.clone());
func_table.insert(Arc::clone(&arc), sig_assoc.push(Arc::clone(&arc)));
arc
}
}
} }

View File

@ -34,6 +34,12 @@ pub struct Ctx {
/// A pointer to an array of imported functions, indexed by `FuncIndex`. /// A pointer to an array of imported functions, indexed by `FuncIndex`.
pub(crate) imported_funcs: *mut ImportedFunc, pub(crate) imported_funcs: *mut ImportedFunc,
/// A pointer to an array of signature ids. Conceptually, this maps
/// from a static, module-local signature id to a runtime-global
/// signature id. This is used to allow call-indirect to other
/// modules safely.
pub(crate) dynamic_sigindices: *const SigId,
local_backing: *mut LocalBacking, local_backing: *mut LocalBacking,
import_backing: *mut ImportBacking, import_backing: *mut ImportBacking,
module: *const ModuleInner, module: *const ModuleInner,
@ -59,6 +65,8 @@ impl Ctx {
imported_globals: import_backing.vm_globals.as_mut_ptr(), imported_globals: import_backing.vm_globals.as_mut_ptr(),
imported_funcs: import_backing.vm_functions.as_mut_ptr(), imported_funcs: import_backing.vm_functions.as_mut_ptr(),
dynamic_sigindices: local_backing.dynamic_sigindices.as_ptr(),
local_backing, local_backing,
import_backing, import_backing,
module, module,
@ -86,6 +94,8 @@ impl Ctx {
imported_globals: import_backing.vm_globals.as_mut_ptr(), imported_globals: import_backing.vm_globals.as_mut_ptr(),
imported_funcs: import_backing.vm_functions.as_mut_ptr(), imported_funcs: import_backing.vm_functions.as_mut_ptr(),
dynamic_sigindices: local_backing.dynamic_sigindices.as_ptr(),
local_backing, local_backing,
import_backing, import_backing,
module, module,
@ -458,6 +468,8 @@ mod vm_ctx_tests {
vm_memories: Map::new().into_boxed_map(), vm_memories: Map::new().into_boxed_map(),
vm_tables: Map::new().into_boxed_map(), vm_tables: Map::new().into_boxed_map(),
vm_globals: Map::new().into_boxed_map(), vm_globals: Map::new().into_boxed_map(),
dynamic_sigindices: Map::new().into_boxed_map(),
}; };
let mut import_backing = ImportBacking { let mut import_backing = ImportBacking {
memories: Map::new().into_boxed_map(), memories: Map::new().into_boxed_map(),

View File

@ -23,10 +23,14 @@ version = "0.2.0"
[dev-dependencies] [dev-dependencies]
tempfile = "3.0.7" tempfile = "3.0.7"
criterion = "0.2" criterion = "0.2"
wabt = "0.7.4"
[target.'cfg(not(windows))'.dependencies.wasmer-llvm-backend]
path = "../llvm-backend"
[features] [features]
debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"] debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"]
[[bench]] [[bench]]
name = "nginx" name = "nginx"
harness = false harness = false

View File

@ -0,0 +1,58 @@
use wasmer_runtime::{compile, error, imports, Ctx, Func, Value};
use wabt::wat2wasm;
static WAT: &'static str = r#"
(module
(type (;0;) (func (result i32)))
(func $dbz (result i32)
i32.const 42
i32.const 0
i32.div_u
)
(export "dbz" (func $dbz))
)
"#;
// static WAT2: &'static str = r#"
// (module
// (type $t0 (func (param i32)))
// (type $t1 (func))
// (func $print_i32 (export "print_i32") (type $t0) (param $lhs i32))
// (func $print (export "print") (type $t1))
// (table $table (export "table") 10 20 anyfunc)
// (memory $memory (export "memory") 1 2)
// (global $global_i32 (export "global_i32") i32 (i32.const 666)))
// "#;
fn get_wasm() -> Vec<u8> {
wat2wasm(WAT).unwrap()
}
fn foobar(ctx: &mut Ctx) -> i32 {
42
}
fn main() -> Result<(), error::Error> {
let wasm = get_wasm();
let module = compile(&wasm)?;
// let import_module = compile(&wat2wasm(WAT2).unwrap())?;
// let import_instance = import_module.instantiate(&imports! {})?;
// let imports = imports! {
// "spectest" => import_instance,
// };
println!("instantiating");
let instance = module.instantiate(&imports! {})?;
let foo = instance.dyn_func("dbz")?;
let result = foo.call(&[]);
println!("result: {:?}", result);
Ok(())
}

View File

@ -154,10 +154,15 @@ pub fn instantiate(wasm: &[u8], import_object: &ImportObject) -> error::Result<I
fn default_compiler() -> &'static dyn Compiler { fn default_compiler() -> &'static dyn Compiler {
use lazy_static::lazy_static; use lazy_static::lazy_static;
use wasmer_clif_backend::CraneliftCompiler;
#[cfg(feature = "llvm")]
use wasmer_llvm_backend::LLVMCompiler as DefaultCompiler;
#[cfg(not(feature = "llvm"))]
use wasmer_clif_backend::CraneliftCompiler as DefaultCompiler;
lazy_static! { lazy_static! {
static ref DEFAULT_COMPILER: CraneliftCompiler = { CraneliftCompiler::new() }; static ref DEFAULT_COMPILER: DefaultCompiler = { DefaultCompiler::new() };
} }
&*DEFAULT_COMPILER as &dyn Compiler &*DEFAULT_COMPILER as &dyn Compiler

View File

@ -18,6 +18,11 @@ wabt = "0.7.2"
wasmer-clif-backend = { path = "../clif-backend", version = "0.2.0" } wasmer-clif-backend = { path = "../clif-backend", version = "0.2.0" }
wabt = "0.7.2" wabt = "0.7.2"
[target.'cfg(not(windows))'.dev-dependencies]
wasmer-llvm-backend = { path = "../llvm-backend", version = "0.1.0" }
[features] [features]
default = ["fast-tests"] default = ["fast-tests"]
fast-tests = [] fast-tests = []
clif = []
llvm = []

View File

@ -77,12 +77,12 @@ const TESTS: &[&str] = &[
static COMMON: &'static str = r##" static COMMON: &'static str = r##"
use std::{{f32, f64}}; use std::{{f32, f64}};
use wabt::wat2wasm; use wabt::wat2wasm;
use wasmer_clif_backend::CraneliftCompiler;
use wasmer_runtime_core::import::ImportObject; use wasmer_runtime_core::import::ImportObject;
use wasmer_runtime_core::types::Value; use wasmer_runtime_core::types::Value;
use wasmer_runtime_core::{{Instance, module::Module}}; use wasmer_runtime_core::{{Instance, module::Module}};
use wasmer_runtime_core::error::Result; use wasmer_runtime_core::error::Result;
use wasmer_runtime_core::vm::Ctx; use wasmer_runtime_core::vm::Ctx;
use wasmer_runtime_core::backend::Compiler;
static IMPORT_MODULE: &str = r#" static IMPORT_MODULE: &str = r#"
(module (module
@ -95,9 +95,28 @@ static IMPORT_MODULE: &str = r#"
(global $global_i32 (export "global_i32") i32 (i32.const 666))) (global $global_i32 (export "global_i32") i32 (i32.const 666)))
"#; "#;
#[cfg(feature = "clif")]
fn get_compiler() -> impl Compiler {
use wasmer_clif_backend::CraneliftCompiler;
CraneliftCompiler::new()
}
#[cfg(feature = "llvm")]
fn get_compiler() -> impl Compiler {
use wasmer_llvm_backend::LLVMCompiler;
LLVMCompiler::new()
}
#[cfg(not(any(feature = "llvm", feature = "clif")))]
fn get_compiler() -> impl Compiler {
panic!("compiler not specified, activate a compiler via features");
use wasmer_clif_backend::CraneliftCompiler;
CraneliftCompiler::new()
}
pub fn generate_imports() -> ImportObject { pub fn generate_imports() -> ImportObject {
let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed"); let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed");
let module = wasmer_runtime_core::compile_with(&wasm_binary[..], &CraneliftCompiler::new()) let module = wasmer_runtime_core::compile_with(&wasm_binary[..], &get_compiler())
.expect("WASM can't be compiled"); .expect("WASM can't be compiled");
let instance = module let instance = module
.instantiate(&ImportObject::new()) .instantiate(&ImportObject::new())
@ -358,7 +377,7 @@ fn test_module_{}() {{
let module_str = \"{}\"; let module_str = \"{}\";
println!(\"{{}}\", module_str); println!(\"{{}}\", module_str);
let wasm_binary = wat2wasm(module_str.as_bytes()).expect(\"WAST not valid or malformed\"); let wasm_binary = wat2wasm(module_str.as_bytes()).expect(\"WAST not valid or malformed\");
let module = wasmer_runtime_core::compile_with(&wasm_binary[..], &CraneliftCompiler::new()).expect(\"WASM can't be compiled\"); let module = wasmer_runtime_core::compile_with(&wasm_binary[..], &get_compiler()).expect(\"WASM can't be compiled\");
module.instantiate(&generate_imports()).expect(\"WASM can't be instantiated\") module.instantiate(&generate_imports()).expect(\"WASM can't be instantiated\")
}}\n", }}\n",
self.last_module, self.last_module,
@ -381,7 +400,7 @@ fn test_module_{}() {{
"#[test] "#[test]
fn {}_assert_invalid() {{ fn {}_assert_invalid() {{
let wasm_binary = {:?}; let wasm_binary = {:?};
let module = wasmer_runtime_core::compile_with(&wasm_binary, &CraneliftCompiler::new()); let module = wasmer_runtime_core::compile_with(&wasm_binary, &get_compiler());
assert!(module.is_err(), \"WASM should not compile as is invalid\"); assert!(module.is_err(), \"WASM should not compile as is invalid\");
}}\n", }}\n",
command_name, command_name,
@ -512,7 +531,7 @@ fn {}_assert_invalid() {{
"#[test] "#[test]
fn {}_assert_malformed() {{ fn {}_assert_malformed() {{
let wasm_binary = {:?}; let wasm_binary = {:?};
let compilation = wasmer_runtime_core::compile_with(&wasm_binary, &CraneliftCompiler::new()); let compilation = wasmer_runtime_core::compile_with(&wasm_binary, &get_compiler());
assert!(compilation.is_err(), \"WASM should not compile as is malformed\"); assert!(compilation.is_err(), \"WASM should not compile as is malformed\");
}}\n", }}\n",
command_name, command_name,

View File

@ -1,6 +1,6 @@
use wabt::wat2wasm; use wabt::wat2wasm;
use wasmer_clif_backend::CraneliftCompiler;
use wasmer_runtime_core::{ use wasmer_runtime_core::{
backend::Compiler,
error, error,
global::Global, global::Global,
memory::Memory, memory::Memory,
@ -10,12 +10,31 @@ use wasmer_runtime_core::{
units::Pages, units::Pages,
}; };
#[cfg(feature = "clif")]
fn get_compiler() -> impl Compiler {
use wasmer_clif_backend::CraneliftCompiler;
CraneliftCompiler::new()
}
#[cfg(feature = "llvm")]
fn get_compiler() -> impl Compiler {
use wasmer_llvm_backend::LLVMCompiler;
LLVMCompiler::new()
}
#[cfg(not(any(feature = "llvm", feature = "clif")))]
fn get_compiler() -> impl Compiler {
panic!("compiler not specified, activate a compiler via features");
use wasmer_clif_backend::CraneliftCompiler;
CraneliftCompiler::new()
}
static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm"); static EXAMPLE_WASM: &'static [u8] = include_bytes!("simple.wasm");
fn main() -> error::Result<()> { fn main() -> error::Result<()> {
let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed"); let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed");
let inner_module = wasmer_runtime_core::compile_with(&wasm_binary, &CraneliftCompiler::new())?; let inner_module = wasmer_runtime_core::compile_with(&wasm_binary, &get_compiler())?;
let memory = Memory::new(MemoryDescriptor { let memory = Memory::new(MemoryDescriptor {
minimum: Pages(1), minimum: Pages(1),
@ -50,7 +69,7 @@ fn main() -> error::Result<()> {
"env" => inner_instance, "env" => inner_instance,
}; };
let outer_module = wasmer_runtime_core::compile_with(EXAMPLE_WASM, &CraneliftCompiler::new())?; let outer_module = wasmer_runtime_core::compile_with(EXAMPLE_WASM, &get_compiler())?;
let outer_instance = outer_module.instantiate(&outer_imports)?; let outer_instance = outer_module.instantiate(&outer_imports)?;
let ret = outer_instance.call("main", &[Value::I32(42)])?; let ret = outer_instance.call("main", &[Value::I32(42)])?;
println!("ret: {:?}", ret); println!("ret: {:?}", ret);