Merge branch 'master' into wasi-common

This commit is contained in:
Jakub Konka
2019-11-08 06:53:18 +01:00
39 changed files with 692 additions and 398 deletions

View File

@@ -70,19 +70,19 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
build: [stable, beta, nightly, windows, linux] build: [stable, beta, nightly, windows, macos]
include: include:
- build: stable - build: stable
os: macos-latest os: ubuntu-latest
rust: stable rust: stable
- build: beta - build: beta
os: macos-latest os: ubuntu-latest
rust: beta rust: beta
- build: nightly - build: nightly
os: macos-latest
rust: nightly
- build: linux
os: ubuntu-latest os: ubuntu-latest
rust: nightly
- build: macos
os: macos-latest
rust: stable rust: stable
- build: windows - build: windows
os: windows-latest os: windows-latest
@@ -216,7 +216,7 @@ jobs:
- run: $CENTOS cargo build --release --bin wasmtime --bin wasm2obj - run: $CENTOS cargo build --release --bin wasmtime --bin wasm2obj
shell: bash shell: bash
# Build `libwasmtime_api.so` # Build `libwasmtime_api.so`
- run: $CENTOS cargo build --release --features wasm-c-api --manifest-path wasmtime-api/Cargo.toml - run: $CENTOS cargo build --release --manifest-path wasmtime-api/Cargo.toml
shell: bash shell: bash
# Test what we just built # Test what we just built
- run: $CENTOS cargo test --features wasi-common/wasm_tests --release --all --exclude lightbeam --exclude wasmtime-wasi-c --exclude wasmtime-py --exclude wasmtime-api - run: $CENTOS cargo test --features wasi-common/wasm_tests --release --all --exclude lightbeam --exclude wasmtime-wasi-c --exclude wasmtime-py --exclude wasmtime-api

View File

@@ -38,9 +38,8 @@ libc = "0.2.60"
rayon = "1.1" rayon = "1.1"
wasm-webidl-bindings = "0.6" wasm-webidl-bindings = "0.6"
# build.rs tests whether to enable a workaround for the libc strtof function. [build-dependencies]
[target.'cfg(target_os = "linux")'.build-dependencies] anyhow = "1.0.19"
libc = "0.2.60"
[workspace] [workspace]
members = [ members = [

View File

@@ -35,34 +35,24 @@ of ongoing research.
Additional goals for Wasmtime include: Additional goals for Wasmtime include:
- Support a variety of host APIs (not just WASI), with fast calling sequences, - Support a variety of host APIs (not just WASI), with fast calling sequences,
and develop proposals for additional API modules to be part of WASI. and develop proposals for additional API modules to be part of WASI.
- Implement the [proposed WebAssembly C API]. - Facilitate development and testing around the [Cranelift] and [Lightbeam] JITs,
- Facilitate testing, experimentation, and development around the [Cranelift] and and other WebAssembly execution strategies.
[Lightbeam] JITs.
- Develop a native ABI used for compiling WebAssembly suitable for use in both - Develop a native ABI used for compiling WebAssembly suitable for use in both
JIT and AOT to native object files. JIT and AOT to native object files.
[proposed WebAssembly C API]: https://github.com/rossberg/wasm-c-api
[Cranelift]: https://github.com/CraneStation/cranelift [Cranelift]: https://github.com/CraneStation/cranelift
[Lightbeam]: https://github.com/CraneStation/lightbeam [Lightbeam]: https://github.com/CraneStation/wasmtime/tree/master/lightbeam
#### Including Wasmtime in your project #### Including Wasmtime in your project
Wasmtime exposes an API for JIT compilation through the `wasmtime-jit` subcrate, which depends on `wasmtime-environ` and `wasmtime-runtime` for the ABI and runtime support respectively. However, this API is not documented and subject to change. Please use at your own risk!
Build the individual crates as such: Wasmtime exposes an API for embedding as a library through the `wasmtime-api` subcrate,
which contains both a [high-level and safe Rust API], as well as a C-compatible API
compatible with the [proposed WebAssembly C API].
``` For more information, see the [Rust API embedding chapter] of the Wasmtime documentation.
cargo build --package wasmtime-jit
```
Wasmtime does not currently publish these crates on crates.io. They may be included as a git dependency, like this: [high-level and safe Rust API]: https://docs.rs/wasmtime-api/
[proposed WebAssembly C API]: https://github.com/WebAssembly/wasm-c-api
```toml [Rust API embedding chapter]: https://cranestation.github.io/wasmtime/embed-rust.html
[dependencies]
wasmtime-environ = { git = "https://github.com/CraneStation/wasmtime", rev = "somecommithash" }
wasmtime-runtime = { git = "https://github.com/CraneStation/wasmtime", rev = "somecommithash" }
wasmtime-jit = { git = "https://github.com/CraneStation/wasmtime", rev = "somecommithash" }
```
All three crates must be specified as dependencies for `wasmtime-jit` to build correctly, at the moment.
It's Wasmtime. It's Wasmtime.

235
build.rs
View File

@@ -3,108 +3,105 @@
//! By generating a separate `#[test]` test for each file, we allow cargo test //! By generating a separate `#[test]` test for each file, we allow cargo test
//! to automatically run the files in parallel. //! to automatically run the files in parallel.
use anyhow::Context;
use std::env; use std::env;
use std::fs::{read_dir, File}; use std::fmt::Write;
use std::io::{self, Write}; use std::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::Command;
fn main() { fn main() -> anyhow::Result<()> {
let out_dir = let out_dir = PathBuf::from(
PathBuf::from(env::var("OUT_DIR").expect("The OUT_DIR environment variable must be set")); env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
let mut out = File::create(out_dir.join("wast_testsuite_tests.rs")) );
.expect("error generating test source file"); let mut out = String::new();
for strategy in &[ for strategy in &[
"Cranelift", "Cranelift",
#[cfg(feature = "lightbeam")] #[cfg(feature = "lightbeam")]
"Lightbeam", "Lightbeam",
] { ] {
writeln!(out, "#[cfg(test)]").expect("generating tests"); writeln!(out, "#[cfg(test)]")?;
writeln!(out, "#[allow(non_snake_case)]").expect("generating tests"); writeln!(out, "#[allow(non_snake_case)]")?;
writeln!(out, "mod {} {{", strategy).expect("generating tests"); writeln!(out, "mod {} {{", strategy)?;
test_directory(&mut out, "misc_testsuite", strategy).expect("generating tests"); test_directory(&mut out, "misc_testsuite", strategy)?;
test_directory(&mut out, "spec_testsuite", strategy).expect("generating tests"); let spec_tests = test_directory(&mut out, "spec_testsuite", strategy)?;
// Skip running spec_testsuite tests if the submodule isn't checked out. // Skip running spec_testsuite tests if the submodule isn't checked
if read_dir("spec_testsuite") // out.
.expect("reading testsuite directory") if spec_tests > 0 {
.next() start_test_module(&mut out, "simd")?;
.is_some() write_testsuite_tests(
{
test_file(
&mut out, &mut out,
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_address.wast"]), "spec_testsuite/proposals/simd/simd_address.wast",
"simd",
strategy, strategy,
) )?;
.expect("generating tests"); write_testsuite_tests(
test_file(
&mut out, &mut out,
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_align.wast"]), "spec_testsuite/proposals/simd/simd_align.wast",
"simd",
strategy, strategy,
) )?;
.expect("generating tests"); write_testsuite_tests(
test_file(
&mut out, &mut out,
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_const.wast"]), "spec_testsuite/proposals/simd/simd_const.wast",
"simd",
strategy, strategy,
) )?;
.expect("generating tests"); finish_test_module(&mut out)?;
let multi_value_suite = &to_os_path(&["spec_testsuite", "proposals", "multi-value"]); test_directory(&mut out, "spec_testsuite/proposals/multi-value", strategy)
test_directory(&mut out, &multi_value_suite, strategy).expect("generating tests"); .expect("generating tests");
} else { } else {
println!("cargo:warning=The spec testsuite is disabled. To enable, run `git submodule update --remote`."); println!("cargo:warning=The spec testsuite is disabled. To enable, run `git submodule update --remote`.");
} }
writeln!(out, "}}").expect("generating tests"); writeln!(out, "}}")?;
}
} }
/// Helper for creating OS-independent paths. // Write out our auto-generated tests and opportunistically format them with
fn to_os_path(components: &[&str]) -> String { // `rustfmt` if it's installed.
let path: PathBuf = components.iter().collect(); let output = out_dir.join("wast_testsuite_tests.rs");
path.display().to_string() fs::write(&output, out)?;
drop(Command::new("rustfmt").arg(&output).status());
Ok(())
} }
fn test_directory(out: &mut File, path: &str, strategy: &str) -> io::Result<()> { fn test_directory(
let mut dir_entries: Vec<_> = read_dir(path) out: &mut String,
.expect("reading testsuite directory") path: impl AsRef<Path>,
strategy: &str,
) -> anyhow::Result<usize> {
let path = path.as_ref();
let mut dir_entries: Vec<_> = path
.read_dir()
.context(format!("failed to read {:?}", path))?
.map(|r| r.expect("reading testsuite directory entry")) .map(|r| r.expect("reading testsuite directory entry"))
.filter(|dir_entry| { .filter_map(|dir_entry| {
let p = dir_entry.path(); let p = dir_entry.path();
if let Some(ext) = p.extension() { let ext = p.extension()?;
// Only look at wast files. // Only look at wast files.
if ext == "wast" { if ext != "wast" {
return None;
}
// Ignore files starting with `.`, which could be editor temporary files // Ignore files starting with `.`, which could be editor temporary files
if let Some(stem) = p.file_stem() { if p.file_stem()?.to_str()?.starts_with(".") {
if let Some(stemstr) = stem.to_str() { return None;
if !stemstr.starts_with('.') {
return true;
} }
} Some(p)
}
}
}
false
}) })
.collect(); .collect();
dir_entries.sort_by_key(|dir| dir.path()); dir_entries.sort();
let testsuite = &extract_name(path); let testsuite = &extract_name(path);
start_test_module(out, testsuite)?; start_test_module(out, testsuite)?;
for dir_entry in dir_entries { for entry in dir_entries.iter() {
write_testsuite_tests(out, &dir_entry.path(), testsuite, strategy)?; write_testsuite_tests(out, entry, testsuite, strategy)?;
} }
finish_test_module(out) finish_test_module(out)?;
} Ok(dir_entries.len())
fn test_file(out: &mut File, testfile: &str, strategy: &str) -> io::Result<()> {
let path = Path::new(testfile);
let testsuite = format!("single_test_{}", extract_name(path));
start_test_module(out, &testsuite)?;
write_testsuite_tests(out, path, &testsuite, strategy)?;
finish_test_module(out)
} }
/// Extract a valid Rust identifier from the stem of a path. /// Extract a valid Rust identifier from the stem of a path.
@@ -118,62 +115,37 @@ fn extract_name(path: impl AsRef<Path>) -> String {
.replace("/", "_") .replace("/", "_")
} }
fn start_test_module(out: &mut File, testsuite: &str) -> io::Result<()> { fn start_test_module(out: &mut String, testsuite: &str) -> anyhow::Result<()> {
writeln!(out, "mod {} {{", testsuite)?; writeln!(out, "mod {} {{", testsuite)?;
writeln!( Ok(())
out,
" use super::super::{{native_isa, Path, WastContext, Compiler, Features, CompilationStrategy}};"
)
} }
fn finish_test_module(out: &mut File) -> io::Result<()> { fn finish_test_module(out: &mut String) -> anyhow::Result<()> {
writeln!(out, " }}") out.push_str("}\n");
Ok(())
} }
fn write_testsuite_tests( fn write_testsuite_tests(
out: &mut File, out: &mut String,
path: &Path, path: impl AsRef<Path>,
testsuite: &str, testsuite: &str,
strategy: &str, strategy: &str,
) -> io::Result<()> { ) -> anyhow::Result<()> {
let path = path.as_ref();
println!("cargo:rerun-if-changed={}", path.display());
let testname = extract_name(path); let testname = extract_name(path);
writeln!(out, "#[test]")?; writeln!(out, "#[test]")?;
if ignore(testsuite, &testname, strategy) { if ignore(testsuite, &testname, strategy) {
writeln!(out, "#[ignore]")?; writeln!(out, "#[ignore]")?;
} }
writeln!(out, " fn r#{}() {{", &testname)?; writeln!(out, "fn r#{}() -> anyhow::Result<()> {{", &testname)?;
writeln!(out, " let isa = native_isa();")?;
writeln!( writeln!(
out, out,
" let compiler = Compiler::new(isa, CompilationStrategy::{});", "crate::run_wast(r#\"{}\"#, crate::CompilationStrategy::{})",
path.display(),
strategy strategy
)?; )?;
writeln!(
out,
" let features = Features {{ simd: {}, multi_value: {}, ..Default::default() }};",
testsuite.contains("simd"),
testsuite.contains("multi_value")
)?;
writeln!(
out,
" let mut wast_context = WastContext::new(Box::new(compiler)).with_features(features);"
)?;
writeln!(out, " wast_context")?;
writeln!(out, " .register_spectest()")?;
writeln!(
out,
" .expect(\"instantiating \\\"spectest\\\"\");"
)?;
writeln!(out, " wast_context")?;
write!(out, " .run_file(Path::new(\"")?;
// Write out the string with escape_debug to prevent special characters such
// as backslash from being reinterpreted.
for c in path.display().to_string().chars() {
write!(out, "{}", c.escape_debug())?;
}
writeln!(out, "\"))")?;
writeln!(out, " .expect(\"error running wast file\");",)?;
writeln!(out, "}}")?; writeln!(out, "}}")?;
writeln!(out)?; writeln!(out)?;
Ok(()) Ok(())
@@ -206,66 +178,9 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool {
// ABI only has a single return register, so we need to wait on full // ABI only has a single return register, so we need to wait on full
// multi-value support in Cranelift. // multi-value support in Cranelift.
(_, _) if is_multi_value => true, (_, _) if is_multi_value => true,
// Until Windows unwind information is added we must disable SIMD spec tests that trap.
(_, _) if testname.starts_with("simd") => return true,
("spec_testsuite", "address") => true,
("spec_testsuite", "align") => true,
("spec_testsuite", "call") => true,
("spec_testsuite", "call_indirect") => true,
("spec_testsuite", "conversions") => true,
("spec_testsuite", "elem") => true,
("spec_testsuite", "fac") => true,
("spec_testsuite", "func_ptrs") => true,
("spec_testsuite", "globals") => true,
("spec_testsuite", "i32") => true,
("spec_testsuite", "i64") => true,
("spec_testsuite", "f32") => true,
("spec_testsuite", "f64") => true,
("spec_testsuite", "if") => true,
("spec_testsuite", "imports") => true,
("spec_testsuite", "int_exprs") => true,
("spec_testsuite", "linking") => true,
("spec_testsuite", "memory_grow") => true,
("spec_testsuite", "memory_trap") => true,
("spec_testsuite", "resizing") => true,
("spec_testsuite", "select") => true,
("spec_testsuite", "skip_stack_guard_page") => true,
("spec_testsuite", "start") => true,
("spec_testsuite", "traps") => true,
("spec_testsuite", "unreachable") => true,
("spec_testsuite", "unwind") => true,
("misc_testsuite", "misc_traps") => true,
("misc_testsuite", "stack_overflow") => true,
(_, _) => false, (_, _) => false,
}; };
} }
#[cfg(target_os = "linux")]
{
// Test whether the libc correctly parses the following constant; if so,
// we can run the "const" test. If not, the "const" test will fail, since
// we use wabt to parse the tests and wabt uses strtof.
extern "C" {
pub fn strtof(s: *const libc::c_char, endp: *mut *mut libc::c_char) -> libc::c_float;
}
if unsafe {
strtof(
b"8.8817847263968443574e-16" as *const u8 as *const libc::c_char,
core::ptr::null_mut(),
)
}
.to_bits()
!= 0x26800001
{
return match (testsuite, testname) {
("spec_testsuite", "const") => true,
("single_file_spec_test", "simd_const") => true,
(_, _) => false,
};
}
}
false false
} }

