mirror of
https://github.com/fluencelabs/wasmer
synced 2024-12-13 22:25:40 +00:00
Merge pull request #239 from wasmerio/feature/llvm-backend
LLVM-based compiler backend
This commit is contained in:
commit
b1a52bc8ff
@ -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
|
||||
|
@ -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
3
.gitignore
vendored
@ -3,5 +3,4 @@
|
||||
/artifacts
|
||||
.DS_Store
|
||||
.idea
|
||||
|
||||
\.vscode
|
||||
**/.vscode
|
||||
|
478
Cargo.lock
generated
478
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -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"
|
||||
|
8
Makefile
8
Makefile
@ -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
|
||||
|
12
README.md
12
README.md
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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 _
|
||||
}
|
||||
};
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 = []
|
@ -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;
|
||||
|
@ -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));
|
||||
}
|
||||
|
@ -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[..])
|
||||
|
29
lib/llvm-backend/Cargo.toml
Normal file
29
lib/llvm-backend/Cargo.toml
Normal 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
215
lib/llvm-backend/build.rs
Normal 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");
|
||||
}
|
199
lib/llvm-backend/cpp/object_loader.cpp
Normal file
199
lib/llvm-backend/cpp/object_loader.cpp
Normal 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();
|
||||
}
|
185
lib/llvm-backend/cpp/object_loader.hh
Normal file
185
lib/llvm-backend/cpp/object_loader.hh
Normal 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));
|
||||
}
|
||||
}
|
502
lib/llvm-backend/src/backend.rs
Normal file
502
lib/llvm-backend/src/backend.rs
Normal 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
2465
lib/llvm-backend/src/code.rs
Normal file
File diff suppressed because it is too large
Load Diff
61
lib/llvm-backend/src/example.rs
Normal file
61
lib/llvm-backend/src/example.rs
Normal 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() }
|
||||
}
|
772
lib/llvm-backend/src/intrinsics.rs
Normal file
772
lib/llvm-backend/src/intrinsics.rs
Normal 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
137
lib/llvm-backend/src/lib.rs
Normal 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);
|
||||
}
|
||||
}
|
7
lib/llvm-backend/src/platform/mod.rs
Normal file
7
lib/llvm-backend/src/platform/mod.rs
Normal 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");
|
72
lib/llvm-backend/src/platform/unix.rs
Normal file
72
lib/llvm-backend/src/platform/unix.rs
Normal 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);
|
||||
}
|
||||
}
|
341
lib/llvm-backend/src/read_info.rs
Normal file
341
lib/llvm-backend/src/read_info.rs
Normal 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,
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
244
lib/llvm-backend/src/state.rs
Normal file
244
lib/llvm-backend/src/state.rs
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
120
lib/llvm-backend/src/trampolines.rs
Normal file
120
lib/llvm-backend/src/trampolines.rs
Normal 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);
|
||||
}
|
@ -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
|
||||
|
@ -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(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
})?
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
58
lib/runtime/examples/call.rs
Normal file
58
lib/runtime/examples/call.rs
Normal 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(())
|
||||
}
|
@ -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
|
||||
|
@ -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 = []
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user