Merge pull request #239 from wasmerio/feature/llvm-backend

LLVM-based compiler backend
This commit is contained in:
Syrus Akbary 2019-03-07 19:49:06 -08:00 committed by GitHub
commit b1a52bc8ff
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 6053 additions and 291 deletions

View File

@ -17,14 +17,26 @@ cache:
- target
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
# - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe
# - 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 update
- rustc -vV
- cargo -vV
# Install InnoSetup
- appveyor-retry appveyor DownloadFile https://s3-us-west-1.amazonaws.com/rust-lang-ci2/rust-ci-mirror/2017-08-22-is.exe
- 2017-08-22-is.exe /VERYSILENT /SUPPRESSMSGBOXES /NORESTART /SP-
@ -36,7 +48,7 @@ build_script:
- cargo build --release --verbose
test_script:
- cargo test --package wasmer-spectests
- cargo test --manifest-path lib/spectests/Cargo.toml --features clif
after_build:
- cd ./src/installer

View File

@ -13,6 +13,8 @@ jobs:
name: Install dependencies
command: |
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:
name: Install lint deps
command: |
@ -20,7 +22,9 @@ jobs:
rustup component add clippy
- run:
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:
paths:
- /usr/local/cargo/registry
@ -41,8 +45,23 @@ jobs:
name: Install dependencies
command: |
sudo apt-get install -y cmake
- run: make test
- run: make integration-tests
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:
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:
paths:
- /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
tar xf cmake-3.4.1-Darwin-x86_64.tar.gz
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
# Installing LLVM outside of brew
curl -O https://releases.llvm.org/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:
name: Install Rust
command: |
@ -73,19 +95,31 @@ jobs:
export PATH="$HOME/.cargo/bin:$PATH"
cargo --version
- run:
name: Execute tests
name: Tests
command: |
export PATH="$HOME/.cargo/bin:$PATH"
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export LLVM_SYS_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
- run:
name: Execute integration tests
name: Emscripten 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/"
# 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
- save_cache:
paths:
@ -110,12 +144,22 @@ jobs:
name: Install dependencies
command: |
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:
name: Execute tests
command: make test
- run:
name: Make release build
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: Release Build
command: |
export LLVM_SYS_70_PREFIX="`pwd`/clang+llvm-7.0.0-x86_64-linux-gnu-ubuntu-16.04/"
make release
mkdir -p artifacts
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
tar xf cmake-3.4.1-Darwin-x86_64.tar.gz
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
# Installing LLVM outside of brew
curl -O https://releases.llvm.org/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:
name: Install Rust
command: |
@ -160,19 +207,31 @@ jobs:
export PATH="$HOME/.cargo/bin:$PATH"
cargo --version
- run:
name: Execute tests
name: Tests
command: |
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH"
export LLVM_SYS_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
- run:
name: Make release build
name: Emscripten Tests
command: |
export PATH="`pwd`/cmake-3.4.1-Darwin-x86_64/CMake.app/Contents/bin:$PATH"
export PATH="$HOME/.cargo/bin:$PATH"
export LLVM_SYS_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
mkdir -p artifacts
# VERSION=$(cargo pkgid | cut -d# -f2 | cut -d: -f2)
@ -205,8 +264,12 @@ jobs:
name: Install dependencies
command: |
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: 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:
paths:
- /usr/local/cargo/registry

3
.gitignore vendored
View File

@ -3,5 +3,4 @@
/artifacts
.DS_Store
.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-emscripten = { path = "lib/emscripten" }
[target.'cfg(not(windows))'.dependencies]
wasmer-llvm-backend = { path = "lib/llvm-backend" }
[workspace]
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]
wabt = "0.7.2"
glob = "0.2.11"

View File

@ -37,11 +37,17 @@ precommit: lint test
test:
# 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 --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 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:
# If you are in OS-X, you will need mingw-w64 for cross compiling to windows
# 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
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.
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).
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

View File

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

View File

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

View File

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

View File

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

View File

