diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c381ee0d7c..fa7f30ccc4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/Cargo.toml b/Cargo.toml index 60972c775c..3c47549512 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 = [ diff --git a/README.md b/README.md index 2ca99c5493..8e82aa6db8 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/build.rs b/build.rs index cb94a74af8..0c3bf69053 100644 --- a/build.rs +++ b/build.rs @@ -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, + strategy: &str, +) -> anyhow::Result { + 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() { - // Only look at wast files. - if ext == "wast" { - // 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; - } - } - } - } + let ext = p.extension()?; + // Only look at wast files. + if ext != "wast" { + return None; } - false + // Ignore files starting with `.`, which could be editor temporary files + if p.file_stem()?.to_str()?.starts_with(".") { + return None; + } + 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) -> 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, 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 } diff --git a/docs/embed-rust.md b/docs/embed-rust.md index 34d837a1f5..3701871d0b 100644 --- a/docs/embed-rust.md +++ b/docs/embed-rust.md @@ -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()); +} +``` diff --git a/lightbeam/src/microwasm.rs b/lightbeam/src/microwasm.rs index 6d485de51c..dd304e03ef 100644 --- a/lightbeam/src/microwasm.rs +++ b/lightbeam/src/microwasm.rs @@ -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 diff --git a/lightbeam/src/tests.rs b/lightbeam/src/tests.rs index a832062ec2..1fa6551106 100644 --- a/lightbeam/src/tests.rs +++ b/lightbeam/src/tests.rs @@ -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,))); } diff --git a/misc/wasmtime-py/src/import.rs b/misc/wasmtime-py/src/import.rs index 9268bc5b07..41c903ec8a 100644 --- a/misc/wasmtime-py/src/import.rs +++ b/misc/wasmtime-py/src/import.rs @@ -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() } diff --git a/src/bin/wasmtime.rs b/src/bin/wasmtime.rs index 0b26977712..3bf755a55f 100644 --- a/src/bin/wasmtime.rs +++ b/src/bin/wasmtime.rs @@ -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: &HostRef, module_registry: &HashMap)>, path: &Path, ) -> Result<(HostRef, HostRef, Vec)> { // 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::, _>>()?; - 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: &HostRef, module_registry: &HashMap)>, 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: &HostRef, instance: HostRef, data: &ModuleData, name: &str, diff --git a/tests/wast_testsuites.rs b/tests/wast_testsuites.rs index d319332ae8..69d0d0328e 100644 --- a/tests/wast_testsuites.rs +++ b/tests/wast_testsuites.rs @@ -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 { let mut flag_builder = settings::builder(); flag_builder.enable("enable_verifier").unwrap(); diff --git a/wasmtime-api/Cargo.toml b/wasmtime-api/Cargo.toml index 76ad9c5da6..0a89fd71e5 100644 --- a/wasmtime-api/Cargo.toml +++ b/wasmtime-api/Cargo.toml @@ -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] diff --git a/wasmtime-api/c-examples/Makefile b/wasmtime-api/c-examples/Makefile index 9fa9351753..d3ef772c46 100644 --- a/wasmtime-api/c-examples/Makefile +++ b/wasmtime-api/c-examples/Makefile @@ -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) diff --git a/wasmtime-api/examples/gcd.rs b/wasmtime-api/examples/gcd.rs index 0e71279496..c15df5a53f 100644 --- a/wasmtime-api/examples/gcd.rs +++ b/wasmtime-api/examples/gcd.rs @@ -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] diff --git a/wasmtime-api/examples/hello.rs b/wasmtime-api/examples/hello.rs index 1c1bc8ba8a..4f367cdd70 100644 --- a/wasmtime-api/examples/hello.rs +++ b/wasmtime-api/examples/hello.rs @@ -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!")?, ); diff --git a/wasmtime-api/examples/memory.rs b/wasmtime-api/examples/memory.rs index 7d9be3099c..6ca8f9c125 100644 --- a/wasmtime-api/examples/memory.rs +++ b/wasmtime-api/examples/memory.rs @@ -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); diff --git a/wasmtime-api/examples/multi.rs b/wasmtime-api/examples/multi.rs index 4bda0b48c3..98a80f5087 100644 --- a/wasmtime-api/examples/multi.rs +++ b/wasmtime-api/examples/multi.rs @@ -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!")?, ); diff --git a/wasmtime-api/src/callable.rs b/wasmtime-api/src/callable.rs index 0de7726fb5..a5838f6b2b 100644 --- a/wasmtime-api/src/callable.rs +++ b/wasmtime-api/src/callable.rs @@ -34,9 +34,9 @@ pub(crate) struct WasmtimeFn { } impl WasmtimeFn { - pub fn new(store: HostRef, instance: InstanceHandle, export: Export) -> WasmtimeFn { + pub fn new(store: &HostRef, instance: InstanceHandle, export: Export) -> WasmtimeFn { WasmtimeFn { - store, + store: store.clone(), instance, export, } diff --git a/wasmtime-api/src/externals.rs b/wasmtime-api/src/externals.rs index fbdab1cfb6..56aca24ad5 100644 --- a/wasmtime-api/src/externals.rs +++ b/wasmtime-api/src/externals.rs @@ -66,7 +66,7 @@ impl Extern { } pub(crate) fn from_wasmtime_export( - store: HostRef, + store: &HostRef, instance_handle: InstanceHandle, export: wasmtime_runtime::Export, ) -> Extern { @@ -118,18 +118,18 @@ pub struct Func { } impl Func { - pub fn new(store: HostRef, ty: FuncType, callable: Rc) -> Self { + pub fn new(store: &HostRef, ty: FuncType, callable: Rc) -> Self { let callable = Rc::new(NativeCallable::new(callable, &ty, &store)); Func::from_wrapped(store, ty, callable) } fn from_wrapped( - store: HostRef, + store: &HostRef, r#type: FuncType, callable: Rc, ) -> 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: &HostRef, 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, r#type: GlobalType, val: Val) -> Global { + pub fn new(store: &HostRef, 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: &HostRef, ) -> 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, r#type: TableType, init: Val) -> Table { + pub fn new(store: &HostRef, 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: &HostRef, 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, r#type: MemoryType) -> Memory { + pub fn new(store: &HostRef, 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: &HostRef, 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, diff --git a/wasmtime-api/src/instance.rs b/wasmtime-api/src/instance.rs index d8df3f8b89..0dcb420f15 100644 --- a/wasmtime-api/src/instance.rs +++ b/wasmtime-api/src/instance.rs @@ -61,8 +61,8 @@ pub struct Instance { impl Instance { pub fn new( - store: HostRef, - module: HostRef, + store: &HostRef, + module: &HostRef, externs: &[Extern], ) -> Result { 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: &HostRef, instance_handle: InstanceHandle, ) -> Result<(Instance, HashMap)> { 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(), )); diff --git a/wasmtime-api/src/lib.rs b/wasmtime-api/src/lib.rs index 46866ea65d..866c17c8f8 100644 --- a/wasmtime-api/src/lib.rs +++ b/wasmtime-api/src/lib.rs @@ -14,7 +14,6 @@ mod trap; mod types; mod values; -#[cfg(feature = "wasm-c-api")] pub mod wasm; #[macro_use] diff --git a/wasmtime-api/src/module.rs b/wasmtime-api/src/module.rs index b70183c00b..c7683a74f0 100644 --- a/wasmtime-api/src/module.rs +++ b/wasmtime-api/src/module.rs @@ -182,10 +182,10 @@ pub struct Module { } impl Module { - pub fn new(store: HostRef, binary: &[u8]) -> Result { + pub fn new(store: &HostRef, binary: &[u8]) -> Result { let (imports, exports) = read_imports_and_exports(binary)?; Ok(Module { - store, + store: store.clone(), binary: binary.into(), imports, exports, diff --git a/wasmtime-api/src/runtime.rs b/wasmtime-api/src/runtime.rs index e3e0f4359a..a5c35cc0f8 100644 --- a/wasmtime-api/src/runtime.rs +++ b/wasmtime-api/src/runtime.rs @@ -102,13 +102,13 @@ pub struct Store { } impl Store { - pub fn new(engine: HostRef) -> Store { + pub fn new(engine: &HostRef) -> 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(), diff --git a/wasmtime-api/src/trampoline/func.rs b/wasmtime-api/src/trampoline/func.rs index 1ed333f936..95b17631ff 100644 --- a/wasmtime-api/src/trampoline/func.rs +++ b/wasmtime-api/src/trampoline/func.rs @@ -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() } diff --git a/wasmtime-api/src/values.rs b/wasmtime-api/src/values.rs index 4dc762767a..2329d980ca 100644 --- a/wasmtime-api/src/values.rs +++ b/wasmtime-api/src/values.rs @@ -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)) } diff --git a/wasmtime-api/src/wasm.rs b/wasmtime-api/src/wasm.rs index e9205b121f..ad4563372f 100644 --- a/wasmtime-api/src/wasm.rs +++ b/wasmtime-api/src/wasm.rs @@ -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 = 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, ) -> *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) diff --git a/wasmtime-api/tests/import_calling_export.rs b/wasmtime-api/tests/import_calling_export.rs index deee58ce4a..8e49387d85 100644 --- a/wasmtime-api/tests/import_calling_export.rs +++ b/wasmtime-api/tests/import_calling_export.rs @@ -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()); diff --git a/wasmtime-environ/src/cache/tests.rs b/wasmtime-environ/src/cache/tests.rs index e1b006bf03..7e9632da89 100644 --- a/wasmtime-environ/src/cache/tests.rs +++ b/wasmtime-environ/src/cache/tests.rs @@ -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(); diff --git a/wasmtime-environ/src/compilation.rs b/wasmtime-environ/src/compilation.rs index 8be354ba79..797d7d82a7 100644 --- a/wasmtime-environ/src/compilation.rs +++ b/wasmtime-environ/src/compilation.rs @@ -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, /// The jump tables offsets (in the body). pub jt_offsets: ir::JumpTableOffsets, + + /// The unwind information. + pub unwind_info: Vec, } -type Functions = PrimaryMap; +type Functions = PrimaryMap; /// 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, - functions: impl IntoIterator, ir::JumpTableOffsets)>, + functions: impl IntoIterator, ir::JumpTableOffsets, Range)>, ) -> 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 { self.functions .iter() - .map(|(_, code_and_jt)| code_and_jt.jt_offsets.clone()) + .map(|(_, func)| func.jt_offsets.clone()) .collect::>() } } @@ -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.iterator.next().map(|(_, b)| b) diff --git a/wasmtime-environ/src/cranelift.rs b/wasmtime-environ/src/cranelift.rs index 02002b0853..e96b6d1e3d 100644 --- a/wasmtime-environ/src/cranelift.rs +++ b/wasmtime-environ/src/cranelift.rs @@ -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 = 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 { diff --git a/wasmtime-environ/src/lib.rs b/wasmtime-environ/src/lib.rs index c23e97b357..8c27157173 100644 --- a/wasmtime-environ/src/lib.rs +++ b/wasmtime-environ/src/lib.rs @@ -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; diff --git a/wasmtime-environ/src/lightbeam.rs b/wasmtime-environ/src/lightbeam.rs index 4407d0b35f..23e1c013bb 100644 --- a/wasmtime-environ/src/lightbeam.rs +++ b/wasmtime-environ/src/lightbeam.rs @@ -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), diff --git a/wasmtime-jit/Cargo.toml b/wasmtime-jit/Cargo.toml index 059fae0472..96d2e3c4ed 100644 --- a/wasmtime-jit/Cargo.toml +++ b/wasmtime-jit/Cargo.toml @@ -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"] diff --git a/wasmtime-jit/src/code_memory.rs b/wasmtime-jit/src/code_memory.rs index 4a9b3a3142..9cd3f5a296 100644 --- a/wasmtime-jit/src/code_memory.rs +++ b/wasmtime-jit/src/code_memory.rs @@ -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, + 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, 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(), + ), + ); - /// 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, 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)); + if previous.0.len() > 0 { + self.mmaps.push(previous); + } else { + assert!(previous.1.len() == 0); } - 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(()) } } diff --git a/wasmtime-jit/src/compiler.rs b/wasmtime-jit/src/compiler.rs index a54b640d84..993ba050e6 100644 --- a/wasmtime-jit/src/compiler.rs +++ b/wasmtime-jit/src/compiler.rs @@ -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 = 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, 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::>(); - 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() { diff --git a/wasmtime-jit/src/function_table.rs b/wasmtime-jit/src/function_table.rs new file mode 100644 index 0000000000..3ec8bcd0da --- /dev/null +++ b/wasmtime-jit/src/function_table.rs @@ -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, + 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()); + } + } + } +} diff --git a/wasmtime-jit/src/lib.rs b/wasmtime-jit/src/lib.rs index 79aafb456f..7400aa627a 100644 --- a/wasmtime-jit/src/lib.rs +++ b/wasmtime-jit/src/lib.rs @@ -34,6 +34,7 @@ mod action; mod code_memory; mod compiler; mod context; +mod function_table; mod instantiate; mod link; mod namespace; diff --git a/wasmtime-runtime/signalhandlers/SignalHandlers.cpp b/wasmtime-runtime/signalhandlers/SignalHandlers.cpp index f82fba6b27..ef9200a440 100644 --- a/wasmtime-runtime/signalhandlers/SignalHandlers.cpp +++ b/wasmtime-runtime/signalhandlers/SignalHandlers.cpp @@ -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))) { + if (HandleTrap(static_cast(context), false)) { return; } } diff --git a/wasmtime-runtime/signalhandlers/SignalHandlers.hpp b/wasmtime-runtime/signalhandlers/SignalHandlers.hpp index 4c5c4d1e4c..cd0c9e4cdb 100644 --- a/wasmtime-runtime/signalhandlers/SignalHandlers.hpp +++ b/wasmtime-runtime/signalhandlers/SignalHandlers.hpp @@ -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*); diff --git a/wasmtime-runtime/src/traphandlers.rs b/wasmtime-runtime/src/traphandlers.rs index 8c8b32b7e4..5ddb3563c5 100644 --- a/wasmtime-runtime/src/traphandlers.rs +++ b/wasmtime-runtime/src/traphandlers.rs @@ -21,6 +21,7 @@ extern "C" { thread_local! { static RECORDED_TRAP: Cell> = Cell::new(None); static JMP_BUF: Cell<*const u8> = Cell::new(ptr::null()); + static RESET_GUARD_PAGE: Cell = 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))