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:
fail-fast: false
matrix:
build: [stable, beta, nightly, windows, linux]
build: [stable, beta, nightly, windows, macos]
include:
- build: stable
os: macos-latest
os: ubuntu-latest
rust: stable
- build: beta
os: macos-latest
os: ubuntu-latest
rust: beta
- build: nightly
os: macos-latest
rust: nightly
- build: linux
os: ubuntu-latest
rust: nightly
- build: macos
os: macos-latest
rust: stable
- build: windows
os: windows-latest
@@ -216,7 +216,7 @@ jobs:
- run: $CENTOS cargo build --release --bin wasmtime --bin wasm2obj
shell: bash
# 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
# 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

View File

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

View File

@@ -35,34 +35,24 @@ of ongoing research.
Additional goals for Wasmtime include:
- 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.
- Implement the [proposed WebAssembly C API].
- Facilitate testing, experimentation, and development around the [Cranelift] and
[Lightbeam] JITs.
- Facilitate development and testing around the [Cranelift] and [Lightbeam] JITs,
and other WebAssembly execution strategies.
- Develop a native ABI used for compiling WebAssembly suitable for use in both
JIT and AOT to native object files.
[proposed WebAssembly C API]: https://github.com/rossberg/wasm-c-api
[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
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].
```
cargo build --package wasmtime-jit
```
For more information, see the [Rust API embedding chapter] of the Wasmtime documentation.
Wasmtime does not currently publish these crates on crates.io. They may be included as a git dependency, like this:
```toml
[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.
[high-level and safe Rust API]: https://docs.rs/wasmtime-api/
[proposed WebAssembly C API]: https://github.com/WebAssembly/wasm-c-api
[Rust API embedding chapter]: https://cranestation.github.io/wasmtime/embed-rust.html
It's Wasmtime.

245
build.rs
View File

@@ -3,108 +3,105 @@
//! By generating a separate `#[test]` test for each file, we allow cargo test
//! to automatically run the files in parallel.
use anyhow::Context;
use std::env;
use std::fs::{read_dir, File};
use std::io::{self, Write};
use std::fmt::Write;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
fn main() {
let out_dir =
PathBuf::from(env::var("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");
fn main() -> anyhow::Result<()> {
let out_dir = PathBuf::from(
env::var_os("OUT_DIR").expect("The OUT_DIR environment variable must be set"),
);
let mut out = String::new();
for strategy in &[
"Cranelift",
#[cfg(feature = "lightbeam")]
"Lightbeam",
] {
writeln!(out, "#[cfg(test)]").expect("generating tests");
writeln!(out, "#[allow(non_snake_case)]").expect("generating tests");
writeln!(out, "mod {} {{", strategy).expect("generating tests");
writeln!(out, "#[cfg(test)]")?;
writeln!(out, "#[allow(non_snake_case)]")?;
writeln!(out, "mod {} {{", strategy)?;
test_directory(&mut out, "misc_testsuite", strategy).expect("generating tests");
test_directory(&mut out, "spec_testsuite", strategy).expect("generating tests");
// Skip running spec_testsuite tests if the submodule isn't checked out.
if read_dir("spec_testsuite")
.expect("reading testsuite directory")
.next()
.is_some()
{
test_file(
test_directory(&mut out, "misc_testsuite", strategy)?;
let spec_tests = test_directory(&mut out, "spec_testsuite", strategy)?;
// Skip running spec_testsuite tests if the submodule isn't checked
// out.
if spec_tests > 0 {
start_test_module(&mut out, "simd")?;
write_testsuite_tests(
&mut out,
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_address.wast"]),
"spec_testsuite/proposals/simd/simd_address.wast",
"simd",
strategy,
)
.expect("generating tests");
test_file(
)?;
write_testsuite_tests(
&mut out,
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_align.wast"]),
"spec_testsuite/proposals/simd/simd_align.wast",
"simd",
strategy,
)
.expect("generating tests");
test_file(
)?;
write_testsuite_tests(
&mut out,
&to_os_path(&["spec_testsuite", "proposals", "simd", "simd_const.wast"]),
"spec_testsuite/proposals/simd/simd_const.wast",
"simd",
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, &multi_value_suite, strategy).expect("generating tests");
test_directory(&mut out, "spec_testsuite/proposals/multi-value", strategy)
.expect("generating tests");
} else {
println!("cargo:warning=The spec testsuite is disabled. To enable, run `git submodule update --remote`.");
}
writeln!(out, "}}").expect("generating tests");
writeln!(out, "}}")?;
}
// Write out our auto-generated tests and opportunistically format them with
// `rustfmt` if it's installed.
let output = out_dir.join("wast_testsuite_tests.rs");
fs::write(&output, out)?;
drop(Command::new("rustfmt").arg(&output).status());
Ok(())
}
/// Helper for creating OS-independent paths.
fn to_os_path(components: &[&str]) -> String {
let path: PathBuf = components.iter().collect();
path.display().to_string()
}
fn test_directory(out: &mut File, path: &str, strategy: &str) -> io::Result<()> {
let mut dir_entries: Vec<_> = read_dir(path)
.expect("reading testsuite directory")
fn test_directory(
out: &mut String,
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"))
.filter(|dir_entry| {
.filter_map(|dir_entry| {
let p = dir_entry.path();
if let Some(ext) = p.extension() {
let ext = p.extension()?;
// Only look at wast files.
if ext == "wast" {
if ext != "wast" {
return None;
}
// Ignore files starting with `.`, which could be editor temporary files
if let Some(stem) = p.file_stem() {
if let Some(stemstr) = stem.to_str() {
if !stemstr.starts_with('.') {
return true;
if p.file_stem()?.to_str()?.starts_with(".") {
return None;
}
}
}
}
}
false
Some(p)
})
.collect();
dir_entries.sort_by_key(|dir| dir.path());
dir_entries.sort();
let testsuite = &extract_name(path);
start_test_module(out, testsuite)?;
for dir_entry in dir_entries {
write_testsuite_tests(out, &dir_entry.path(), testsuite, strategy)?;
for entry in dir_entries.iter() {
write_testsuite_tests(out, entry, testsuite, strategy)?;
}
finish_test_module(out)
}
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)
finish_test_module(out)?;
Ok(dir_entries.len())
}
/// Extract a valid Rust identifier from the stem of a path.
@@ -118,63 +115,38 @@ fn extract_name(path: impl AsRef<Path>) -> String {
.replace("/", "_")
}
fn start_test_module(out: &mut File, testsuite: &str) -> io::Result<()> {
writeln!(out, " mod {} {{", testsuite)?;
writeln!(
out,
" use super::super::{{native_isa, Path, WastContext, Compiler, Features, CompilationStrategy}};"
)
fn start_test_module(out: &mut String, testsuite: &str) -> anyhow::Result<()> {
writeln!(out, "mod {} {{", testsuite)?;
Ok(())
}
fn finish_test_module(out: &mut File) -> io::Result<()> {
writeln!(out, " }}")
fn finish_test_module(out: &mut String) -> anyhow::Result<()> {
out.push_str("}\n");
Ok(())
}
fn write_testsuite_tests(
out: &mut File,
path: &Path,
out: &mut String,
path: impl AsRef<Path>,
testsuite: &str,
strategy: &str,
) -> io::Result<()> {
) -> anyhow::Result<()> {
let path = path.as_ref();
println!("cargo:rerun-if-changed={}", path.display());
let testname = extract_name(path);
writeln!(out, " #[test]")?;
writeln!(out, "#[test]")?;
if ignore(testsuite, &testname, strategy) {
writeln!(out, " #[ignore]")?;
writeln!(out, "#[ignore]")?;
}
writeln!(out, " fn r#{}() {{", &testname)?;
writeln!(out, " let isa = native_isa();")?;
writeln!(out, "fn r#{}() -> anyhow::Result<()> {{", &testname)?;
writeln!(
out,
" let compiler = Compiler::new(isa, CompilationStrategy::{});",
"crate::run_wast(r#\"{}\"#, crate::CompilationStrategy::{})",
path.display(),
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)?;
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
// multi-value support in Cranelift.
(_, _) 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,
};
}
#[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
}

View File

@@ -1,3 +1,99 @@
# 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))
}
WasmOperator::GetGlobal { global_index } => 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::GetGlobal { global_index } => {
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::F64Load { .. } => sig!((I32) -> (F64)),
@@ -1259,8 +1263,12 @@ where
| WasmOperator::F64Le
| WasmOperator::F64Ge => sig!((F64, F64) -> (I32)),
WasmOperator::I32Clz | WasmOperator::I32Ctz | WasmOperator::I32Popcnt => sig!((I32) -> (I32)),
WasmOperator::I64Clz | WasmOperator::I64Ctz | WasmOperator::I64Popcnt => sig!((I64) -> (I64)),
WasmOperator::I32Clz | WasmOperator::I32Ctz | WasmOperator::I32Popcnt => {
sig!((I32) -> (I32))
}
WasmOperator::I64Clz | WasmOperator::I64Ctz | WasmOperator::I64Popcnt => {
sig!((I64) -> (I64))
}
WasmOperator::I32Add
| WasmOperator::I32Sub

View File

@@ -1027,18 +1027,18 @@ test_select!(select64, i64);
mod benches {
extern crate test;
use super::{translate, wabt, FIBONACCI, FIBONACCI_OPT};
use super::{translate, FIBONACCI, FIBONACCI_OPT};
#[bench]
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()));
}
#[bench]
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();
b.iter(|| module.execute_func::<_, u32>(0, (20,)));
@@ -1046,7 +1046,7 @@ mod benches {
#[bench]
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,)));
}

View File

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

View File

@@ -270,14 +270,14 @@ fn main() -> Result<()> {
strategy,
);
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();
// Make spectest available by default.
module_registry.insert(
"spectest".to_owned(),
Instance::from_handle(store.clone(), instantiate_spectest()?)?,
Instance::from_handle(&store, instantiate_spectest()?)?,
);
// Make wasi available by default.
@@ -301,36 +301,36 @@ fn main() -> Result<()> {
module_registry.insert(
"wasi_unstable".to_owned(),
Instance::from_handle(store.clone(), wasi.clone())?,
Instance::from_handle(&store, wasi.clone())?,
);
module_registry.insert(
"wasi_unstable_preview0".to_owned(),
Instance::from_handle(store.clone(), wasi)?,
Instance::from_handle(&store, wasi)?,
);
// Load the preload wasm modules.
for filename in &args.flag_preload {
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()))?;
}
// Load the main wasm module.
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()))?;
Ok(())
}
fn instantiate_module(
store: HostRef<Store>,
store: &HostRef<Store>,
module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>,
path: &Path,
) -> Result<(HostRef<Instance>, HostRef<Module>, Vec<u8>)> {
// Read the wasm module binary either as `*.wat` or a raw binary
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.
let imports = module
@@ -356,18 +356,18 @@ fn instantiate_module(
})
.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))
}
fn handle_module(
store: HostRef<Store>,
store: &HostRef<Store>,
module_registry: &HashMap<String, (Instance, HashMap<String, usize>)>,
args: &Args,
path: &Path,
) -> 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 let Some(f) = &args.flag_invoke {
@@ -379,7 +379,7 @@ fn handle_module(
}
fn invoke_export(
store: HostRef<Store>,
store: &HostRef<Store>,
instance: HostRef<Instance>,
data: &ModuleData,
name: &str,

View File

@@ -9,7 +9,24 @@ use wasmtime_wast::WastContext;
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> {
let mut flag_builder = settings::builder();
flag_builder.enable("enable_verifier").unwrap();

View File

@@ -30,7 +30,6 @@ hashbrown = { version = "0.6.0", optional = true }
[features]
default = ["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"]
[dev-dependencies]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -34,9 +34,9 @@ pub(crate) struct 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 {
store,
store: store.clone(),
instance,
export,
}

View File

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

View File

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

View File

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

View File

@@ -182,10 +182,10 @@ pub struct 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)?;
Ok(Module {
store,
store: store.clone(),
binary: binary.into(),
imports,
exports,

View File

@@ -102,13 +102,13 @@ pub struct 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 features = engine.borrow().config().features().clone();
let debug_info = engine.borrow().config().debug_info();
let strategy = engine.borrow().config().strategy();
Store {
engine,
engine: engine.clone(),
context: Context::create(flags, features, debug_info, strategy),
global_exports: Rc::new(RefCell::new(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_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_wasm::{DefinedFuncIndex, FuncIndex};
//use target_lexicon::HOST;
use wasmtime_environ::{Export, Module};
use wasmtime_environ::{CompiledFunction, Export, Module};
use wasmtime_jit::CodeMemory;
use wasmtime_runtime::{InstanceHandle, VMContext, VMFunctionBody};
@@ -185,9 +184,16 @@ fn make_trampoline(
)
.expect("compile_and_emit");
let mut unwind_info = Vec::new();
context.emit_unwind_info(isa, &mut unwind_info);
code_memory
.allocate_copy_of_byte_slice(&code_buf)
.expect("allocate_copy_of_byte_slice")
.allocate_for_function(&CompiledFunction {
body: code_buf,
jt_offsets: context.func.jt_offsets,
unwind_info,
})
.expect("allocate_for_function")
.as_ptr()
}

View File

@@ -230,6 +230,6 @@ pub(crate) fn from_checked_anyfunc(
signature,
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))
}

View File

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

View File

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

View File

@@ -1,7 +1,7 @@
use super::config::tests::test_prolog;
use super::*;
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 alloc::boxed::Box;
use alloc::vec::Vec;
@@ -258,9 +258,10 @@ fn new_module_cache_data(rng: &mut impl Rng) -> ModuleCacheData {
*v = (j as u32) * 3 / 4
}
});
CodeAndJTOffsets {
CompiledFunction {
body: (0..(i * 3 / 2)).collect(),
jt_offsets: sm,
unwind_info: (0..(i * 3 / 2)).collect(),
}
})
.collect();

View File

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

View File

@@ -5,7 +5,7 @@ use crate::address_map::{
};
use crate::cache::{ModuleCacheData, ModuleCacheEntry};
use crate::compilation::{
CodeAndJTOffsets, Compilation, CompileError, Relocation, RelocationTarget, Relocations,
Compilation, CompileError, CompiledFunction, Relocation, RelocationTarget, Relocations,
TrapInformation, Traps,
};
use crate::func_environ::{
@@ -235,6 +235,7 @@ impl crate::compilation::Compiler for Cranelift {
)?;
let mut code_buf: Vec<u8> = Vec::new();
let mut unwind_info = Vec::new();
let mut reloc_sink = RelocSink::new(func_index);
let mut trap_sink = TrapSink::new();
let mut stackmap_sink = binemit::NullStackmapSink {};
@@ -246,7 +247,7 @@ impl crate::compilation::Compiler for Cranelift {
&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 body_len = code_buf.len();
@@ -261,16 +262,15 @@ impl crate::compilation::Compiler for Cranelift {
None
};
let stack_slots = context.func.stack_slots.clone();
Ok((
code_buf,
jt_offsets,
context.func.jt_offsets,
reloc_sink.func_relocs,
address_transform,
ranges,
stack_slots,
context.func.stack_slots,
trap_sink.traps,
unwind_info,
))
},
)
@@ -285,10 +285,12 @@ impl crate::compilation::Compiler for Cranelift {
ranges,
sss,
function_traps,
unwind_info,
)| {
functions.push(CodeAndJTOffsets {
functions.push(CompiledFunction {
body: function,
jt_offsets: func_jt_offsets,
unwind_info,
});
relocations.push(relocs);
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::compilation::{
Compilation, CompileError, Compiler, Relocation, RelocationTarget, Relocations,
TrapInformation, Traps,
Compilation, CompileError, CompiledFunction, Compiler, Relocation, RelocationTarget,
Relocations, TrapInformation, Traps,
};
pub use crate::cranelift::Cranelift;
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
// 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
.funcs()
.into_iter()
.map(|r| (r, SecondaryMap::new()));
.map(|r| (r, SecondaryMap::new(), 0..0));
Ok((
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 }
wasmparser = { version = "0.39.2", default-features = false }
[target.'cfg(target_os = "windows")'.dependencies]
winapi = { version = "0.3.7", features = ["winnt", "impl-default"] }
[features]
default = ["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.
use crate::function_table::FunctionTable;
use alloc::boxed::Box;
use alloc::string::String;
use alloc::vec::Vec;
use core::{cmp, mem};
use region;
use wasmtime_environ::{Compilation, CompiledFunction};
use wasmtime_runtime::{Mmap, VMFunctionBody};
/// Memory manager for executable code.
pub struct CodeMemory {
current: Mmap,
mmaps: Vec<Mmap>,
current: (Mmap, FunctionTable),
mmaps: Vec<(Mmap, FunctionTable)>,
position: usize,
published: usize,
}
@@ -19,30 +21,144 @@ impl CodeMemory {
/// Create a new `CodeMemory` instance.
pub fn new() -> Self {
Self {
current: Mmap::new(),
current: (Mmap::new(), FunctionTable::new()),
mmaps: Vec::new(),
position: 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
/// 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
/// actually executing from it.
///
/// TODO: Add an alignment flag.
fn allocate(&mut self, size: usize) -> Result<&mut [u8], String> {
if self.current.len() - self.position < size {
self.mmaps.push(mem::replace(
&mut self.current,
Mmap::with_at_least(cmp::max(0x10000, size))?,
));
self.position = 0;
fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut FunctionTable), String> {
if self.current.0.len() - self.position < size {
self.push_current(cmp::max(0x10000, size))?;
}
let old_position = self.position;
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.
@@ -52,51 +168,28 @@ impl CodeMemory {
unsafe { &mut *body_ptr }
}
/// Allocate enough memory to hold a copy of `slice` and copy the data into it.
/// 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_copy_of_byte_slice(
&mut self,
slice: &[u8],
) -> Result<&mut [VMFunctionBody], String> {
let new = self.allocate(slice.len())?;
new.copy_from_slice(slice);
Ok(Self::view_as_mut_vmfunc_slice(new))
/// Pushes the current Mmap (and function table) and allocates a new Mmap of the given size.
fn push_current(&mut self, new_size: usize) -> Result<(), String> {
let previous = mem::replace(
&mut self.current,
(
if new_size == 0 {
Mmap::new()
} else {
Mmap::with_at_least(cmp::max(0x10000, new_size))?
},
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;
for m 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");
}
}
self.published = self.mmaps.len();
Ok(())
}
}

View File

@@ -17,8 +17,8 @@ use cranelift_frontend::{FunctionBuilder, FunctionBuilderContext};
use cranelift_wasm::{DefinedFuncIndex, DefinedMemoryIndex, ModuleTranslationState};
use wasmtime_debug::{emit_debugsections_image, DebugInfoData};
use wasmtime_environ::{
Compilation, CompileError, Compiler as _C, FunctionBodyData, Module, ModuleVmctxInfo,
Relocations, Traps, Tunables, VMOffsets,
Compilation, CompileError, CompiledFunction, Compiler as _C, FunctionBodyData, Module,
ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets,
};
use wasmtime_runtime::{
get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard,
@@ -323,7 +323,8 @@ fn make_trampoline(
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 trap_sink = binemit::NullTrapSink {};
let mut stackmap_sink = binemit::NullStackmapSink {};
@@ -337,8 +338,14 @@ fn make_trampoline(
)
.map_err(|error| SetupError::Compile(CompileError::Codegen(error)))?;
context.emit_unwind_info(isa, &mut unwind_info);
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)))?
.as_ptr())
}
@@ -347,14 +354,8 @@ fn allocate_functions(
code_memory: &mut CodeMemory,
compilation: &Compilation,
) -> Result<PrimaryMap<DefinedFuncIndex, *mut [VMFunctionBody]>, String> {
// Allocate code for all function in one continuous memory block.
// 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)?;
let fat_ptrs = code_memory.allocate_for_compilation(compilation)?;
// Second, create a PrimaryMap from result vector of pointers.
let mut result = PrimaryMap::with_capacity(compilation.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 compiler;
mod context;
mod function_table;
mod instantiate;
mod link;
mod namespace;

View File

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

View File

@@ -13,7 +13,7 @@ extern "C" {
int8_t CheckIfTrapAtAddress(const uint8_t* pc);
// 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 LeaveScope(void*);

View File

@@ -21,6 +21,7 @@ extern "C" {
thread_local! {
static RECORDED_TRAP: Cell<Option<TrapDescription>> = Cell::new(None);
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
@@ -40,7 +41,7 @@ pub extern "C" fn CheckIfTrapAtAddress(_pc: *const u8) -> i8 {
#[doc(hidden)]
#[allow(non_snake_case)]
#[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.
let registry = get_trap_registry();
let trap_desc = registry
@@ -49,6 +50,11 @@ pub extern "C" fn RecordTrap(pc: *const u8) {
source_loc: ir::SourceLoc::default(),
trap_code: ir::TrapCode::StackOverflow,
});
if reset_guard_page {
RESET_GUARD_PAGE.with(|v| v.set(true));
}
RECORDED_TRAP.with(|data| {
assert_eq!(
data.get(),
@@ -77,9 +83,32 @@ pub extern "C" fn GetScope() -> *const u8 {
#[allow(non_snake_case)]
#[no_mangle]
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))
}
#[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 {
let trap_desc = RECORDED_TRAP
.with(|data| data.replace(None))