View File

@@ -1,3 +1,99 @@
# Embedding Wasmtime in Rust # Embedding Wasmtime in Rust
... more coming soon This document shows how to embed Wasmtime using the Rust API, and run a simple
wasm program.
# Create some wasm
Let's create a simple WebAssembly file with a single exported function that returns an integer:
```wat
(;; wat2wasm hello.wat -o $WASM_FILES/hello.wasm ;;)
(module
(func (export "answer") (result i32)
i32.const 42
)
)
```
# Create rust project
```
$ cargo new --bin wasmtime_hello
$ cd wasmtime_hello
$ cp $WASM_FILES/hello.wasm .
```
We will be using the wasmtime engine/API to run the wasm file, so we will add the dependency to `Cargo.toml`:
```
[dependencies]
wasmtime-api = { git = "https://github.com/CraneStation/wasmtime" }
```
It is time to add code to the `src/main.rs`. First, the engine and storage need to be activated:
```rust
use wasmtime_api::*;
let engine = HostRef::new(Engine::default());
let store = HostRef::new(Store::new(&engine));
```
The `HostRef` will be used a lot -- it is a "convenience" object to store and refer an object between the host and
the embedded environments.
The `hello.wasm` can be read from the file system and provided to the `Module` object constructor as `&[u8]`:
```rust
use std::fs::read;
let hello_wasm = read("hello.wasm").expect("wasm file");
let module = HostRef::new(Module::new(&store, &hello_wasm).expect("wasm module"));
```
The module instance can now be created. Normally, you would provide exports, but in this case, there is none required:
```rust
let instance = Instance::new(&store, &module, &[]).expect("wasm instance");
```
Everything is set. If a WebAssembly module has a start function -- it was run.
The instance's exports can be used at this point. This wasm file has only one export, so we can index it directly:
```rust
let answer_fn = instance.exports()[0].func().expect("answer function");
```
The exported function can be called using the `call` method. Remember that in most of the cases,
a `HostRef<_>` object will be returned, so `borrow()` or `borrow_mut()` method has to be used to refer the
specific object. The exported "answer" function accepts no parameters and returns a single `i32` value.
```rust
let result = answer_fn.borrow().call(&[]).expect("success");
println!("Answer: {}", result[0].i32());
```
The names of the WebAssembly module's imports and exports can be discovered by means of module's corresponding methods.
# src/main.rs
```rust
use std::fs::read;
use wasmtime_api::*;
fn main() {
let engine = HostRef::new(Engine::default());
let store = HostRef::new(Store::new(&engine));
let wasm = read("hello.wasm").expect("wasm file");
let module = HostRef::new(Module::new(&store, &wasm).expect("wasm module"));
let instance = Instance::new(&store, &module, &[]).expect("wasm instance");
let answer_fn = instance.exports()[0].func().expect("answer function");
let result = answer_fn.borrow().call(&[]).expect("success");
println!("Answer: {}", result[0].i32());
}
```

View File

@@ -1179,8 +1179,12 @@ where
sig!((ty) -> (ty)) sig!((ty) -> (ty))
} }
WasmOperator::GetGlobal { global_index } => sig!(() -> (self.module.global_type(*global_index).to_microwasm_type())), WasmOperator::GetGlobal { global_index } => {
WasmOperator::SetGlobal { global_index } => sig!((self.module.global_type(*global_index).to_microwasm_type()) -> ()), sig!(() -> (self.module.global_type(*global_index).to_microwasm_type()))
}
WasmOperator::SetGlobal { global_index } => {
sig!((self.module.global_type(*global_index).to_microwasm_type()) -> ())
}
WasmOperator::F32Load { .. } => sig!((I32) -> (F32)), WasmOperator::F32Load { .. } => sig!((I32) -> (F32)),
WasmOperator::F64Load { .. } => sig!((I32) -> (F64)), WasmOperator::F64Load { .. } => sig!((I32) -> (F64)),
@@ -1259,8 +1263,12 @@ where
| WasmOperator::F64Le | WasmOperator::F64Le
| WasmOperator::F64Ge => sig!((F64, F64) -> (I32)), | WasmOperator::F64Ge => sig!((F64, F64) -> (I32)),
WasmOperator::I32Clz | WasmOperator::I32Ctz | WasmOperator::I32Popcnt => sig!((I32) -> (I32)), WasmOperator::I32Clz | WasmOperator::I32Ctz | WasmOperator::I32Popcnt => {
WasmOperator::I64Clz | WasmOperator::I64Ctz | WasmOperator::I64Popcnt => sig!((I64) -> (I64)), sig!((I32) -> (I32))
}
WasmOperator::I64Clz | WasmOperator::I64Ctz | WasmOperator::I64Popcnt => {
sig!((I64) -> (I64))
}
WasmOperator::I32Add WasmOperator::I32Add
| WasmOperator::I32Sub | WasmOperator::I32Sub

View File