@ -153,11 +153,11 @@ impl ProtectedCaller for Caller {
}
}
fn get_func_from_index(
module: &ModuleInner,
fn get_func_from_index<'a>(
module: &'a ModuleInner,
import_backing: &ImportBacking,
func_index: FuncIndex,
) -> (*const vm::Func, Context, Arc<FuncSig>, SigIndex) {
) -> (*const vm::Func, Context, &'a FuncSig, SigIndex) {
let sig_index = *module
.info
.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)
}

View File

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

View File

@ -343,7 +343,7 @@ impl EmscriptenGlobals {
if name == "abortOnCannotGrowMemory" && namespace == "env" {
let sig_index = module.info().func_assoc[index.convert_up(module.info())];
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;
}
break;

View File

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

View File

@ -1,16 +1,35 @@
macro_rules! assert_emscripten_output {
($file:expr, $name:expr, $args:expr, $expected:expr) => {{
use wasmer_clif_backend::CraneliftCompiler;
use wasmer_emscripten::{
EmscriptenGlobals,
generate_emscripten_env,
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 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");
// 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

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

View File

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

View File

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

View File

@ -1,5 +1,9 @@
use crate::export::Export;
use hashbrown::{hash_map::Entry, HashMap};
use std::{
cell::{Ref, RefCell},
rc::Rc,
};
pub trait LikeNamespace {
fn get_export(&self, name: &str) -> Option<Export>;
@ -37,14 +41,14 @@ impl IsExport for Export {
/// }
/// ```
pub struct ImportObject {
map: HashMap<String, Box<dyn LikeNamespace>>,
map: Rc<RefCell<HashMap<String, Box<dyn LikeNamespace>>>>,
}
impl ImportObject {
/// Create a new `ImportObject`.
pub fn new() -> Self {
Self {
map: HashMap::new(),
map: Rc::new(RefCell::new(HashMap::new())),
}
}
@ -67,7 +71,9 @@ impl ImportObject {
S: Into<String>,
N: LikeNamespace + 'static,
{
match self.map.entry(name.into()) {
let mut map = self.map.borrow_mut();
match map.entry(name.into()) {
Entry::Vacant(empty) => {
empty.insert(Box::new(namespace));
None
@ -76,8 +82,20 @@ impl ImportObject {
}
}
pub fn get_namespace(&self, namespace: &str) -> Option<&(dyn LikeNamespace + 'static)> {
self.map.get(namespace).map(|namespace| &**namespace)
pub fn get_namespace(&self, namespace: &str) -> Option<Ref<dyn LikeNamespace + 'static>> {
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},
memory::Memory,
module::{ExportIndex, Module, ModuleInner},
sig_registry::SigRegistry,
table::Table,
typed_func::{Func, Safe, WasmTypeList},
types::{FuncIndex, FuncSig, GlobalIndex, LocalOrImport, MemoryIndex, TableIndex, Value},
@ -38,6 +39,8 @@ impl Drop for InstanceInner {
pub struct Instance {
module: Arc<ModuleInner>,
inner: Box<InstanceInner>,
#[allow(dead_code)]
import_object: ImportObject,
}
impl Instance {
@ -63,7 +66,11 @@ impl Instance {
*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 {
instance.call_with_index(start_index, &[])?;
@ -112,11 +119,12 @@ impl Instance {
.func_assoc
.get(*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() {
Err(ResolveError::Signature {
expected: Arc::clone(&signature),
expected: (*signature).clone(),
found: Args::types().to_vec(),
})?;
}
@ -183,7 +191,8 @@ impl Instance {
.func_assoc
.get(*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 {
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,
Arc::clone(signature),
)
(unsafe { FuncPointer::new(func_ptr) }, ctx, signature)
}
fn get_memory_from_index(&self, module: &ModuleInner, mem_index: MemoryIndex) -> Memory {
@ -454,10 +460,10 @@ impl<'a> DynFunc<'a> {
/// # 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) {
Err(ResolveError::Signature {
expected: self.signature.clone(),
expected: (*self.signature).clone(),
found: params.iter().map(|val| val.ty()).collect(),
})?
}

View File

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

View File

@ -49,4 +49,20 @@ impl SigRegistry {
let global = (*GLOBAL_SIG_REGISTRY).read();
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`.
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,
import_backing: *mut ImportBacking,
module: *const ModuleInner,
@ -59,6 +65,8 @@ impl Ctx {
imported_globals: import_backing.vm_globals.as_mut_ptr(),
imported_funcs: import_backing.vm_functions.as_mut_ptr(),
dynamic_sigindices: local_backing.dynamic_sigindices.as_ptr(),
local_backing,
import_backing,
module,
@ -86,6 +94,8 @@ impl Ctx {
imported_globals: import_backing.vm_globals.as_mut_ptr(),
imported_funcs: import_backing.vm_functions.as_mut_ptr(),
dynamic_sigindices: local_backing.dynamic_sigindices.as_ptr(),
local_backing,
import_backing,
module,
@ -458,6 +468,8 @@ mod vm_ctx_tests {
vm_memories: Map::new().into_boxed_map(),
vm_tables: Map::new().into_boxed_map(),
vm_globals: Map::new().into_boxed_map(),
dynamic_sigindices: Map::new().into_boxed_map(),
};
let mut import_backing = ImportBacking {
memories: Map::new().into_boxed_map(),

View File

@ -23,10 +23,14 @@ version = "0.2.0"
[dev-dependencies]
tempfile = "3.0.7"
criterion = "0.2"
wabt = "0.7.4"
[target.'cfg(not(windows))'.dependencies.wasmer-llvm-backend]
path = "../llvm-backend"
[features]
debug = ["wasmer-clif-backend/debug", "wasmer-runtime-core/debug"]
[[bench]]
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 {
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! {
static ref DEFAULT_COMPILER: CraneliftCompiler = { CraneliftCompiler::new() };
static ref DEFAULT_COMPILER: DefaultCompiler = { DefaultCompiler::new() };
}
&*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" }
wabt = "0.7.2"
[target.'cfg(not(windows))'.dev-dependencies]
wasmer-llvm-backend = { path = "../llvm-backend", version = "0.1.0" }
[features]
default = ["fast-tests"]
fast-tests = []
fast-tests = []
clif = []
llvm = []

View File

@ -77,12 +77,12 @@ const TESTS: &[&str] = &[
static COMMON: &'static str = r##"
use std::{{f32, f64}};
use wabt::wat2wasm;
use wasmer_clif_backend::CraneliftCompiler;
use wasmer_runtime_core::import::ImportObject;
use wasmer_runtime_core::types::Value;
use wasmer_runtime_core::{{Instance, module::Module}};
use wasmer_runtime_core::error::Result;
use wasmer_runtime_core::vm::Ctx;
use wasmer_runtime_core::backend::Compiler;
static IMPORT_MODULE: &str = r#"
(module
@ -95,9 +95,28 @@ static IMPORT_MODULE: &str = r#"
(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 {
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");
let instance = module
.instantiate(&ImportObject::new())
@ -358,7 +377,7 @@ fn test_module_{}() {{
let module_str = \"{}\";
println!(\"{{}}\", module_str);
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\")
}}\n",
self.last_module,
@ -381,7 +400,7 @@ fn test_module_{}() {{
"#[test]
fn {}_assert_invalid() {{
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\");
}}\n",
command_name,
@ -512,7 +531,7 @@ fn {}_assert_invalid() {{
"#[test]
fn {}_assert_malformed() {{
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\");
}}\n",
command_name,

View File

@ -1,6 +1,6 @@
use wabt::wat2wasm;
use wasmer_clif_backend::CraneliftCompiler;
use wasmer_runtime_core::{
backend::Compiler,
error,
global::Global,
memory::Memory,
@ -10,12 +10,31 @@ use wasmer_runtime_core::{
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");
fn main() -> error::Result<()> {
let wasm_binary = wat2wasm(IMPORT_MODULE.as_bytes()).expect("WAST not valid or malformed");
let inner_module = wasmer_runtime_core::compile_with(&wasm_binary, &CraneliftCompiler::new())?;
let inner_module = wasmer_runtime_core::compile_with(&wasm_binary, &get_compiler())?;
let memory = Memory::new(MemoryDescriptor {
minimum: Pages(1),
@ -50,7 +69,7 @@ fn main() -> error::Result<()> {
"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 ret = outer_instance.call("main", &[Value::I32(42)])?;
println!("ret: {:?}", ret);