@@ -1027,18 +1027,18 @@ test_select!(select64, i64);
mod benches { mod benches {
extern crate test; extern crate test;
use super::{translate, wabt, FIBONACCI, FIBONACCI_OPT}; use super::{translate, FIBONACCI, FIBONACCI_OPT};
#[bench] #[bench]
fn bench_fibonacci_compile(b: &mut test::Bencher) { fn bench_fibonacci_compile(b: &mut test::Bencher) {
let wasm = wabt::wat2wasm(FIBONACCI).unwrap(); let wasm = wat::parse_str(FIBONACCI).unwrap();
b.iter(|| test::black_box(translate(&wasm).unwrap())); b.iter(|| test::black_box(translate(&wasm).unwrap()));
} }
#[bench] #[bench]
fn bench_fibonacci_run(b: &mut test::Bencher) { fn bench_fibonacci_run(b: &mut test::Bencher) {
let wasm = wabt::wat2wasm(FIBONACCI_OPT).unwrap(); let wasm = wat::parse_str(FIBONACCI_OPT).unwrap();
let module = translate(&wasm).unwrap(); let module = translate(&wasm).unwrap();
b.iter(|| module.execute_func::<_, u32>(0, (20,))); b.iter(|| module.execute_func::<_, u32>(0, (20,)));
@@ -1046,7 +1046,7 @@ mod benches {
#[bench] #[bench]
fn bench_fibonacci_compile_run(b: &mut test::Bencher) { fn bench_fibonacci_compile_run(b: &mut test::Bencher) {
let wasm = wabt::wat2wasm(FIBONACCI).unwrap(); let wasm = wat::parse_str(FIBONACCI).unwrap();
b.iter(|| translate(&wasm).unwrap().execute_func::<_, u32>(0, (20,))); b.iter(|| translate(&wasm).unwrap().execute_func::<_, u32>(0, (20,)));
} }

View File

@@ -16,7 +16,7 @@ use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_wasm::{DefinedFuncIndex, FuncIndex}; use cranelift_wasm::{DefinedFuncIndex, FuncIndex};
use target_lexicon::HOST; use target_lexicon::HOST;
use wasmtime_environ::{Export, Module}; use wasmtime_environ::{CompiledFunction, Export, Module};
use wasmtime_jit::CodeMemory; use wasmtime_jit::CodeMemory;
use wasmtime_runtime::{Imports, InstanceHandle, VMContext, VMFunctionBody}; use wasmtime_runtime::{Imports, InstanceHandle, VMContext, VMFunctionBody};
@@ -184,9 +184,16 @@ fn make_trampoline(
) )
.expect("compile_and_emit"); .expect("compile_and_emit");
let mut unwind_info = Vec::new();
context.emit_unwind_info(isa, &mut unwind_info);
code_memory code_memory
.allocate_copy_of_byte_slice(&code_buf) .allocate_for_function(&CompiledFunction {
.expect("allocate_copy_of_byte_slice") body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
})
.expect("allocate_for_function")
.as_ptr() .as_ptr()
} }

View File

@@ -270,14 +270,14 @@ fn main() -> Result<()> {
strategy, strategy,
); );
let engine = HostRef::new(Engine::new(config)); let engine = HostRef::new(Engine::new(config));
let store = HostRef::new(Store::new(engine)); let store = HostRef::new(Store::new(&engine));
let mut module_registry = HashMap::new(); let mut module_registry = HashMap::new();
// Make spectest available by default. // Make spectest available by default.
module_registry.insert( module_registry.insert(
"spectest".to_owned(), "spectest".to_owned(),
Instance::from_handle(store.clone(), instantiate_spectest()?)?, Instance::from_handle(&store, instantiate_spectest()?)?,
); );
// Make wasi available by default. // Make wasi available by default.
@@ -301,36 +301,36 @@ fn main() -> Result<()> {
module_registry.insert( module_registry.insert(
"wasi_unstable".to_owned(), "wasi_unstable".to_owned(),
Instance::from_handle(store.clone(), wasi.clone())?, Instance::from_handle(&store, wasi.clone())?,
); );
module_registry.insert( module_registry.insert(
"wasi_unstable_preview0".to_owned(), "wasi_unstable_preview0".to_owned(),
Instance::from_handle(store.clone(), wasi)?, Instance::from_handle(&store, wasi)?,
); );
// Load the preload wasm modules. // Load the preload wasm modules.
for filename in &args.flag_preload { for filename in &args.flag_preload {
let path = Path::new(&filename); let path = Path::new(&filename);
instantiate_module(store.clone(), &module_registry, path) instantiate_module(&store, &module_registry, path)
.with_context(|| format!("failed to process preload at `{}`", path.display()))?; .with_context(|| format!("failed to process preload at `{}`", path.display()))?;
} }
// Load the main wasm module. // Load the main wasm module.
let path = Path::new(&args.arg_file); let path = Path::new(&args.arg_file);
handle_module(store, &module_registry, &args, path) handle_module(&store, &module_registry, &args, path)
.with_context(|| format!("failed to process main module `{}`", path.display()))?; .with_context(|| format!("failed to process main module `{}`", path.display()))?;
Ok(()) Ok(())
} }
fn instantiate_module( fn instantiate_module(
store: HostRef<Store>, store: &HostRef<Store>,
module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>, module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>,
path: &Path, path: &Path,
) -> Result<(HostRef<Instance>, HostRef<Module>, Vec<u8>)> { ) -> Result<(HostRef<Instance>, HostRef<Module>, Vec<u8>)> {
// Read the wasm module binary either as `*.wat` or a raw binary // Read the wasm module binary either as `*.wat` or a raw binary
let data = wat::parse_file(path.to_path_buf())?; let data = wat::parse_file(path.to_path_buf())?;
let module = HostRef::new(Module::new(store.clone(), &data)?); let module = HostRef::new(Module::new(store, &data)?);
// Resolve import using module_registry. // Resolve import using module_registry.
let imports = module let imports = module
@@ -356,18 +356,18 @@ fn instantiate_module(
}) })
.collect::<Result<Vec<_>, _>>()?; .collect::<Result<Vec<_>, _>>()?;
let instance = HostRef::new(Instance::new(store.clone(), module.clone(), &imports)?); let instance = HostRef::new(Instance::new(store, &module, &imports)?);
Ok((instance, module, data)) Ok((instance, module, data))
} }
fn handle_module( fn handle_module(
store: HostRef<Store>, store: &HostRef<Store>,
module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>, module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>,
args: &Args, args: &Args,
path: &Path, path: &Path,
) -> Result<()> { ) -> Result<()> {
let (instance, _module, data) = instantiate_module(store.clone(), module_registry, path)?; let (instance, _module, data) = instantiate_module(store, module_registry, path)?;
// If a function to invoke was given, invoke it. // If a function to invoke was given, invoke it.
if let Some(f) = &args.flag_invoke { if let Some(f) = &args.flag_invoke {
@@ -379,7 +379,7 @@ fn handle_module(
} }
fn invoke_export( fn invoke_export(
store: HostRef<Store>, store: &HostRef<Store>,
instance: HostRef<Instance>, instance: HostRef<Instance>,
data: &ModuleData, data: &ModuleData,
name: &str, name: &str,

View File

@@ -9,7 +9,24 @@ use wasmtime_wast::WastContext;
include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs")); include!(concat!(env!("OUT_DIR"), "/wast_testsuite_tests.rs"));
#[cfg(test)] // Each of the tests included from `wast_testsuite_tests` will call this
// function which actually executes the `wast` test suite given the `strategy`
// to compile it.
fn run_wast(wast: &str, strategy: CompilationStrategy) -> anyhow::Result<()> {
let wast = Path::new(wast);
let isa = native_isa();
let compiler = Compiler::new(isa, strategy);
let features = Features {
simd: wast.iter().any(|s| s == "simd"),
multi_value: wast.iter().any(|s| s == "multi-value"),
..Default::default()
};
let mut wast_context = WastContext::new(Box::new(compiler)).with_features(features);
wast_context.register_spectest()?;
wast_context.run_file(wast)?;
Ok(())
}
fn native_isa() -> Box<dyn isa::TargetIsa> { fn native_isa() -> Box<dyn isa::TargetIsa> {
let mut flag_builder = settings::builder(); let mut flag_builder = settings::builder();
flag_builder.enable("enable_verifier").unwrap(); flag_builder.enable("enable_verifier").unwrap();

View File

@@ -30,7 +30,6 @@ hashbrown = { version = "0.6.0", optional = true }
[features] [features]
default = ["std"] default = ["std"]
std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std", "wasmparser/std"] std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std", "wasmparser/std"]
wasm-c-api = []
core = ["hashbrown/nightly", "cranelift-codegen/core", "cranelift-wasm/core", "wasmtime-environ/core", "wasmparser/core"] core = ["hashbrown/nightly", "cranelift-codegen/core", "cranelift-wasm/core", "wasmtime-environ/core", "wasmparser/core"]
[dev-dependencies] [dev-dependencies]

View File

@@ -46,9 +46,9 @@ WASM_CC_LIBS = $(error unsupported C++)
# Compiler config # Compiler config
ifeq (${WASMTIME_API_MODE},release) ifeq (${WASMTIME_API_MODE},release)
CARGO_BUILD_FLAGS = --features "wasm-c-api" --release CARGO_BUILD_FLAGS = --release
else else
CARGO_BUILD_FLAGS = --features "wasm-c-api" CARGO_BUILD_FLAGS =
endif endif
ifeq (${C_COMP},clang) ifeq (${C_COMP},clang)

View File

@@ -10,10 +10,10 @@ fn main() -> Result<()> {
// Instantiate engine and store. // Instantiate engine and store.
let engine = HostRef::new(Engine::default()); let engine = HostRef::new(Engine::default());
let store = HostRef::new(Store::new(engine)); let store = HostRef::new(Store::new(&engine));
// Load a module. // Load a module.
let module = HostRef::new(Module::new(store.clone(), &wasm)?); let module = HostRef::new(Module::new(&store, &wasm)?);
// Find index of the `gcd` export. // Find index of the `gcd` export.
let gcd_index = module let gcd_index = module
@@ -26,7 +26,7 @@ fn main() -> Result<()> {
.0; .0;
// Instantiate the module. // Instantiate the module.
let instance = HostRef::new(Instance::new(store.clone(), module, &[])?); let instance = HostRef::new(Instance::new(&store, &module, &[])?);
// Invoke `gcd` export // Invoke `gcd` export
let gcd = instance.borrow().exports()[gcd_index] let gcd = instance.borrow().exports()[gcd_index]

View File

@@ -21,8 +21,8 @@ impl Callable for HelloCallback {
fn main() -> Result<()> { fn main() -> Result<()> {
// Initialize. // Initialize.
println!("Initializing..."); println!("Initializing...");
let engine = HostRef::new(Engine::new(Config::default())); let engine = HostRef::new(Engine::default());
let store = HostRef::new(Store::new(engine)); let store = HostRef::new(Store::new(&engine));
// Load binary. // Load binary.
println!("Loading binary..."); println!("Loading binary...");
@@ -30,19 +30,18 @@ fn main() -> Result<()> {
// Compile. // Compile.
println!("Compiling module..."); println!("Compiling module...");
let module = let module = HostRef::new(Module::new(&store, &binary).context("> Error compiling module!")?);
HostRef::new(Module::new(store.clone(), &binary).context("> Error compiling module!")?);
// Create external print functions. // Create external print functions.
println!("Creating callback..."); println!("Creating callback...");
let hello_type = FuncType::new(Box::new([]), Box::new([])); let hello_type = FuncType::new(Box::new([]), Box::new([]));
let hello_func = HostRef::new(Func::new(store.clone(), hello_type, Rc::new(HelloCallback))); let hello_func = HostRef::new(Func::new(&store, hello_type, Rc::new(HelloCallback)));
// Instantiate. // Instantiate.
println!("Instantiating module..."); println!("Instantiating module...");
let imports = vec![hello_func.into()]; let imports = vec![hello_func.into()];
let instance = HostRef::new( let instance = HostRef::new(
Instance::new(store.clone(), module, imports.as_slice()) Instance::new(&store, &module, imports.as_slice())
.context("> Error instantiating module!")?, .context("> Error instantiating module!")?,
); );

View File

@@ -64,8 +64,8 @@ macro_rules! call {
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
// Initialize. // Initialize.
println!("Initializing..."); println!("Initializing...");
let engine = HostRef::new(Engine::new(Config::default())); let engine = HostRef::new(Engine::default());
let store = HostRef::new(Store::new(engine)); let store = HostRef::new(Store::new(&engine));
// Load binary. // Load binary.
println!("Loading binary..."); println!("Loading binary...");
@@ -73,14 +73,12 @@ fn main() -> Result<(), Error> {
// Compile. // Compile.
println!("Compiling module..."); println!("Compiling module...");
let module = let module = HostRef::new(Module::new(&store, &binary).context("> Error compiling module!")?);
HostRef::new(Module::new(store.clone(), &binary).context("> Error compiling module!")?);
// Instantiate. // Instantiate.
println!("Instantiating module..."); println!("Instantiating module...");
let instance = HostRef::new( let instance =
Instance::new(store.clone(), module, &[]).context("> Error instantiating module!")?, HostRef::new(Instance::new(&store, &module, &[]).context("> Error instantiating module!")?);
);
// Extract export. // Extract export.
println!("Extracting export..."); println!("Extracting export...");
@@ -141,7 +139,7 @@ fn main() -> Result<(), Error> {
// TODO(wasm+): Once Wasm allows multiple memories, turn this into import. // TODO(wasm+): Once Wasm allows multiple memories, turn this into import.
println!("Creating stand-alone memory..."); println!("Creating stand-alone memory...");
let memorytype = MemoryType::new(Limits::new(5, 5)); let memorytype = MemoryType::new(Limits::new(5, 5));
let mut memory2 = Memory::new(store.clone(), memorytype); let mut memory2 = Memory::new(&store, memorytype);
check!(memory2.size(), 5u32); check!(memory2.size(), 5u32);
check!(memory2.grow(1), false); check!(memory2.grow(1), false);
check!(memory2.grow(0), true); check!(memory2.grow(0), true);

View File

@@ -24,8 +24,8 @@ impl Callable for Callback {
fn main() -> Result<()> { fn main() -> Result<()> {
// Initialize. // Initialize.
println!("Initializing..."); println!("Initializing...");
let engine = HostRef::new(Engine::new(Config::default())); let engine = HostRef::new(Engine::default());
let store = HostRef::new(Store::new(engine)); let store = HostRef::new(Store::new(&engine));
// Load binary. // Load binary.
println!("Loading binary..."); println!("Loading binary...");
@@ -33,8 +33,7 @@ fn main() -> Result<()> {
// Compile. // Compile.
println!("Compiling module..."); println!("Compiling module...");
let module = let module = HostRef::new(Module::new(&store, &binary).context("Error compiling module!")?);
HostRef::new(Module::new(store.clone(), &binary).context("Error compiling module!")?);
// Create external print functions. // Create external print functions.
println!("Creating callback..."); println!("Creating callback...");
@@ -42,13 +41,13 @@ fn main() -> Result<()> {
Box::new([ValType::I32, ValType::I64]), Box::new([ValType::I32, ValType::I64]),
Box::new([ValType::I64, ValType::I32]), Box::new([ValType::I64, ValType::I32]),
); );
let callback_func = HostRef::new(Func::new(store.clone(), callback_type, Rc::new(Callback))); let callback_func = HostRef::new(Func::new(&store, callback_type, Rc::new(Callback)));
// Instantiate. // Instantiate.
println!("Instantiating module..."); println!("Instantiating module...");
let imports = vec![callback_func.into()]; let imports = vec![callback_func.into()];
let instance = HostRef::new( let instance = HostRef::new(
Instance::new(store.clone(), module, imports.as_slice()) Instance::new(&store, &module, imports.as_slice())
.context("Error instantiating module!")?, .context("Error instantiating module!")?,
); );

View File

@@ -34,9 +34,9 @@ pub(crate) struct WasmtimeFn {
} }
impl WasmtimeFn { impl WasmtimeFn {
pub fn new(store: HostRef<Store>, instance: InstanceHandle, export: Export) -> WasmtimeFn { pub fn new(store: &HostRef<Store>, instance: InstanceHandle, export: Export) -> WasmtimeFn {
WasmtimeFn { WasmtimeFn {
store, store: store.clone(),
instance, instance,
export, export,
} }

View File

@@ -66,7 +66,7 @@ impl Extern {
} }
pub(crate) fn from_wasmtime_export( pub(crate) fn from_wasmtime_export(
store: HostRef<Store>, store: &HostRef<Store>,
instance_handle: InstanceHandle, instance_handle: InstanceHandle,
export: wasmtime_runtime::Export, export: wasmtime_runtime::Export,
) -> Extern { ) -> Extern {
@@ -118,18 +118,18 @@ pub struct Func {
} }
impl Func { impl Func {
pub fn new(store: HostRef<Store>, ty: FuncType, callable: Rc<dyn Callable + 'static>) -> Self { pub fn new(store: &HostRef<Store>, ty: FuncType, callable: Rc<dyn Callable + 'static>) -> Self {
let callable = Rc::new(NativeCallable::new(callable, &ty, &store)); let callable = Rc::new(NativeCallable::new(callable, &ty, &store));
Func::from_wrapped(store, ty, callable) Func::from_wrapped(store, ty, callable)
} }
fn from_wrapped( fn from_wrapped(
store: HostRef<Store>, store: &HostRef<Store>,
r#type: FuncType, r#type: FuncType,
callable: Rc<dyn WrappedCallable + 'static>, callable: Rc<dyn WrappedCallable + 'static>,
) -> Func { ) -> Func {
Func { Func {
_store: store, _store: store.clone(),
callable, callable,
r#type, r#type,
} }
@@ -159,7 +159,7 @@ impl Func {
pub(crate) fn from_wasmtime_function( pub(crate) fn from_wasmtime_function(
export: wasmtime_runtime::Export, export: wasmtime_runtime::Export,
store: HostRef<Store>, store: &HostRef<Store>,
instance_handle: InstanceHandle, instance_handle: InstanceHandle,
) -> Self { ) -> Self {
let ty = if let wasmtime_runtime::Export::Function { signature, .. } = &export { let ty = if let wasmtime_runtime::Export::Function { signature, .. } = &export {
@@ -167,7 +167,7 @@ impl Func {
} else { } else {
panic!("expected function export") panic!("expected function export")
}; };
let callable = WasmtimeFn::new(store.clone(), instance_handle, export.clone()); let callable = WasmtimeFn::new(store, instance_handle, export.clone());
Func::from_wrapped(store, ty, Rc::new(callable)) Func::from_wrapped(store, ty, Rc::new(callable))
} }
} }
@@ -187,11 +187,11 @@ pub struct Global {
} }
impl Global { impl Global {
pub fn new(store: HostRef<Store>, r#type: GlobalType, val: Val) -> Global { pub fn new(store: &HostRef<Store>, r#type: GlobalType, val: Val) -> Global {
let (wasmtime_export, wasmtime_state) = let (wasmtime_export, wasmtime_state) =
generate_global_export(&r#type, val).expect("generated global"); generate_global_export(&r#type, val).expect("generated global");
Global { Global {
_store: store, _store: store.clone(),
r#type, r#type,
wasmtime_export, wasmtime_export,
wasmtime_state: Some(wasmtime_state), wasmtime_state: Some(wasmtime_state),
@@ -248,7 +248,7 @@ impl Global {
pub(crate) fn from_wasmtime_global( pub(crate) fn from_wasmtime_global(
export: wasmtime_runtime::Export, export: wasmtime_runtime::Export,
store: HostRef<Store>, store: &HostRef<Store>,
) -> Global { ) -> Global {
let global = if let wasmtime_runtime::Export::Global { ref global, .. } = export { let global = if let wasmtime_runtime::Export::Global { ref global, .. } = export {
global global
@@ -257,7 +257,7 @@ impl Global {
}; };
let ty = GlobalType::from_cranelift_global(global.clone()); let ty = GlobalType::from_cranelift_global(global.clone());
Global { Global {
_store: store, _store: store.clone(),
r#type: ty, r#type: ty,
wasmtime_export: export, wasmtime_export: export,
wasmtime_state: None, wasmtime_state: None,
@@ -302,7 +302,7 @@ fn set_table_item(
} }
impl Table { impl Table {
pub fn new(store: HostRef<Store>, r#type: TableType, init: Val) -> Table { pub fn new(store: &HostRef<Store>, r#type: TableType, init: Val) -> Table {
match r#type.element() { match r#type.element() {
ValType::FuncRef => (), ValType::FuncRef => (),
_ => panic!("table is not for funcref"), _ => panic!("table is not for funcref"),
@@ -317,7 +317,7 @@ impl Table {
let len = unsafe { (*definition).current_elements }; let len = unsafe { (*definition).current_elements };
for i in 0..len { for i in 0..len {
let _success = let _success =
set_table_item(&mut wasmtime_handle, &store, index, i, init.clone()); set_table_item(&mut wasmtime_handle, store, index, i, init.clone());
assert!(_success); assert!(_success);
} }
} }
@@ -325,7 +325,7 @@ impl Table {
} }
Table { Table {
store, store: store.clone(),
r#type, r#type,
wasmtime_handle, wasmtime_handle,
wasmtime_export, wasmtime_export,
@@ -387,7 +387,7 @@ impl Table {
pub(crate) fn from_wasmtime_table( pub(crate) fn from_wasmtime_table(
export: wasmtime_runtime::Export, export: wasmtime_runtime::Export,
store: HostRef<Store>, store: &HostRef<Store>,
instance_handle: wasmtime_runtime::InstanceHandle, instance_handle: wasmtime_runtime::InstanceHandle,
) -> Table { ) -> Table {
let table = if let wasmtime_runtime::Export::Table { ref table, .. } = export { let table = if let wasmtime_runtime::Export::Table { ref table, .. } = export {
@@ -397,7 +397,7 @@ impl Table {
}; };
let ty = TableType::from_cranelift_table(table.table.clone()); let ty = TableType::from_cranelift_table(table.table.clone());
Table { Table {
store, store: store.clone(),
r#type: ty, r#type: ty,
wasmtime_handle: instance_handle, wasmtime_handle: instance_handle,
wasmtime_export: export, wasmtime_export: export,
@@ -413,11 +413,11 @@ pub struct Memory {
} }
impl Memory { impl Memory {
pub fn new(store: HostRef<Store>, r#type: MemoryType) -> Memory { pub fn new(store: &HostRef<Store>, r#type: MemoryType) -> Memory {
let (wasmtime_handle, wasmtime_export) = let (wasmtime_handle, wasmtime_export) =
generate_memory_export(&r#type).expect("generated memory"); generate_memory_export(&r#type).expect("generated memory");
Memory { Memory {
_store: store, _store: store.clone(),
r#type, r#type,
wasmtime_handle, wasmtime_handle,
wasmtime_export, wasmtime_export,
@@ -471,7 +471,7 @@ impl Memory {
pub(crate) fn from_wasmtime_memory( pub(crate) fn from_wasmtime_memory(
export: wasmtime_runtime::Export, export: wasmtime_runtime::Export,
store: HostRef<Store>, store: &HostRef<Store>,
instance_handle: wasmtime_runtime::InstanceHandle, instance_handle: wasmtime_runtime::InstanceHandle,
) -> Memory { ) -> Memory {
let memory = if let wasmtime_runtime::Export::Memory { ref memory, .. } = export { let memory = if let wasmtime_runtime::Export::Memory { ref memory, .. } = export {
@@ -481,7 +481,7 @@ impl Memory {
}; };
let ty = MemoryType::from_cranelift_memory(memory.memory.clone()); let ty = MemoryType::from_cranelift_memory(memory.memory.clone());
Memory { Memory {
_store: store, _store: store.clone(),
r#type: ty, r#type: ty,
wasmtime_handle: instance_handle, wasmtime_handle: instance_handle,
wasmtime_export: export, wasmtime_export: export,

View File

@@ -61,8 +61,8 @@ pub struct Instance {
impl Instance { impl Instance {
pub fn new( pub fn new(
store: HostRef<Store>, store: &HostRef<Store>,
module: HostRef<Module>, module: &HostRef<Module>,
externs: &[Extern], externs: &[Extern],
) -> Result<Instance> { ) -> Result<Instance> {
let context = store.borrow_mut().context().clone(); let context = store.borrow_mut().context().clone();
@@ -84,7 +84,7 @@ impl Instance {
let name = export.name().to_string(); let name = export.name().to_string();
let export = instance_handle.lookup(&name).expect("export"); let export = instance_handle.lookup(&name).expect("export");
exports.push(Extern::from_wasmtime_export( exports.push(Extern::from_wasmtime_export(
store.clone(), store,
instance_handle.clone(), instance_handle.clone(),
export, export,
)); ));
@@ -103,7 +103,7 @@ impl Instance {
} }
pub fn from_handle( pub fn from_handle(
store: HostRef<Store>, store: &HostRef<Store>,
instance_handle: InstanceHandle, instance_handle: InstanceHandle,
) -> Result<(Instance, HashMap<String, usize>)> { ) -> Result<(Instance, HashMap<String, usize>)> {
let contexts = HashSet::new(); let contexts = HashSet::new();
@@ -121,7 +121,7 @@ impl Instance {
} }
export_names_map.insert(name.to_owned(), exports.len()); export_names_map.insert(name.to_owned(), exports.len());
exports.push(Extern::from_wasmtime_export( exports.push(Extern::from_wasmtime_export(
store.clone(), store,
instance_handle.clone(), instance_handle.clone(),
export.clone(), export.clone(),
)); ));

View File

@@ -14,7 +14,6 @@ mod trap;
mod types; mod types;
mod values; mod values;
#[cfg(feature = "wasm-c-api")]
pub mod wasm; pub mod wasm;
#[macro_use] #[macro_use]

View File

@@ -182,10 +182,10 @@ pub struct Module {
} }
impl Module { impl Module {
pub fn new(store: HostRef<Store>, binary: &[u8]) -> Result<Module> { pub fn new(store: &HostRef<Store>, binary: &[u8]) -> Result<Module> {
let (imports, exports) = read_imports_and_exports(binary)?; let (imports, exports) = read_imports_and_exports(binary)?;
Ok(Module { Ok(Module {
store, store: store.clone(),
binary: binary.into(), binary: binary.into(),
imports, imports,
exports, exports,

View File

@@ -102,13 +102,13 @@ pub struct Store {
} }
impl Store { impl Store {
pub fn new(engine: HostRef<Engine>) -> Store { pub fn new(engine: &HostRef<Engine>) -> Store {
let flags = engine.borrow().config().flags().clone(); let flags = engine.borrow().config().flags().clone();
let features = engine.borrow().config().features().clone(); let features = engine.borrow().config().features().clone();
let debug_info = engine.borrow().config().debug_info(); let debug_info = engine.borrow().config().debug_info();
let strategy = engine.borrow().config().strategy(); let strategy = engine.borrow().config().strategy();
Store { Store {
engine, engine: engine.clone(),
context: Context::create(flags, features, debug_info, strategy), context: Context::create(flags, features, debug_info, strategy),
global_exports: Rc::new(RefCell::new(HashMap::new())), global_exports: Rc::new(RefCell::new(HashMap::new())),
signature_cache: HashMap::new(), signature_cache: HashMap::new(),

View File

@@ -9,8 +9,7 @@ use cranelift_codegen::{binemit, ir, isa};
use cranelift_entity::{EntityRef, PrimaryMap}; use cranelift_entity::{EntityRef, PrimaryMap};
use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext}; use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_wasm::{DefinedFuncIndex, FuncIndex}; use cranelift_wasm::{DefinedFuncIndex, FuncIndex};
//use target_lexicon::HOST; use wasmtime_environ::{CompiledFunction, Export, Module};
use wasmtime_environ::{Export, Module};
use wasmtime_jit::CodeMemory; use wasmtime_jit::CodeMemory;
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody}; use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody};
@@ -185,9 +184,16 @@ fn make_trampoline(
) )
.expect("compile_and_emit"); .expect("compile_and_emit");
let mut unwind_info = Vec::new();
context.emit_unwind_info(isa, &mut unwind_info);
code_memory code_memory
.allocate_copy_of_byte_slice(&code_buf) .allocate_for_function(&CompiledFunction {
.expect("allocate_copy_of_byte_slice") body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
})
.expect("allocate_for_function")
.as_ptr() .as_ptr()
} }

View File

@@ -230,6 +230,6 @@ pub(crate) fn from_checked_anyfunc(
signature, signature,
vmctx: item.vmctx, vmctx: item.vmctx,
}; };
let f = Func::from_wasmtime_function(export, store.clone(), instance_handle); let f = Func::from_wasmtime_function(export, store, instance_handle);
Val::FuncRef(HostRef::new(f)) Val::FuncRef(HostRef::new(f))
} }

View File

@@ -609,7 +609,7 @@ pub unsafe extern "C" fn wasm_func_new(
ty: *const wasm_functype_t, ty: *const wasm_functype_t,
callback: wasm_func_callback_t, callback: wasm_func_callback_t,
) -> *mut wasm_func_t { ) -> *mut wasm_func_t {
let store = (*store).store.clone(); let store = &(*store).store;
let ty = (*ty).functype.clone(); let ty = (*ty).functype.clone();
let callback = Rc::new(callback); let callback = Rc::new(callback);
let func = Box::new(wasm_func_t { let func = Box::new(wasm_func_t {
@@ -663,13 +663,13 @@ pub unsafe extern "C" fn wasm_instance_new(
imports: *const *const wasm_extern_t, imports: *const *const wasm_extern_t,
result: *mut *mut wasm_trap_t, result: *mut *mut wasm_trap_t,
) -> *mut wasm_instance_t { ) -> *mut wasm_instance_t {
let store = (*store).store.clone(); let store = &(*store).store;
let mut externs: Vec<Extern> = Vec::with_capacity((*module).imports.len()); let mut externs: Vec<Extern> = Vec::with_capacity((*module).imports.len());
for i in 0..(*module).imports.len() { for i in 0..(*module).imports.len() {
let import = *imports.offset(i as isize); let import = *imports.offset(i as isize);
externs.push((*import).ext.clone()); externs.push((*import).ext.clone());
} }
let module = (*module).module.clone(); let module = &(*module).module;
match Instance::new(store, module, &externs) { match Instance::new(store, module, &externs) {
Ok(instance) => { Ok(instance) => {
let instance = Box::new(wasm_instance_t { let instance = Box::new(wasm_instance_t {
@@ -731,7 +731,7 @@ pub unsafe extern "C" fn wasm_module_new(
binary: *const wasm_byte_vec_t, binary: *const wasm_byte_vec_t,
) -> *mut wasm_module_t { ) -> *mut wasm_module_t {
let binary = (*binary).as_slice(); let binary = (*binary).as_slice();
let store = (*store).store.clone(); let store = &(*store).store;
let module = Module::new(store, binary).expect("module"); let module = Module::new(store, binary).expect("module");
let imports = module let imports = module
.imports() .imports()
@@ -766,9 +766,9 @@ pub unsafe extern "C" fn wasm_store_delete(store: *mut wasm_store_t) {
#[no_mangle] #[no_mangle]
pub unsafe extern "C" fn wasm_store_new(engine: *mut wasm_engine_t) -> *mut wasm_store_t { pub unsafe extern "C" fn wasm_store_new(engine: *mut wasm_engine_t) -> *mut wasm_store_t {
let engine = (*engine).engine.clone(); let engine = &(*engine).engine;
let store = Box::new(wasm_store_t { let store = Box::new(wasm_store_t {
store: HostRef::new(Store::new(engine)), store: HostRef::new(Store::new(&engine)),
}); });
Box::into_raw(store) Box::into_raw(store)
} }
@@ -804,7 +804,7 @@ pub unsafe extern "C" fn wasm_func_new_with_env(
env: *mut ::core::ffi::c_void, env: *mut ::core::ffi::c_void,
finalizer: ::core::option::Option<unsafe extern "C" fn(arg1: *mut ::core::ffi::c_void)>, finalizer: ::core::option::Option<unsafe extern "C" fn(arg1: *mut ::core::ffi::c_void)>,
) -> *mut wasm_func_t { ) -> *mut wasm_func_t {
let store = (*store).store.clone(); let store = &(*store).store;
let ty = (*ty).functype.clone(); let ty = (*ty).functype.clone();
let callback = Rc::new(CallbackWithEnv { let callback = Rc::new(CallbackWithEnv {
callback, callback,
@@ -1327,7 +1327,7 @@ pub unsafe extern "C" fn wasm_global_new(
val: *const wasm_val_t, val: *const wasm_val_t,
) -> *mut wasm_global_t { ) -> *mut wasm_global_t {
let global = HostRef::new(Global::new( let global = HostRef::new(Global::new(
(*store).store.clone(), &(*store).store,
(*gt).globaltype.clone(), (*gt).globaltype.clone(),
(*val).val(), (*val).val(),
)); ));
@@ -1446,10 +1446,7 @@ pub unsafe extern "C" fn wasm_memory_new(
store: *mut wasm_store_t, store: *mut wasm_store_t,
mt: *const wasm_memorytype_t, mt: *const wasm_memorytype_t,
) -> *mut wasm_memory_t { ) -> *mut wasm_memory_t {
let memory = HostRef::new(Memory::new( let memory = HostRef::new(Memory::new(&(*store).store, (*mt).memorytype.clone()));
(*store).store.clone(),
(*mt).memorytype.clone(),
));
let m = Box::new(wasm_memory_t { memory, ext: None }); let m = Box::new(wasm_memory_t { memory, ext: None });
Box::into_raw(m) Box::into_raw(m)
} }
@@ -1537,11 +1534,7 @@ pub unsafe extern "C" fn wasm_table_new(
Val::AnyRef(AnyRef::Null) Val::AnyRef(AnyRef::Null)
}; };
let t = Box::new(wasm_table_t { let t = Box::new(wasm_table_t {
table: HostRef::new(Table::new( table: HostRef::new(Table::new(&(*store).store, (*tt).tabletype.clone(), init)),
(*store).store.clone(),
(*tt).tabletype.clone(),
init,
)),
ext: None, ext: None,
}); });
Box::into_raw(t) Box::into_raw(t)

View File

@@ -25,10 +25,10 @@ fn test_import_calling_export() {
} }
let engine = HostRef::new(Engine::new(Config::default())); let engine = HostRef::new(Engine::new(Config::default()));
let store = HostRef::new(Store::new(engine)); let store = HostRef::new(Store::new(&engine));
let module = HostRef::new( let module = HostRef::new(
Module::new( Module::new(
store.clone(), &store,
&read("tests/import_calling_export.wasm").expect("failed to read wasm file"), &read("tests/import_calling_export.wasm").expect("failed to read wasm file"),
) )
.expect("failed to create module"), .expect("failed to create module"),
@@ -39,15 +39,14 @@ fn test_import_calling_export() {
}); });
let callback_func = HostRef::new(Func::new( let callback_func = HostRef::new(Func::new(
store.clone(), &store,
FuncType::new(Box::new([]), Box::new([])), FuncType::new(Box::new([]), Box::new([])),
callback.clone(), callback.clone(),
)); ));
let imports = vec![callback_func.into()]; let imports = vec![callback_func.into()];
let instance = HostRef::new( let instance = HostRef::new(
Instance::new(store.clone(), module, imports.as_slice()) Instance::new(&store, &module, imports.as_slice()).expect("failed to instantiate module"),
.expect("failed to instantiate module"),
); );
let exports = Ref::map(instance.borrow(), |instance| instance.exports()); let exports = Ref::map(instance.borrow(), |instance| instance.exports());

View File

@@ -1,7 +1,7 @@
use super::config::tests::test_prolog; use super::config::tests::test_prolog;
use super::*; use super::*;
use crate::address_map::{FunctionAddressMap, InstructionAddressMap}; use crate::address_map::{FunctionAddressMap, InstructionAddressMap};
use crate::compilation::{CodeAndJTOffsets, Relocation, RelocationTarget, TrapInformation}; use crate::compilation::{CompiledFunction, Relocation, RelocationTarget, TrapInformation};
use crate::module::{MemoryPlan, MemoryStyle, Module}; use crate::module::{MemoryPlan, MemoryStyle, Module};
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::vec::Vec; use alloc::vec::Vec;
@@ -258,9 +258,10 @@ fn new_module_cache_data(rng: &mut impl Rng) -> ModuleCacheData {
*v = (j as u32) * 3 / 4 *v = (j as u32) * 3 / 4
} }
}); });
CodeAndJTOffsets { CompiledFunction {
body: (0..(i * 3 / 2)).collect(), body: (0..(i * 3 / 2)).collect(),
jt_offsets: sm, jt_offsets: sm,
unwind_info: (0..(i * 3 / 2)).collect(),
} }
}) })
.collect(); .collect();

View File

@@ -12,17 +12,20 @@ use serde::{Deserialize, Serialize};
use std::ops::Range; use std::ops::Range;
use thiserror::Error; use thiserror::Error;
/// Compiled machine code: body and jump table offsets. /// Compiled function: machine code body, jump table offsets, and unwind information.
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
pub struct CodeAndJTOffsets { pub struct CompiledFunction {
/// The function body. /// The function body.
pub body: Vec<u8>, pub body: Vec<u8>,
/// The jump tables offsets (in the body). /// The jump tables offsets (in the body).
pub jt_offsets: ir::JumpTableOffsets, pub jt_offsets: ir::JumpTableOffsets,
/// The unwind information.
pub unwind_info: Vec<u8>,
} }
type Functions = PrimaryMap<DefinedFuncIndex, CodeAndJTOffsets>; type Functions = PrimaryMap<DefinedFuncIndex, CompiledFunction>;
/// The result of compiling a WebAssembly module's functions. /// The result of compiling a WebAssembly module's functions.
#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)] #[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
@@ -40,21 +43,22 @@ impl Compilation {
/// Allocates the compilation result with the given function bodies. /// Allocates the compilation result with the given function bodies.
pub fn from_buffer( pub fn from_buffer(
buffer: Vec<u8>, buffer: Vec<u8>,
functions: impl IntoIterator<Item = (Range<usize>, ir::JumpTableOffsets)>, functions: impl IntoIterator<Item = (Range<usize>, ir::JumpTableOffsets, Range<usize>)>,
) -> Self { ) -> Self {
Self::new( Self::new(
functions functions
.into_iter() .into_iter()
.map(|(range, jt_offsets)| CodeAndJTOffsets { .map(|(body_range, jt_offsets, unwind_range)| CompiledFunction {
body: buffer[range].to_vec(), body: buffer[body_range].to_vec(),
jt_offsets, jt_offsets,
unwind_info: buffer[unwind_range].to_vec(),
}) })
.collect(), .collect(),
) )
} }
/// Gets the bytes of a single function /// Gets the bytes of a single function
pub fn get(&self, func: DefinedFuncIndex) -> &CodeAndJTOffsets { pub fn get(&self, func: DefinedFuncIndex) -> &CompiledFunction {
&self.functions[func] &self.functions[func]
} }
@@ -67,7 +71,7 @@ impl Compilation {
pub fn get_jt_offsets(&self) -> PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets> { pub fn get_jt_offsets(&self) -> PrimaryMap<DefinedFuncIndex, ir::JumpTableOffsets> {
self.functions self.functions
.iter() .iter()
.map(|(_, code_and_jt)| code_and_jt.jt_offsets.clone()) .map(|(_, func)| func.jt_offsets.clone())
.collect::<PrimaryMap<DefinedFuncIndex, _>>() .collect::<PrimaryMap<DefinedFuncIndex, _>>()
} }
} }
@@ -88,7 +92,7 @@ pub struct Iter<'a> {
} }
impl<'a> Iterator for Iter<'a> { impl<'a> Iterator for Iter<'a> {
type Item = &'a CodeAndJTOffsets; type Item = &'a CompiledFunction;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.iterator.next().map(|(_, b)| b) self.iterator.next().map(|(_, b)| b)

View File

@@ -5,7 +5,7 @@ use crate::address_map::{
}; };
use crate::cache::{ModuleCacheData, ModuleCacheEntry}; use crate::cache::{ModuleCacheData, ModuleCacheEntry};
use crate::compilation::{ use crate::compilation::{
CodeAndJTOffsets, Compilation, CompileError, Relocation, RelocationTarget, Relocations, Compilation, CompileError, CompiledFunction, Relocation, RelocationTarget, Relocations,
TrapInformation, Traps, TrapInformation, Traps,
}; };
use crate::func_environ::{ use crate::func_environ::{
@@ -235,6 +235,7 @@ impl crate::compilation::Compiler for Cranelift {
)?; )?;
let mut code_buf: Vec<u8> = Vec::new(); let mut code_buf: Vec<u8> = Vec::new();
let mut unwind_info = Vec::new();
let mut reloc_sink = RelocSink::new(func_index); let mut reloc_sink = RelocSink::new(func_index);
let mut trap_sink = TrapSink::new(); let mut trap_sink = TrapSink::new();
let mut stackmap_sink = binemit::NullStackmapSink {}; let mut stackmap_sink = binemit::NullStackmapSink {};
@@ -246,7 +247,7 @@ impl crate::compilation::Compiler for Cranelift {
&mut stackmap_sink, &mut stackmap_sink,
)?; )?;
let jt_offsets = context.func.jt_offsets.clone(); context.emit_unwind_info(isa, &mut unwind_info);
let address_transform = if generate_debug_info { let address_transform = if generate_debug_info {
let body_len = code_buf.len(); let body_len = code_buf.len();
@@ -261,16 +262,15 @@ impl crate::compilation::Compiler for Cranelift {
None None
}; };
let stack_slots = context.func.stack_slots.clone();
Ok(( Ok((
code_buf, code_buf,
jt_offsets, context.func.jt_offsets,
reloc_sink.func_relocs, reloc_sink.func_relocs,
address_transform, address_transform,
ranges, ranges,
stack_slots, context.func.stack_slots,
trap_sink.traps, trap_sink.traps,
unwind_info,
)) ))
}, },
) )
@@ -285,10 +285,12 @@ impl crate::compilation::Compiler for Cranelift {
ranges, ranges,
sss, sss,
function_traps, function_traps,
unwind_info,
)| { )| {
functions.push(CodeAndJTOffsets { functions.push(CompiledFunction {
body: function, body: function,
jt_offsets: func_jt_offsets, jt_offsets: func_jt_offsets,
unwind_info,
}); });
relocations.push(relocs); relocations.push(relocs);
if let Some(address_transform) = address_transform { if let Some(address_transform) = address_transform {

View File

@@ -46,8 +46,8 @@ pub use crate::address_map::{
}; };
pub use crate::cache::{create_new_config as cache_create_new_config, init as cache_init}; pub use crate::cache::{create_new_config as cache_create_new_config, init as cache_init};
pub use crate::compilation::{ pub use crate::compilation::{
Compilation, CompileError, Compiler, Relocation, RelocationTarget, Relocations, Compilation, CompileError, CompiledFunction, Compiler, Relocation, RelocationTarget,
TrapInformation, Traps, Relocations, TrapInformation, Traps,
}; };
pub use crate::cranelift::Cranelift; pub use crate::cranelift::Cranelift;
pub use crate::func_environ::BuiltinFunctionIndex; pub use crate::func_environ::BuiltinFunctionIndex;

View File

@@ -65,10 +65,11 @@ impl crate::compilation::Compiler for Lightbeam {
// TODO pass jump table offsets to Compilation::from_buffer() when they // TODO pass jump table offsets to Compilation::from_buffer() when they
// are implemented in lightbeam -- using empty set of offsets for now. // are implemented in lightbeam -- using empty set of offsets for now.
// TODO: pass an empty range for the unwind information until lightbeam emits it
let code_section_ranges_and_jt = code_section let code_section_ranges_and_jt = code_section
.funcs() .funcs()
.into_iter() .into_iter()
.map(|r| (r, SecondaryMap::new())); .map(|r| (r, SecondaryMap::new(), 0..0));
Ok(( Ok((
Compilation::from_buffer(code_section.buffer().to_vec(), code_section_ranges_and_jt), Compilation::from_buffer(code_section.buffer().to_vec(), code_section_ranges_and_jt),

View File

@@ -25,6 +25,9 @@ target-lexicon = { version = "0.9.0", default-features = false }
hashbrown = { version = "0.6.0", optional = true } hashbrown = { version = "0.6.0", optional = true }
wasmparser = { version = "0.39.2", default-features = false } wasmparser = { version = "0.39.2", default-features = false }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.7", features = ["winnt", "impl-default"] }
[features] [features]
default = ["std"] default = ["std"]
std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std", "wasmtime-debug/std", "wasmtime-runtime/std", "wasmparser/std"] std = ["cranelift-codegen/std", "cranelift-wasm/std", "wasmtime-environ/std", "wasmtime-debug/std", "wasmtime-runtime/std", "wasmparser/std"]

View File

@@ -1,16 +1,18 @@
//! Memory management for executable code. //! Memory management for executable code.
use crate::function_table::FunctionTable;
use alloc::boxed::Box; use alloc::boxed::Box;
use alloc::string::String; use alloc::string::String;
use alloc::vec::Vec; use alloc::vec::Vec;
use core::{cmp, mem}; use core::{cmp, mem};
use region; use region;
use wasmtime_environ::{Compilation, CompiledFunction};
use wasmtime_runtime::{Mmap, VMFunctionBody}; use wasmtime_runtime::{Mmap, VMFunctionBody};
/// Memory manager for executable code. /// Memory manager for executable code.
pub struct CodeMemory { pub struct CodeMemory {
current: Mmap, current: (Mmap, FunctionTable),
mmaps: Vec<Mmap>, mmaps: Vec<(Mmap, FunctionTable)>,
position: usize, position: usize,
published: usize, published: usize,
} }
@@ -19,30 +21,144 @@ impl CodeMemory {
/// Create a new `CodeMemory` instance. /// Create a new `CodeMemory` instance.
pub fn new() -> Self { pub fn new() -> Self {
Self { Self {
current: Mmap::new(), current: (Mmap::new(), FunctionTable::new()),
mmaps: Vec::new(), mmaps: Vec::new(),
position: 0, position: 0,
published: 0, published: 0,
} }
} }
/// Allocate a continuous memory block for a single compiled function.
/// TODO: Reorganize the code that calls this to emit code directly into the
/// mmap region rather than into a Vec that we need to copy in.
pub fn allocate_for_function(
&mut self,
func: &CompiledFunction,
) -> Result<&mut [VMFunctionBody], String> {
let size = Self::function_allocation_size(func);
let start = self.position as u32;
let (buf, table) = self.allocate(size)?;
let (_, _, _, vmfunc) = Self::copy_function(func, start, buf, table);
Ok(vmfunc)
}
/// Allocate a continuous memory block for a compilation.
///
/// Allocates memory for both the function bodies as well as function unwind data.
pub fn allocate_for_compilation(
&mut self,
compilation: &Compilation,
) -> Result<Box<[&mut [VMFunctionBody]]>, String> {
let total_len = compilation
.into_iter()
.fold(0, |acc, func| acc + Self::function_allocation_size(func));
let mut start = self.position as u32;
let (mut buf, mut table) = self.allocate(total_len)?;
let mut result = Vec::with_capacity(compilation.len());
for func in compilation.into_iter() {
let (next_start, next_buf, next_table, vmfunc) =
Self::copy_function(func, start, buf, table);
result.push(vmfunc);
start = next_start;
buf = next_buf;
table = next_table;
}
Ok(result.into_boxed_slice())
}
/// Make all allocated memory executable.
pub fn publish(&mut self) {
self.push_current(0)
.expect("failed to push current memory map");
for (m, t) in &mut self.mmaps[self.published..] {
if m.len() != 0 {
unsafe {
region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute)
}
.expect("unable to make memory readonly and executable");
}
t.publish(m.as_ptr() as u64)
.expect("failed to publish function table");
}
self.published = self.mmaps.len();
}
/// Allocate `size` bytes of memory which can be made executable later by /// Allocate `size` bytes of memory which can be made executable later by
/// calling `publish()`. Note that we allocate the memory as writeable so /// calling `publish()`. Note that we allocate the memory as writeable so
/// that it can be written to and patched, though we make it readonly before /// that it can be written to and patched, though we make it readonly before
/// actually executing from it. /// actually executing from it.
/// ///
/// TODO: Add an alignment flag. /// TODO: Add an alignment flag.
fn allocate(&mut self, size: usize) -> Result<&mut [u8], String> { fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut FunctionTable), String> {
if self.current.len() - self.position < size { if self.current.0.len() - self.position < size {
self.mmaps.push(mem::replace( self.push_current(cmp::max(0x10000, size))?;
&mut self.current,
Mmap::with_at_least(cmp::max(0x10000, size))?,
));
self.position = 0;
} }
let old_position = self.position; let old_position = self.position;
self.position += size; self.position += size;
Ok(&mut self.current.as_mut_slice()[old_position..self.position])
Ok((
&mut self.current.0.as_mut_slice()[old_position..self.position],
&mut self.current.1,
))
}
/// Calculates the allocation size of the given compiled function.
fn function_allocation_size(func: &CompiledFunction) -> usize {
if func.unwind_info.is_empty() {
func.body.len()
} else {
// Account for necessary unwind information alignment padding (32-bit)
((func.body.len() + 3) & !3) + func.unwind_info.len()
}
}
/// Copies the data of the compiled function to the given buffer.
///
/// This will also add the function to the current function table.
fn copy_function<'a>(
func: &CompiledFunction,
func_start: u32,
buf: &'a mut [u8],
table: &'a mut FunctionTable,
) -> (
u32,
&'a mut [u8],
&'a mut FunctionTable,
&'a mut [VMFunctionBody],
) {
let func_end = func_start + (func.body.len() as u32);
let (body, remainder) = buf.split_at_mut(func.body.len());
body.copy_from_slice(&func.body);
let vmfunc = Self::view_as_mut_vmfunc_slice(body);
if func.unwind_info.is_empty() {
return (func_end, remainder, table, vmfunc);
}
// Keep unwind information 32-bit aligned (round up to the nearest 4 byte boundary)
let padding = ((func.body.len() + 3) & !3) - func.body.len();
let (unwind, remainder) = remainder.split_at_mut(padding + func.unwind_info.len());
unwind[padding..].copy_from_slice(&func.unwind_info);
let unwind_start = func_end + (padding as u32);
let unwind_end = unwind_start + (func.unwind_info.len() as u32);
table.add_function(func_start, func_end, unwind_start);
(unwind_end, remainder, table, vmfunc)
} }
/// Convert mut a slice from u8 to VMFunctionBody. /// Convert mut a slice from u8 to VMFunctionBody.
@@ -52,51 +168,28 @@ impl CodeMemory {
unsafe { &mut *body_ptr } unsafe { &mut *body_ptr }
} }
/// Allocate enough memory to hold a copy of `slice` and copy the data into it. /// Pushes the current Mmap (and function table) and allocates a new Mmap of the given size.
/// TODO: Reorganize the code that calls this to emit code directly into the fn push_current(&mut self, new_size: usize) -> Result<(), String> {
/// mmap region rather than into a Vec that we need to copy in. let previous = mem::replace(
pub fn allocate_copy_of_byte_slice( &mut self.current,
&mut self, (
slice: &[u8], if new_size == 0 {
) -> Result<&mut [VMFunctionBody], String> { Mmap::new()
let new = self.allocate(slice.len())?; } else {
new.copy_from_slice(slice); Mmap::with_at_least(cmp::max(0x10000, new_size))?
Ok(Self::view_as_mut_vmfunc_slice(new)) },
FunctionTable::new(),
),
);
if previous.0.len() > 0 {
self.mmaps.push(previous);
} else {
assert!(previous.1.len() == 0);
} }
/// Allocate enough continuous memory block for multiple code blocks. See also
/// allocate_copy_of_byte_slice.
pub fn allocate_copy_of_byte_slices(
&mut self,
slices: &[&[u8]],
) -> Result<Box<[&mut [VMFunctionBody]]>, String> {
let total_len = slices.into_iter().fold(0, |acc, slice| acc + slice.len());
let new = self.allocate(total_len)?;
let mut tail = new;
let mut result = Vec::with_capacity(slices.len());
for slice in slices {
let (block, next_tail) = tail.split_at_mut(slice.len());
block.copy_from_slice(slice);
tail = next_tail;
result.push(Self::view_as_mut_vmfunc_slice(block));
}
Ok(result.into_boxed_slice())
}
/// Make all allocated memory executable.
pub fn publish(&mut self) {
self.mmaps
.push(mem::replace(&mut self.current, Mmap::new()));
self.position = 0; self.position = 0;
for m in &mut self.mmaps[self.published..] { Ok(())
if m.len() != 0 {
unsafe {
region::protect(m.as_mut_ptr(), m.len(), region::Protection::ReadExecute)
}
.expect("unable to make memory readonly and executable");
}
}
self.published = self.mmaps.len();
} }
} }

View File

@@ -17,8 +17,8 @@ use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_wasm::{DefinedFuncIndex, DefinedMemoryIndex, ModuleTranslationState}; use cranelift_wasm::{DefinedFuncIndex, DefinedMemoryIndex, ModuleTranslationState};
use wasmtime_debug::{emit_debugsections_image, DebugInfoData}; use wasmtime_debug::{emit_debugsections_image, DebugInfoData};
use wasmtime_environ::{ use wasmtime_environ::{
Compilation, CompileError, Compiler as _C, FunctionBodyData, Module, ModuleVmctxInfo, Compilation, CompileError, CompiledFunction, Compiler as _C, FunctionBodyData, Module,
Relocations, Traps, Tunables, VMOffsets, ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets,
}; };
use wasmtime_runtime::{ use wasmtime_runtime::{
get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard, get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard,
@@ -323,7 +323,8 @@ fn make_trampoline(
builder.finalize() builder.finalize()
} }
let mut code_buf: Vec<u8> = Vec::new(); let mut code_buf = Vec::new();
let mut unwind_info = Vec::new();
let mut reloc_sink = RelocSink {}; let mut reloc_sink = RelocSink {};
let mut trap_sink = binemit::NullTrapSink {}; let mut trap_sink = binemit::NullTrapSink {};
let mut stackmap_sink = binemit::NullStackmapSink {}; let mut stackmap_sink = binemit::NullStackmapSink {};
@@ -337,8 +338,14 @@ fn make_trampoline(
) )
.map_err(|error| SetupError::Compile(CompileError::Codegen(error)))?; .map_err(|error| SetupError::Compile(CompileError::Codegen(error)))?;
context.emit_unwind_info(isa, &mut unwind_info);
Ok(code_memory Ok(code_memory
.allocate_copy_of_byte_slice(&code_buf) .allocate_for_function(&CompiledFunction {
body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
})
.map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))? .map_err(|message| SetupError::Instantiate(InstantiationError::Resource(message)))?
.as_ptr()) .as_ptr())
} }
@@ -347,14 +354,8 @@ fn allocate_functions(
code_memory: &mut CodeMemory, code_memory: &mut CodeMemory,
compilation: &Compilation, compilation: &Compilation,
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> { ) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
// Allocate code for all function in one continuous memory block. let fat_ptrs = code_memory.allocate_for_compilation(compilation)?;
// First, collect all function bodies into vector to pass to the
// allocate_copy_of_byte_slices.
let bodies = compilation
.into_iter()
.map(|code_and_jt| &code_and_jt.body[..])
.collect::<Vec<&[u8]>>();
let fat_ptrs = code_memory.allocate_copy_of_byte_slices(&bodies)?;
// Second, create a PrimaryMap from result vector of pointers. // Second, create a PrimaryMap from result vector of pointers.
let mut result = PrimaryMap::with_capacity(compilation.len()); let mut result = PrimaryMap::with_capacity(compilation.len());
for i in 0..fat_ptrs.len() { for i in 0..fat_ptrs.len() {

View File

@@ -0,0 +1,134 @@
//! Runtime function table.
//!
//! This module is primarily used to track JIT functions on Windows for stack walking and unwind.
/// Represents a runtime function table.
///
/// The runtime function table is not implemented for non-Windows target platforms.
#[cfg(not(target_os = "windows"))]
pub(crate) struct FunctionTable;
#[cfg(not(target_os = "windows"))]
impl FunctionTable {
/// Creates a new function table.
pub fn new() -> Self {
Self
}
/// Returns the number of functions in the table, also referred to as its 'length'.
///
/// For non-Windows platforms, the table will always be empty.
pub fn len(&self) -> usize {
0
}
/// Adds a function to the table based off of the start offset, end offset, and unwind offset.
///
/// The offsets are from the "module base", which is provided when the table is published.
///
/// For non-Windows platforms, this is a no-op.
pub fn add_function(&mut self, _start: u32, _end: u32, _unwind: u32) {}
/// Publishes the function table using the given base address.
///
/// A published function table will automatically be deleted when it is dropped.
///
/// For non-Windows platforms, this is a no-op.
pub fn publish(&mut self, _base_address: u64) -> Result<(), String> {
Ok(())
}
}
/// Represents a runtime function table.
///
/// This is used to register JIT code with the operating system to enable stack walking and unwinding.
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
pub(crate) struct FunctionTable {
functions: Vec<winapi::um::winnt::RUNTIME_FUNCTION>,
published: bool,
}
#[cfg(all(target_os = "windows", target_arch = "x86_64"))]
impl FunctionTable {
/// Creates a new function table.
pub fn new() -> Self {
Self {
functions: Vec::new(),
published: false,
}
}
/// Returns the number of functions in the table, also referred to as its 'length'.
pub fn len(&self) -> usize {
self.functions.len()
}
/// Adds a function to the table based off of the start offset, end offset, and unwind offset.
///
/// The offsets are from the "module base", which is provided when the table is published.
pub fn add_function(&mut self, start: u32, end: u32, unwind: u32) {
use winapi::um::winnt;
assert!(!self.published, "table has already been published");
let mut entry = winnt::RUNTIME_FUNCTION::default();
entry.BeginAddress = start;
entry.EndAddress = end;
unsafe {
*entry.u.UnwindInfoAddress_mut() = unwind;
}
self.functions.push(entry);
}
/// Publishes the function table using the given base address.
///
/// A published function table will automatically be deleted when it is dropped.
pub fn publish(&mut self, base_address: u64) -> Result<(), String> {
use winapi::um::winnt;
if self.published {
return Err("function table was already published".into());
}
self.published = true;
if self.functions.is_empty() {
return Ok(());
}
unsafe {
// Windows heap allocations are 32-bit aligned, but assert just in case
assert!(
(self.functions.as_mut_ptr() as u64) % 4 == 0,
"function table allocation was not aligned"
);
if winnt::RtlAddFunctionTable(
self.functions.as_mut_ptr(),
self.functions.len() as u32,
base_address,
) == 0
{
return Err("failed to add function table".into());
}
}
Ok(())
}
}
#[cfg(target_os = "windows")]
impl Drop for FunctionTable {
fn drop(&mut self) {
use winapi::um::winnt;
if self.published {
unsafe {
winnt::RtlDeleteFunctionTable(self.functions.as_mut_ptr());
}
}
}
}

View File

@@ -34,6 +34,7 @@ mod action;
mod code_memory; mod code_memory;
mod compiler; mod compiler;
mod context; mod context;
mod function_table;
mod instantiate; mod instantiate;
mod link; mod link;
mod namespace; mod namespace;

View File

@@ -404,7 +404,7 @@ static
__attribute__ ((warn_unused_result)) __attribute__ ((warn_unused_result))
#endif #endif
bool bool
HandleTrap(CONTEXT* context) HandleTrap(CONTEXT* context, bool reset_guard_page)
{ {
assert(sAlreadyHandlingTrap); assert(sAlreadyHandlingTrap);
@@ -412,7 +412,7 @@ HandleTrap(CONTEXT* context)
return false; return false;
} }
RecordTrap(ContextToPC(context)); RecordTrap(ContextToPC(context), reset_guard_page);
// Unwind calls longjmp, so it doesn't run the automatic // Unwind calls longjmp, so it doesn't run the automatic
// sAlreadhHanldingTrap cleanups, so reset it manually before doing // sAlreadhHanldingTrap cleanups, so reset it manually before doing
@@ -467,7 +467,8 @@ WasmTrapHandler(LPEXCEPTION_POINTERS exception)
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
if (!HandleTrap(exception->ContextRecord)) { if (!HandleTrap(exception->ContextRecord,
record->ExceptionCode == EXCEPTION_STACK_OVERFLOW)) {
return EXCEPTION_CONTINUE_SEARCH; return EXCEPTION_CONTINUE_SEARCH;
} }
@@ -549,7 +550,7 @@ HandleMachException(const ExceptionRequest& request)
{ {
AutoHandlingTrap aht; AutoHandlingTrap aht;
if (!HandleTrap(&context)) { if (!HandleTrap(&context, false)) {
return false; return false;
} }
} }
@@ -632,7 +633,7 @@ WasmTrapHandler(int signum, siginfo_t* info, void* context)
if (!sAlreadyHandlingTrap) { if (!sAlreadyHandlingTrap) {
AutoHandlingTrap aht; AutoHandlingTrap aht;
assert(signum == SIGSEGV || signum == SIGBUS || signum == SIGFPE || signum == SIGILL); assert(signum == SIGSEGV || signum == SIGBUS || signum == SIGFPE || signum == SIGILL);
if (HandleTrap(static_cast<CONTEXT*>(context))) { if (HandleTrap(static_cast<CONTEXT*>(context), false)) {
return; return;
} }
} }

View File

@@ -13,7 +13,7 @@ extern "C" {
int8_t CheckIfTrapAtAddress(const uint8_t* pc); int8_t CheckIfTrapAtAddress(const uint8_t* pc);
// Record the Trap code and wasm bytecode offset in TLS somewhere // Record the Trap code and wasm bytecode offset in TLS somewhere
void RecordTrap(const uint8_t* pc); void RecordTrap(const uint8_t* pc, bool reset_guard_page);
void* EnterScope(void*); void* EnterScope(void*);
void LeaveScope(void*); void LeaveScope(void*);

View File

@@ -21,6 +21,7 @@ extern "C" {
thread_local! { thread_local! {
static RECORDED_TRAP: Cell<Option<TrapDescription>> = Cell::new(None); static RECORDED_TRAP: Cell<Option<TrapDescription>> = Cell::new(None);
static JMP_BUF: Cell<*const u8> = Cell::new(ptr::null()); static JMP_BUF: Cell<*const u8> = Cell::new(ptr::null());
static RESET_GUARD_PAGE: Cell<bool> = Cell::new(false);
} }
/// Check if there is a trap at given PC /// Check if there is a trap at given PC
@@ -40,7 +41,7 @@ pub extern "C" fn CheckIfTrapAtAddress(_pc: *const u8) -> i8 {
#[doc(hidden)] #[doc(hidden)]
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[no_mangle] #[no_mangle]
pub extern "C" fn RecordTrap(pc: *const u8) { pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) {
// TODO: please see explanation in CheckIfTrapAtAddress. // TODO: please see explanation in CheckIfTrapAtAddress.
let registry = get_trap_registry(); let registry = get_trap_registry();
let trap_desc = registry let trap_desc = registry
@@ -49,6 +50,11 @@ pub extern "C" fn RecordTrap(pc: *const u8) {
source_loc: ir::SourceLoc::default(), source_loc: ir::SourceLoc::default(),
trap_code: ir::TrapCode::StackOverflow, trap_code: ir::TrapCode::StackOverflow,
}); });
if reset_guard_page {
RESET_GUARD_PAGE.with(|v| v.set(true));
}
RECORDED_TRAP.with(|data| { RECORDED_TRAP.with(|data| {
assert_eq!( assert_eq!(
data.get(), data.get(),
@@ -77,9 +83,32 @@ pub extern "C" fn GetScope() -> *const u8 {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[no_mangle] #[no_mangle]
pub extern "C" fn LeaveScope(ptr: *const u8) { pub extern "C" fn LeaveScope(ptr: *const u8) {
RESET_GUARD_PAGE.with(|v| {
if v.get() {
reset_guard_page();
v.set(false);
}
});
JMP_BUF.with(|buf| buf.set(ptr)) JMP_BUF.with(|buf| buf.set(ptr))
} }
#[cfg(target_os = "windows")]
fn reset_guard_page() {
extern "C" {
fn _resetstkoflw() -> winapi::ctypes::c_int;
}
// We need to restore guard page under stack to handle future stack overflows properly.
// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/resetstkoflw?view=vs-2019
if unsafe { _resetstkoflw() } == 0 {
panic!("failed to restore stack guard page");
}
}
#[cfg(not(target_os = "windows"))]
fn reset_guard_page() {}
fn trap_message() -> String { fn trap_message() -> String {
let trap_desc = RECORDED_TRAP let trap_desc = RECORDED_TRAP
.with(|data| data.replace(None)) .with(|data| data.replace(None))