From 7f997fe7a66de510c41d9582c74ca973241b5d79 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 14 Jan 2020 11:41:03 -0600 Subject: [PATCH 1/9] Fix CI after merge (#817) --- crates/api/tests/invoke_func_via_table.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/api/tests/invoke_func_via_table.rs b/crates/api/tests/invoke_func_via_table.rs index c10d696ef2..9b64e894d8 100644 --- a/crates/api/tests/invoke_func_via_table.rs +++ b/crates/api/tests/invoke_func_via_table.rs @@ -16,7 +16,7 @@ fn test_invoke_func_via_table() -> Result<()> { "#, )?; let module = Module::new(&store, &binary).context("> Error compiling module!")?; - let instance = Instance::new(&store, &module, &[]).context("> Error instantiating module!")?; + let instance = Instance::new(&module, &[]).context("> Error instantiating module!")?; let f = instance .find_export_by_name("table") From 364fa994ede38048cc496b1c58d15271f5352fbc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 14 Jan 2020 13:36:57 -0600 Subject: [PATCH 2/9] Move the C API to a separate crate (#818) * Move the C API to a separate crate This commit moves the C API from `crates/api/src/wasm.rs` to `crates/capi/src/lib.rs` to be located in a separate crate. There's a number of reasons for this: * When a Rust program depends on the `wasmtime` crate, there's no need to compile in the C API. * This should improve compile times of the `wasmtime` crate since it's not producing artifacts which aren't always used. * The development of the C API can be guaranteed to only use the public API of the `wasmtime` crate itself. Some CI pieces are tweaked and this overall shouldn't have much impact on users, it's intended that it's a cleanup/speedup for developers! * Disable rustdoc/tests for capi * Review feedback * Add back in accidentally deleted comment * More renamings * Try to fix dotnet build --- .github/workflows/main.yml | 2 +- .gitmodules | 4 +- Cargo.lock | 7 + Cargo.toml | 1 + ci/build-tarballs.sh | 2 +- crates/api/Cargo.toml | 4 - crates/api/README.md | 9 +- crates/api/src/lib.rs | 4 +- crates/api/src/runtime.rs | 8 +- crates/c-api/Cargo.toml | 20 ++ crates/c-api/LICENSE | 220 ++++++++++++++++++ crates/c-api/README.md | 3 + .../c-examples => c-api/examples}/Makefile | 0 .../c-examples => c-api/examples}/wasm-c-api | 0 crates/{api/src/wasm.rs => c-api/src/lib.rs} | 27 ++- crates/misc/dotnet/Directory.Build.props | 3 +- crates/misc/dotnet/Directory.Build.targets | 4 +- 17 files changed, 289 insertions(+), 29 deletions(-) create mode 100644 crates/c-api/Cargo.toml create mode 100644 crates/c-api/LICENSE create mode 100644 crates/c-api/README.md rename crates/{api/c-examples => c-api/examples}/Makefile (100%) rename crates/{api/c-examples => c-api/examples}/wasm-c-api (100%) rename crates/{api/src/wasm.rs => c-api/src/lib.rs} (99%) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8ac002a4cd..192e9f9141 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -302,7 +302,7 @@ jobs: - run: $CENTOS cargo build --release --bin wasmtime --bin wasm2obj shell: bash # Build `libwasmtime.so` - - run: $CENTOS cargo build --release --manifest-path crates/api/Cargo.toml + - run: $CENTOS cargo build --release --manifest-path crates/c-api/Cargo.toml shell: bash # Test what we just built - run: $CENTOS cargo test --features test_programs --release --all --exclude lightbeam --exclude wasmtime --exclude wasmtime-fuzzing diff --git a/.gitmodules b/.gitmodules index 2bb139cd81..59646f986c 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,8 +1,8 @@ [submodule "spec_testsuite"] path = tests/spec_testsuite url = https://github.com/WebAssembly/testsuite -[submodule "crates/api/c-examples/wasm-c-api"] - path = crates/api/c-examples/wasm-c-api +[submodule "crates/c-api/examples/wasm-c-api"] + path = crates/c-api/examples/wasm-c-api url = https://github.com/WebAssembly/wasm-c-api [submodule "crates/wasi-common/WASI"] path = crates/wasi-common/wig/WASI diff --git a/Cargo.lock b/Cargo.lock index d8b036cd0f..f7aa569109 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2007,6 +2007,13 @@ dependencies = [ "winapi", ] +[[package]] +name = "wasmtime-c-api" +version = "0.9.0" +dependencies = [ + "wasmtime", +] + [[package]] name = "wasmtime-cli" version = "0.9.0" diff --git a/Cargo.toml b/Cargo.toml index 378189e3c2..09bdaa1cfe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "crates/fuzzing", "crates/misc/rust", "crates/misc/py", + "crates/c-api", "fuzz", ] diff --git a/ci/build-tarballs.sh b/ci/build-tarballs.sh index b998bb93c9..433450116b 100755 --- a/ci/build-tarballs.sh +++ b/ci/build-tarballs.sh @@ -48,7 +48,7 @@ mkdir tmp/$api_pkgname/lib mkdir tmp/$api_pkgname/include cp LICENSE README.md tmp/$api_pkgname mv bins-$src/* tmp/$api_pkgname/lib -cp crates/api/c-examples/wasm-c-api/include/wasm.h tmp/$api_pkgname/include +cp crates/c-api/examples/wasm-c-api/include/wasm.h tmp/$api_pkgname/include mktarball $api_pkgname # Move wheels to dist folder diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index 67961e0299..f524f3d036 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -8,10 +8,6 @@ repository = "https://github.com/bytecodealliance/wasmtime" readme = "README.md" edition = "2018" -[lib] -name = "wasmtime" -crate-type = ["lib", "staticlib", "cdylib"] - [dependencies] wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-environ = { path = "../environ", version = "0.9.0" } diff --git a/crates/api/README.md b/crates/api/README.md index b0edded9c2..49ca113c15 100644 --- a/crates/api/README.md +++ b/crates/api/README.md @@ -1,3 +1,8 @@ -# Implementation of wasm-c-api in Rust +## Wasmtime Embedding API -https://github.com/WebAssembly/wasm-c-api +The `wasmtime` crate is an embedding API of the `wasmtime` WebAssembly runtime. +This is intended to be used in Rust projects and provides a high-level API of +working with WebAssembly modules. + +If you're interested in embedding `wasmtime` in other languages, you may wish to +take a look a the [C embedding API](../c-api) instead! diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index 7ccf342ca5..d02a2c16a6 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -18,13 +18,11 @@ mod trap; mod types; mod values; -pub mod wasm; - pub use crate::callable::Callable; pub use crate::externals::*; pub use crate::instance::Instance; pub use crate::module::Module; -pub use crate::r#ref::AnyRef; +pub use crate::r#ref::{AnyRef, HostInfo, HostRef}; pub use crate::runtime::{Config, Engine, OptLevel, Store, Strategy}; pub use crate::trap::{FrameInfo, Trap}; pub use crate::types::*; diff --git a/crates/api/src/runtime.rs b/crates/api/src/runtime.rs index 7476bc44c9..76a599d7c2 100644 --- a/crates/api/src/runtime.rs +++ b/crates/api/src/runtime.rs @@ -398,7 +398,13 @@ impl Store { .cloned() } - pub(crate) fn ptr_eq(a: &Store, b: &Store) -> bool { + /// Returns whether the stores `a` and `b` refer to the same underlying + /// `Store`. + /// + /// Because the `Store` type is reference counted multiple clones may point + /// to the same underlying storage, and this method can be used to determine + /// whether two stores are indeed the same. + pub fn same(a: &Store, b: &Store) -> bool { Rc::ptr_eq(&a.inner, &b.inner) } } diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml new file mode 100644 index 0000000000..9c1a820cb2 --- /dev/null +++ b/crates/c-api/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wasmtime-c-api" +version = "0.9.0" +authors = ["The Wasmtime Project Developers"] +description = "C API to expose the Wasmtime runtime" +license = "Apache-2.0 WITH LLVM-exception" +repository = "https://github.com/bytecodealliance/wasmtime" +readme = "README.md" +edition = "2018" +publish = false + +[lib] +name = "wasmtime" +crate-type = ["staticlib", "cdylib"] +doc = false +test = false +doctest = false + +[dependencies] +wasmtime = { path = "../api" } diff --git a/crates/c-api/LICENSE b/crates/c-api/LICENSE new file mode 100644 index 0000000000..f9d81955f4 --- /dev/null +++ b/crates/c-api/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/crates/c-api/README.md b/crates/c-api/README.md new file mode 100644 index 0000000000..b0edded9c2 --- /dev/null +++ b/crates/c-api/README.md @@ -0,0 +1,3 @@ +# Implementation of wasm-c-api in Rust + +https://github.com/WebAssembly/wasm-c-api diff --git a/crates/api/c-examples/Makefile b/crates/c-api/examples/Makefile similarity index 100% rename from crates/api/c-examples/Makefile rename to crates/c-api/examples/Makefile diff --git a/crates/api/c-examples/wasm-c-api b/crates/c-api/examples/wasm-c-api similarity index 100% rename from crates/api/c-examples/wasm-c-api rename to crates/c-api/examples/wasm-c-api diff --git a/crates/api/src/wasm.rs b/crates/c-api/src/lib.rs similarity index 99% rename from crates/api/src/wasm.rs rename to crates/c-api/src/lib.rs index 083671406f..ea647d207d 100644 --- a/crates/api/src/wasm.rs +++ b/crates/c-api/src/lib.rs @@ -5,15 +5,14 @@ // TODO complete the C API -use super::{ - AnyRef, Callable, Engine, ExportType, Extern, ExternType, Func, FuncType, Global, GlobalType, - ImportType, Instance, Limits, Memory, MemoryType, Module, Store, Table, TableType, Trap, Val, - ValType, -}; -use crate::r#ref::{HostInfo, HostRef}; use std::cell::RefCell; use std::rc::Rc; use std::{mem, ptr, slice}; +use wasmtime::{ + AnyRef, Callable, Engine, ExportType, Extern, ExternType, Func, FuncType, Global, GlobalType, + HostInfo, HostRef, ImportType, Instance, Limits, Memory, MemoryType, Module, Store, Table, + TableType, Trap, Val, ValType, +}; macro_rules! declare_vec { ($name:ident, $elem_ty:path) => { @@ -569,14 +568,18 @@ impl wasm_val_t { } } -impl Callable for wasm_func_callback_t { +struct Callback { + callback: wasm_func_callback_t, +} + +impl Callable for Callback { fn call(&self, params: &[Val], results: &mut [Val]) -> Result<(), Trap> { let params = params .iter() .map(|p| wasm_val_t::from_val(p)) .collect::>(); let mut out_results = vec![wasm_val_t::default(); results.len()]; - let func = self.expect("wasm_func_callback_t fn"); + let func = self.callback.expect("wasm_func_callback_t fn"); let out = unsafe { func(params.as_ptr(), out_results.as_mut_ptr()) }; if !out.is_null() { let trap: Box = unsafe { Box::from_raw(out) }; @@ -633,7 +636,7 @@ pub unsafe extern "C" fn wasm_func_new( ) -> *mut wasm_func_t { let store = &(*store).store.borrow(); let ty = (*ty).functype.clone(); - let callback = Rc::new(callback); + let callback = Rc::new(Callback { callback }); let func = Box::new(wasm_func_t { ext: wasm_extern_t { which: ExternHost::Func(HostRef::new(Func::new(store, ty, callback))), @@ -700,7 +703,7 @@ pub unsafe extern "C" fn wasm_instance_new( let module = &(*module).module.borrow(); // FIXME(WebAssembly/wasm-c-api#126) what else can we do with the `store` // argument? - if !Store::ptr_eq(&store, module.store()) { + if !Store::same(&store, module.store()) { if !result.is_null() { let trap = Trap::new("wasm_store_t must match store in wasm_module_t"); let trap = Box::new(wasm_trap_t { @@ -1254,7 +1257,7 @@ pub unsafe extern "C" fn wasm_globaltype_content( pub unsafe extern "C" fn wasm_globaltype_mutability( gt: *const wasm_globaltype_t, ) -> wasm_mutability_t { - use super::Mutability::*; + use wasmtime::Mutability::*; match (*gt).globaltype.mutability() { Const => 0, Var => 1, @@ -1411,7 +1414,7 @@ pub unsafe extern "C" fn wasm_globaltype_new( ty: *mut wasm_valtype_t, mutability: wasm_mutability_t, ) -> *mut wasm_globaltype_t { - use super::Mutability::*; + use wasmtime::Mutability::*; let ty = Box::from_raw(ty); let mutability = match mutability { 0 => Const, diff --git a/crates/misc/dotnet/Directory.Build.props b/crates/misc/dotnet/Directory.Build.props index b652414ca0..1f08f77261 100644 --- a/crates/misc/dotnet/Directory.Build.props +++ b/crates/misc/dotnet/Directory.Build.props @@ -1,6 +1,7 @@ - 0.8.0 + 0.8.0 wasmtime + wasmtime-c-api diff --git a/crates/misc/dotnet/Directory.Build.targets b/crates/misc/dotnet/Directory.Build.targets index fd577ba0c8..07c6116112 100644 --- a/crates/misc/dotnet/Directory.Build.targets +++ b/crates/misc/dotnet/Directory.Build.targets @@ -6,7 +6,7 @@ .so $(LibraryPrefix)$(WasmtimeLibraryName)$(LibraryExtension) $(MSBuildThisFileDirectory)../../../target/$(Configuration.ToLower()) - cargo build --release -p $(WasmtimeLibraryName) - cargo build -p $(WasmtimeLibraryName) + cargo build --release -p $(WasmtimePackageName) + cargo build -p $(WasmtimePackageName) From 4208c7f8a26745e064344bb3bb410a7fe033b648 Mon Sep 17 00:00:00 2001 From: Alex Wu Date: Wed, 15 Jan 2020 12:22:39 +0800 Subject: [PATCH 3/9] fix build error for using the optional 'wasi-c' feature --- src/commands/run.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/run.rs b/src/commands/run.rs index 4426245c71..313e837017 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -17,8 +17,7 @@ use wasmtime_interface_types::ModuleData; use wasmtime_wasi::{ create_wasi_instance, old::snapshot_0::create_wasi_instance as create_wasi_instance_snapshot_0, }; -#[cfg(feature = "wasi-c")] -use wasmtime_wasi_c::instantiate_wasi_c; + #[cfg(feature = "wasi-c")] use wasmtime_wasi_c::instantiate_wasi_c; @@ -152,7 +151,8 @@ impl RunCommand { #[cfg(feature = "wasi-c")] { let global_exports = store.global_exports().clone(); - let handle = instantiate_wasi_c(global_exports, &preopen_dirs, &argv, &self.vars)?; + let handle = + instantiate_wasi_c("", global_exports, &preopen_dirs, &argv, &self.vars)?; Instance::from_handle(&store, handle) } #[cfg(not(feature = "wasi-c"))] From 2a50701f0a94f2c5f1c34c0e7289ece6c9483631 Mon Sep 17 00:00:00 2001 From: Yury Delendik Date: Wed, 15 Jan 2020 13:48:24 -0600 Subject: [PATCH 4/9] Backtrace WebAssembly function JIT frames (#759) * Create backtrace * Extend unwind information with FDE data. * Expose backtrace via API/Trap * wasmtime_call returns not-str * Return Arc * rename frame -> function * Fix windows crashes and unwrap UNWIND_HISTORY_TABLE * mmaps -> entries * pass a backtrace in ActionOutcome * add test_trap_stack_overflow * Update cranelift version. --- Cargo.lock | 42 +++--- crates/api/src/callable.rs | 10 +- crates/api/src/trampoline/func.rs | 8 +- crates/api/src/trap.rs | 44 +++++- crates/api/tests/traps.rs | 113 +++++++++++++++ crates/environ/Cargo.toml | 8 +- crates/environ/src/cache/tests.rs | 6 +- crates/environ/src/compilation.rs | 129 ++++++++++++++++- crates/environ/src/cranelift.rs | 7 +- crates/environ/src/func_environ.rs | 4 +- crates/environ/src/lib.rs | 5 +- crates/jit/Cargo.toml | 10 +- crates/jit/src/action.rs | 11 +- crates/jit/src/code_memory.rs | 86 +++++++---- crates/jit/src/compiler.rs | 31 +++- crates/jit/src/function_table.rs | 149 +++++++++++++++----- crates/lightbeam/Cargo.toml | 2 +- crates/runtime/Cargo.toml | 1 + crates/runtime/src/backtrace.rs | 148 +++++++++++++++++++ crates/runtime/src/instance.rs | 4 +- crates/runtime/src/jit_function_registry.rs | 83 +++++++++++ crates/runtime/src/lib.rs | 5 +- crates/runtime/src/traphandlers.rs | 32 +++-- crates/wasi-c/Cargo.toml | 6 +- crates/wasi/Cargo.toml | 6 +- scripts/cranelift-version.sh | 2 +- 26 files changed, 803 insertions(+), 149 deletions(-) create mode 100644 crates/runtime/src/backtrace.rs create mode 100644 crates/runtime/src/jit_function_registry.rs diff --git a/Cargo.lock b/Cargo.lock index f7aa569109..0b60270a40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,24 +341,25 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd3225fff1be118941c5fb66f1fb1f7f3e86468fac0e7364713c4fb99b72632b" +checksum = "40af6e6f7419110906d0f7b4b8084d3216be64d7da77aa12887885ebe0fc2776" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3e3e6679892029f76a99b9059d2b74e77ac03637d573bb014bc21579ec1b7da" +checksum = "88583eb22e5cd0fe1ef66f0b80d0063d21d30e84e887d08b3d369def3ea7b4be" dependencies = [ "byteorder", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", + "gimli", "log", "serde", "smallvec", @@ -368,9 +369,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cabe691548e28ca82ebd218f2fe76eec4c5629b64ef3db8b58443b7d9047275" +checksum = "353872c984943b9134d7c835eb9d12932bd90f4992dbe666593771bee920d673" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -378,24 +379,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d173252ffade4aa6e929090977b9a4cd5ac28e15a42626f878be3844b3000ad9" +checksum = "cd1e96479a56981cce5c8f14d26773195d662ccdbbeca39fb8eba22b5ca8ea6a" [[package]] name = "cranelift-entity" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3498ad9ba021715716a1c52e2b31d7829a149913fb0d88493e4b07d3b772b656" +checksum = "37bc8dc3d4274ededc687d84821f491a8a03447dbb7481983936220892cf55b4" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521a30773b8de74345c807a38853f055aca8fecaa39a0fc7698bfebc5a3da515" +checksum = "4ea33e55bda8c425f3f533355b03e3a43cf29b4e228b35b868b6a1c43b6a139e" dependencies = [ "cranelift-codegen", "log", @@ -405,9 +406,9 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "624e755cbe984e437308968239736e7f9aa3193e99928fb76eec7a1946627b34" +checksum = "2c22cfaaa5e69eddaddd6cfa7a76233de964b9b2245e81a5f47dae739931ad0d" dependencies = [ "cranelift-codegen", "raw-cpuid", @@ -416,9 +417,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.54.0" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0320733e518ab9e0e2d1a22034d40e2851fb403ed14db5220cf9b86576b9cfd4" +checksum = "038f8fd636abc83ccd6f110e0891766521c3599b52173c0f37e61c684f19a15d" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -426,7 +427,7 @@ dependencies = [ "log", "serde", "thiserror", - "wasmparser 0.45.2", + "wasmparser 0.47.0", ] [[package]] @@ -1964,12 +1965,6 @@ version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1527c84a5bd585215f29c06b0e2a5274e478ad4dfc970d26ffad66fdc6cb311d" -[[package]] -name = "wasmparser" -version = "0.45.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b4eab1d9971d0803729cba3617b56eb04fcb4bd25361cb63880ed41a42f20d5" - [[package]] name = "wasmparser" version = "0.47.0" @@ -2188,6 +2183,7 @@ dependencies = [ name = "wasmtime-runtime" version = "0.9.0" dependencies = [ + "backtrace", "cc", "cfg-if", "indexmap", diff --git a/crates/api/src/callable.rs b/crates/api/src/callable.rs index 139959f359..545eafde80 100644 --- a/crates/api/src/callable.rs +++ b/crates/api/src/callable.rs @@ -147,7 +147,7 @@ impl WrappedCallable for WasmtimeFn { .map_err(|e| Trap::new(format!("trampoline error: {:?}", e)))?; // Call the trampoline. - if let Err(message) = unsafe { + if let Err(error) = unsafe { self.instance.with_signals_on(|| { wasmtime_runtime::wasmtime_call_trampoline( vmctx, @@ -156,8 +156,12 @@ impl WrappedCallable for WasmtimeFn { ) }) } { - let trap = - take_api_trap().unwrap_or_else(|| Trap::new(format!("call error: {}", message))); + let message = error.0; + let backtrace = error.1; + + let trap = take_api_trap().unwrap_or_else(|| { + Trap::new_with_trace(format!("call error: {}", message), backtrace) + }); return Err(trap); } diff --git a/crates/api/src/trampoline/func.rs b/crates/api/src/trampoline/func.rs index 328a325490..f5d7dde5c3 100644 --- a/crates/api/src/trampoline/func.rs +++ b/crates/api/src/trampoline/func.rs @@ -11,7 +11,9 @@ use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::ir::types; use wasmtime_environ::isa::TargetIsa; use wasmtime_environ::wasm::{DefinedFuncIndex, FuncIndex}; -use wasmtime_environ::{ir, settings, CompiledFunction, Export, Module, TrapInformation}; +use wasmtime_environ::{ + ir, settings, CompiledFunction, CompiledFunctionUnwindInfo, Export, Module, TrapInformation, +}; use wasmtime_jit::trampoline::ir::{ ExternalName, Function, InstBuilder, MemFlags, StackSlotData, StackSlotKind, }; @@ -136,6 +138,7 @@ fn make_trampoline( let mut context = Context::new(); context.func = Function::with_name_signature(ExternalName::user(0, 0), signature.clone()); + context.func.collect_frame_layout_info(); let ss = context.func.create_stack_slot(StackSlotData::new( StackSlotKind::ExplicitSlot, @@ -213,8 +216,7 @@ fn make_trampoline( .map_err(|error| pretty_error(&context.func, Some(isa), error)) .expect("compile_and_emit"); - let mut unwind_info = Vec::new(); - context.emit_unwind_info(isa, &mut unwind_info); + let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context); let traps = trap_sink.traps; diff --git a/crates/api/src/trap.rs b/crates/api/src/trap.rs index 5127d1c5c1..ae74c00af3 100644 --- a/crates/api/src/trap.rs +++ b/crates/api/src/trap.rs @@ -1,6 +1,7 @@ use crate::instance::Instance; use std::fmt; use std::sync::Arc; +use wasmtime_runtime::{get_backtrace, Backtrace, BacktraceFrame}; /// A struct representing an aborted instruction execution, with a message /// indicating the cause. @@ -26,10 +27,21 @@ impl Trap { /// assert_eq!("unexpected error", trap.message()); /// ``` pub fn new>(message: I) -> Self { + Self::new_with_trace(message, get_backtrace()) + } + + pub(crate) fn new_with_trace>(message: I, backtrace: Backtrace) -> Self { + let mut trace = Vec::with_capacity(backtrace.len()); + for i in 0..backtrace.len() { + // Don't include frames without backtrace info. + if let Some(info) = FrameInfo::try_from(backtrace[i]) { + trace.push(info); + } + } Trap { inner: Arc::new(TrapInner { message: message.into(), - trace: Vec::new(), + trace, }), } } @@ -62,22 +74,42 @@ impl fmt::Display for Trap { impl std::error::Error for Trap {} #[derive(Debug)] -pub struct FrameInfo; +pub struct FrameInfo { + module_name: Option, + func_index: u32, +} impl FrameInfo { pub fn instance(&self) -> *const Instance { unimplemented!("FrameInfo::instance"); } - pub fn func_index() -> usize { - unimplemented!("FrameInfo::func_index"); + pub fn func_index(&self) -> u32 { + self.func_index } - pub fn func_offset() -> usize { + pub fn func_offset(&self) -> usize { unimplemented!("FrameInfo::func_offset"); } - pub fn module_offset() -> usize { + pub fn module_offset(&self) -> usize { unimplemented!("FrameInfo::module_offset"); } + + pub fn module_name(&self) -> Option<&str> { + self.module_name.as_deref() + } + + pub(crate) fn try_from(backtrace: BacktraceFrame) -> Option { + if let Some(tag) = backtrace.tag() { + let func_index = tag.func_index as u32; + let module_name = tag.module_id.clone(); + Some(FrameInfo { + func_index, + module_name, + }) + } else { + None + } + } } diff --git a/crates/api/tests/traps.rs b/crates/api/tests/traps.rs index aea6404a9b..6f9cdc582b 100644 --- a/crates/api/tests/traps.rs +++ b/crates/api/tests/traps.rs @@ -41,3 +41,116 @@ fn test_trap_return() -> Result<(), String> { Ok(()) } + +#[test] +fn test_trap_trace() -> Result<(), String> { + let store = Store::default(); + let binary = parse_str( + r#" + (module $hello_mod + (func (export "run") (call $hello)) + (func $hello (unreachable)) + ) + "#, + ) + .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + + let module = + Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; + let instance = Instance::new(&module, &[]) + .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let run_func = instance.exports()[0] + .func() + .expect("expected function export"); + + let e = run_func.call(&[]).err().expect("error calling function"); + + let trace = e.trace(); + assert_eq!(trace.len(), 2); + assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); + assert_eq!(trace[0].func_index(), 1); + assert_eq!(trace[1].module_name().unwrap(), "hello_mod"); + assert_eq!(trace[1].func_index(), 0); + assert!(e.message().contains("unreachable")); + + Ok(()) +} + +#[test] +fn test_trap_trace_cb() -> Result<(), String> { + struct ThrowCallback; + + impl Callable for ThrowCallback { + fn call(&self, _params: &[Val], _results: &mut [Val]) -> Result<(), Trap> { + Err(Trap::new("cb throw")) + } + } + + let store = Store::default(); + let binary = parse_str( + r#" + (module $hello_mod + (import "" "throw" (func $throw)) + (func (export "run") (call $hello)) + (func $hello (call $throw)) + ) + "#, + ) + .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + + let fn_type = FuncType::new(Box::new([]), Box::new([])); + let fn_func = Func::new(&store, fn_type, Rc::new(ThrowCallback)); + + let module = + Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; + let instance = Instance::new(&module, &[fn_func.into()]) + .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let run_func = instance.exports()[0] + .func() + .expect("expected function export"); + + let e = run_func.call(&[]).err().expect("error calling function"); + + let trace = e.trace(); + assert_eq!(trace.len(), 2); + assert_eq!(trace[0].module_name().unwrap(), "hello_mod"); + assert_eq!(trace[0].func_index(), 1); + assert_eq!(trace[1].module_name().unwrap(), "hello_mod"); + assert_eq!(trace[1].func_index(), 0); + assert_eq!(e.message(), "cb throw"); + + Ok(()) +} + +#[test] +fn test_trap_stack_overflow() -> Result<(), String> { + let store = Store::default(); + let binary = parse_str( + r#" + (module $rec_mod + (func $run (export "run") (call $run)) + ) + "#, + ) + .map_err(|e| format!("failed to parse WebAssembly text source: {}", e))?; + + let module = + Module::new(&store, &binary).map_err(|e| format!("failed to compile module: {}", e))?; + let instance = Instance::new(&module, &[]) + .map_err(|e| format!("failed to instantiate module: {:?}", e))?; + let run_func = instance.exports()[0] + .func() + .expect("expected function export"); + + let e = run_func.call(&[]).err().expect("error calling function"); + + let trace = e.trace(); + assert!(trace.len() >= 32); + for i in 0..trace.len() { + assert_eq!(trace[i].module_name().unwrap(), "rec_mod"); + assert_eq!(trace[i].func_index(), 0); + } + assert!(e.message().contains("call stack exhausted")); + + Ok(()) +} diff --git a/crates/environ/Cargo.toml b/crates/environ/Cargo.toml index 0f3d3e52ac..b8a1a0c7ed 100644 --- a/crates/environ/Cargo.toml +++ b/crates/environ/Cargo.toml @@ -13,9 +13,9 @@ edition = "2018" [dependencies] anyhow = "1.0" -cranelift-codegen = { version = "0.54", features = ["enable-serde"] } -cranelift-entity = { version = "0.54", features = ["enable-serde"] } -cranelift-wasm = { version = "0.54", features = ["enable-serde"] } +cranelift-codegen = { version = "0.55", features = ["enable-serde"] } +cranelift-entity = { version = "0.55", features = ["enable-serde"] } +cranelift-wasm = { version = "0.55", features = ["enable-serde"] } wasmparser = "0.47.0" lightbeam = { path = "../lightbeam", optional = true, version = "0.9.0" } indexmap = "1.0.2" @@ -46,7 +46,7 @@ tempfile = "3" target-lexicon = { version = "0.10.0", default-features = false } pretty_env_logger = "0.3.0" rand = { version = "0.7.0", default-features = false, features = ["small_rng"] } -cranelift-codegen = { version = "0.54", features = ["enable-serde", "all-arch"] } +cranelift-codegen = { version = "0.55", features = ["enable-serde", "all-arch"] } filetime = "0.2.7" [badges] diff --git a/crates/environ/src/cache/tests.rs b/crates/environ/src/cache/tests.rs index 55685d6de7..7a9bdc31c6 100644 --- a/crates/environ/src/cache/tests.rs +++ b/crates/environ/src/cache/tests.rs @@ -1,7 +1,9 @@ use super::config::tests::test_prolog; use super::*; use crate::address_map::{FunctionAddressMap, InstructionAddressMap}; -use crate::compilation::{CompiledFunction, Relocation, RelocationTarget, TrapInformation}; +use crate::compilation::{ + CompiledFunction, CompiledFunctionUnwindInfo, Relocation, RelocationTarget, TrapInformation, +}; use crate::module::{MemoryPlan, MemoryStyle, Module}; use cranelift_codegen::{binemit, ir, isa, settings, ValueLocRange}; use cranelift_entity::EntityRef; @@ -259,7 +261,7 @@ fn new_module_cache_data(rng: &mut impl Rng) -> ModuleCacheData { CompiledFunction { body: (0..(i * 3 / 2)).collect(), jt_offsets: sm, - unwind_info: (0..(i * 3 / 2)).collect(), + unwind_info: CompiledFunctionUnwindInfo::Windows((0..(i * 3 / 2)).collect()), } }) .collect(); diff --git a/crates/environ/src/compilation.rs b/crates/environ/src/compilation.rs index 3aea3ba284..ae57584008 100644 --- a/crates/environ/src/compilation.rs +++ b/crates/environ/src/compilation.rs @@ -4,13 +4,136 @@ use crate::cache::ModuleCacheDataTupleType; use crate::module; use crate::module_environ::FunctionBodyData; -use cranelift_codegen::{binemit, ir, isa}; +use cranelift_codegen::{binemit, ir, isa, Context}; use cranelift_entity::PrimaryMap; use cranelift_wasm::{DefinedFuncIndex, FuncIndex, ModuleTranslationState, WasmError}; use serde::{Deserialize, Serialize}; use std::ops::Range; use thiserror::Error; +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct FDERelocEntry(pub i64, pub usize, pub u8); + +/// Relocation entry for unwind info. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct CompiledFunctionUnwindInfoReloc { + /// Entry offest in the code block. + pub offset: u32, + /// Entry addend relative to the code block. + pub addend: u32, +} + +/// Compiled function unwind information. +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub enum CompiledFunctionUnwindInfo { + /// No info. + None, + /// Windows UNWIND_INFO. + Windows(Vec), + /// Frame layout info. + FrameLayout(Vec, usize, Vec), +} + +impl CompiledFunctionUnwindInfo { + /// Constructs unwind info object. + pub fn new(isa: &dyn isa::TargetIsa, context: &Context) -> Self { + use cranelift_codegen::binemit::{ + FrameUnwindKind, FrameUnwindOffset, FrameUnwindSink, Reloc, + }; + use cranelift_codegen::isa::CallConv; + + struct Sink(Vec, usize, Vec); + impl FrameUnwindSink for Sink { + fn len(&self) -> FrameUnwindOffset { + self.0.len() + } + fn bytes(&mut self, b: &[u8]) { + self.0.extend_from_slice(b); + } + fn reserve(&mut self, len: usize) { + self.0.reserve(len) + } + fn reloc(&mut self, r: Reloc, off: FrameUnwindOffset) { + self.2.push(FDERelocEntry( + 0, + off, + match r { + Reloc::Abs4 => 4, + Reloc::Abs8 => 8, + _ => { + panic!("unexpected reloc type"); + } + }, + )) + } + fn set_entry_offset(&mut self, off: FrameUnwindOffset) { + self.1 = off; + } + } + + let kind = match context.func.signature.call_conv { + CallConv::SystemV | CallConv::Fast | CallConv::Cold => FrameUnwindKind::Libunwind, + CallConv::WindowsFastcall => FrameUnwindKind::Fastcall, + _ => { + return CompiledFunctionUnwindInfo::None; + } + }; + + let mut sink = Sink(Vec::new(), 0, Vec::new()); + context.emit_unwind_info(isa, kind, &mut sink); + + let Sink(data, offset, relocs) = sink; + if data.is_empty() { + return CompiledFunctionUnwindInfo::None; + } + + match kind { + FrameUnwindKind::Fastcall => CompiledFunctionUnwindInfo::Windows(data), + FrameUnwindKind::Libunwind => { + CompiledFunctionUnwindInfo::FrameLayout(data, offset, relocs) + } + } + } + + /// Retuns true is no unwind info data. + pub fn is_empty(&self) -> bool { + match self { + CompiledFunctionUnwindInfo::None => true, + CompiledFunctionUnwindInfo::Windows(d) => d.is_empty(), + CompiledFunctionUnwindInfo::FrameLayout(c, _, _) => c.is_empty(), + } + } + + /// Returns size of serilized unwind info. + pub fn len(&self) -> usize { + match self { + CompiledFunctionUnwindInfo::None => 0, + CompiledFunctionUnwindInfo::Windows(d) => d.len(), + CompiledFunctionUnwindInfo::FrameLayout(c, _, _) => c.len(), + } + } + + /// Serializes data into byte array. + pub fn serialize(&self, dest: &mut [u8], relocs: &mut Vec) { + match self { + CompiledFunctionUnwindInfo::None => (), + CompiledFunctionUnwindInfo::Windows(d) => { + dest.copy_from_slice(d); + } + CompiledFunctionUnwindInfo::FrameLayout(code, _fde_offset, r) => { + dest.copy_from_slice(code); + r.iter().for_each(move |r| { + assert_eq!(r.2, 8); + relocs.push(CompiledFunctionUnwindInfoReloc { + offset: r.1 as u32, + addend: r.0 as u32, + }) + }); + } + } + } +} + /// Compiled function: machine code body, jump table offsets, and unwind information. #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] pub struct CompiledFunction { @@ -21,7 +144,7 @@ pub struct CompiledFunction { pub jt_offsets: ir::JumpTableOffsets, /// The unwind information. - pub unwind_info: Vec, + pub unwind_info: CompiledFunctionUnwindInfo, } type Functions = PrimaryMap; @@ -50,7 +173,7 @@ impl Compilation { .map(|(body_range, jt_offsets, unwind_range)| CompiledFunction { body: buffer[body_range].to_vec(), jt_offsets, - unwind_info: buffer[unwind_range].to_vec(), + unwind_info: CompiledFunctionUnwindInfo::Windows(buffer[unwind_range].to_vec()), }) .collect(), ) diff --git a/crates/environ/src/cranelift.rs b/crates/environ/src/cranelift.rs index 790e6cf642..246838373f 100644 --- a/crates/environ/src/cranelift.rs +++ b/crates/environ/src/cranelift.rs @@ -3,7 +3,8 @@ use crate::address_map::{FunctionAddressMap, InstructionAddressMap}; use crate::cache::{ModuleCacheData, ModuleCacheDataTupleType, ModuleCacheEntry}; use crate::compilation::{ - Compilation, CompileError, CompiledFunction, Relocation, RelocationTarget, TrapInformation, + Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Relocation, + RelocationTarget, TrapInformation, }; use crate::func_environ::{ get_func_name, get_imported_memory32_grow_name, get_imported_memory32_size_name, @@ -204,6 +205,7 @@ impl crate::compilation::Compiler for Cranelift { context.func.name = get_func_name(func_index); context.func.signature = module.signatures[module.functions[func_index]].clone(); + context.func.collect_frame_layout_info(); if generate_debug_info { context.func.collect_debug_info(); } @@ -217,7 +219,6 @@ 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 {}; @@ -233,7 +234,7 @@ impl crate::compilation::Compiler for Cranelift { CompileError::Codegen(pretty_error(&context.func, Some(isa), error)) })?; - context.emit_unwind_info(isa, &mut unwind_info); + let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context); let address_transform = if generate_debug_info { let body_len = code_buf.len(); diff --git a/crates/environ/src/func_environ.rs b/crates/environ/src/func_environ.rs index b218641877..c33a2d5c15 100644 --- a/crates/environ/src/func_environ.rs +++ b/crates/environ/src/func_environ.rs @@ -362,8 +362,8 @@ impl<'module_environment> TargetEnvironment for FuncEnvironment<'module_environm } impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'module_environment> { - fn is_wasm_parameter(&self, func: &ir::Function, index: usize) -> bool { - func.signature.params[index].purpose == ir::ArgumentPurpose::Normal + fn is_wasm_parameter(&self, signature: &ir::Signature, index: usize) -> bool { + signature.params[index].purpose == ir::ArgumentPurpose::Normal } fn make_table(&mut self, func: &mut ir::Function, index: TableIndex) -> WasmResult { diff --git a/crates/environ/src/lib.rs b/crates/environ/src/lib.rs index fdbea6c870..ec5525fec2 100644 --- a/crates/environ/src/lib.rs +++ b/crates/environ/src/lib.rs @@ -44,8 +44,9 @@ 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, CompiledFunction, Compiler, Relocation, RelocationTarget, - Relocations, TrapInformation, Traps, + Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, + CompiledFunctionUnwindInfoReloc, Compiler, Relocation, RelocationTarget, Relocations, + TrapInformation, Traps, }; pub use crate::cranelift::Cranelift; pub use crate::data_structures::*; diff --git a/crates/jit/Cargo.toml b/crates/jit/Cargo.toml index 368b9eb4ad..27490f81e1 100644 --- a/crates/jit/Cargo.toml +++ b/crates/jit/Cargo.toml @@ -11,11 +11,11 @@ readme = "README.md" edition = "2018" [dependencies] -cranelift-codegen = { version = "0.54", features = ["enable-serde"] } -cranelift-entity = { version = "0.54", features = ["enable-serde"] } -cranelift-wasm = { version = "0.54", features = ["enable-serde"] } -cranelift-native = "0.54" -cranelift-frontend = "0.54" +cranelift-codegen = { version = "0.55", features = ["enable-serde"] } +cranelift-entity = { version = "0.55", features = ["enable-serde"] } +cranelift-wasm = { version = "0.55", features = ["enable-serde"] } +cranelift-native = "0.55" +cranelift-frontend = "0.55" wasmtime-environ = { path = "../environ", version = "0.9.0" } wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-debug = { path = "../debug", version = "0.9.0" } diff --git a/crates/jit/src/action.rs b/crates/jit/src/action.rs index d3668b0887..385ff32aa3 100644 --- a/crates/jit/src/action.rs +++ b/crates/jit/src/action.rs @@ -6,7 +6,10 @@ use std::cmp::max; use std::{fmt, mem, ptr, slice}; use thiserror::Error; use wasmtime_environ::ir; -use wasmtime_runtime::{wasmtime_call_trampoline, Export, InstanceHandle, VMInvokeArgument}; +use wasmtime_runtime::{ + wasmtime_call_trampoline, Backtrace, Export, InstanceHandle, TrapMessageAndStack, + VMInvokeArgument, +}; /// A runtime value. #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -103,6 +106,8 @@ pub enum ActionOutcome { Trapped { /// The trap message. message: String, + /// Backtrace. + trace: Backtrace, }, } @@ -191,7 +196,7 @@ pub fn invoke( compiler.publish_compiled_code(); // Call the trampoline. - if let Err(message) = unsafe { + if let Err(TrapMessageAndStack(message, trace)) = unsafe { instance.with_signals_on(|| { wasmtime_call_trampoline( callee_vmctx, @@ -200,7 +205,7 @@ pub fn invoke( ) }) } { - return Ok(ActionOutcome::Trapped { message }); + return Ok(ActionOutcome::Trapped { message, trace }); } // Load the return values out of `values_vec`. diff --git a/crates/jit/src/code_memory.rs b/crates/jit/src/code_memory.rs index 49e6d5ee2d..2eb4806364 100644 --- a/crates/jit/src/code_memory.rs +++ b/crates/jit/src/code_memory.rs @@ -2,14 +2,45 @@ use crate::function_table::FunctionTable; use region; +use std::mem::ManuallyDrop; use std::{cmp, mem}; use wasmtime_environ::{Compilation, CompiledFunction}; use wasmtime_runtime::{Mmap, VMFunctionBody}; +struct CodeMemoryEntry { + mmap: ManuallyDrop, + table: ManuallyDrop, +} + +impl CodeMemoryEntry { + fn new() -> Self { + Self { + mmap: ManuallyDrop::new(Mmap::new()), + table: ManuallyDrop::new(FunctionTable::new()), + } + } + fn with_capacity(cap: usize) -> Result { + Ok(Self { + mmap: ManuallyDrop::new(Mmap::with_at_least(cap)?), + table: ManuallyDrop::new(FunctionTable::new()), + }) + } +} + +impl Drop for CodeMemoryEntry { + fn drop(&mut self) { + unsafe { + // Table needs to be freed before mmap. + ManuallyDrop::drop(&mut self.table); + ManuallyDrop::drop(&mut self.mmap); + } + } +} + /// Memory manager for executable code. pub struct CodeMemory { - current: (Mmap, FunctionTable), - mmaps: Vec<(Mmap, FunctionTable)>, + current: CodeMemoryEntry, + entries: Vec, position: usize, published: usize, } @@ -23,8 +54,8 @@ impl CodeMemory { /// Create a new `CodeMemory` instance. pub fn new() -> Self { Self { - current: (Mmap::new(), FunctionTable::new()), - mmaps: Vec::new(), + current: CodeMemoryEntry::new(), + entries: Vec::new(), position: 0, published: 0, } @@ -81,19 +112,20 @@ impl CodeMemory { self.push_current(0) .expect("failed to push current memory map"); - for (m, t) in &mut self.mmaps[self.published..] { + for CodeMemoryEntry { mmap: m, table: t } in &mut self.entries[self.published..] { + // Remove write access to the pages due to the relocation fixups. + t.publish(m.as_ptr() as u64) + .expect("failed to publish function table"); + if !m.is_empty() { 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(); + self.published = self.entries.len(); } /// Allocate `size` bytes of memory which can be made executable later by @@ -103,7 +135,7 @@ impl CodeMemory { /// /// TODO: Add an alignment flag. fn allocate(&mut self, size: usize) -> Result<(&mut [u8], &mut FunctionTable), String> { - if self.current.0.len() - self.position < size { + if self.current.mmap.len() - self.position < size { self.push_current(cmp::max(0x10000, size))?; } @@ -111,8 +143,8 @@ impl CodeMemory { self.position += size; Ok(( - &mut self.current.0.as_mut_slice()[old_position..self.position], - &mut self.current.1, + &mut self.current.mmap.as_mut_slice()[old_position..self.position], + &mut self.current.table, )) } @@ -153,12 +185,19 @@ impl CodeMemory { // 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 mut relocs = Vec::new(); + func.unwind_info + .serialize(&mut unwind[padding..], &mut relocs); 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); + relocs.iter_mut().for_each(move |r| { + r.offset += unwind_start; + r.addend += func_start; + }); + + table.add_function(func_start, func_end, unwind_start, &relocs); (unwind_end, remainder, table, vmfunc) } @@ -174,20 +213,17 @@ impl CodeMemory { 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 new_size == 0 { + CodeMemoryEntry::new() + } else { + CodeMemoryEntry::with_capacity(cmp::max(0x10000, new_size))? + }, ); - if !previous.0.is_empty() { - self.mmaps.push(previous); + if !previous.mmap.is_empty() { + self.entries.push(previous); } else { - assert_eq!(previous.1.len(), 0); + assert_eq!(previous.table.len(), 0); } self.position = 0; diff --git a/crates/jit/src/compiler.rs b/crates/jit/src/compiler.rs index cc4f5b343b..4197a28cb6 100644 --- a/crates/jit/src/compiler.rs +++ b/crates/jit/src/compiler.rs @@ -16,12 +16,12 @@ use wasmtime_environ::entity::{EntityRef, PrimaryMap}; use wasmtime_environ::isa::{TargetFrontendConfig, TargetIsa}; use wasmtime_environ::wasm::{DefinedFuncIndex, DefinedMemoryIndex}; use wasmtime_environ::{ - Compilation, CompileError, CompiledFunction, Compiler as _C, FunctionBodyData, Module, - ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets, + Compilation, CompileError, CompiledFunction, CompiledFunctionUnwindInfo, Compiler as _C, + FunctionBodyData, Module, ModuleVmctxInfo, Relocations, Traps, Tunables, VMOffsets, }; use wasmtime_runtime::{ - get_mut_trap_registry, InstantiationError, SignatureRegistry, TrapRegistrationGuard, - VMFunctionBody, + get_mut_trap_registry, jit_function_registry, InstantiationError, SignatureRegistry, + TrapRegistrationGuard, VMFunctionBody, }; /// Select which kind of compilation to use. @@ -51,6 +51,7 @@ pub struct Compiler { code_memory: CodeMemory, trap_registration_guards: Vec, + jit_function_ranges: Vec<(usize, usize)>, trampoline_park: HashMap<*const VMFunctionBody, *const VMFunctionBody>, signatures: SignatureRegistry, strategy: CompilationStrategy, @@ -66,6 +67,7 @@ impl Compiler { isa, code_memory: CodeMemory::new(), trap_registration_guards: Vec::new(), + jit_function_ranges: Vec::new(), trampoline_park: HashMap::new(), signatures: SignatureRegistry::new(), fn_builder_ctx: FunctionBuilderContext::new(), @@ -85,6 +87,10 @@ impl Drop for Compiler { // Having a custom drop implementation we are independent from the field order // in the struct what reduces potential human error. self.trap_registration_guards.clear(); + + for (start, end) in self.jit_function_ranges.iter() { + jit_function_registry::unregister(*start, *end); + } } } @@ -155,6 +161,18 @@ impl Compiler { &mut self.trap_registration_guards, ); + for (i, allocated) in allocated_functions.iter() { + let ptr = (*allocated) as *const VMFunctionBody; + let body_len = compilation.get(i).body.len(); + self.jit_function_ranges + .push((ptr as usize, ptr as usize + body_len)); + let tag = jit_function_registry::JITFunctionTag { + module_id: module.name.clone(), + func_index: i.index(), + }; + jit_function_registry::register(ptr as usize, ptr as usize + body_len, tag); + } + let dbg = if let Some(debug_data) = debug_data { let target_config = self.isa.frontend_config(); let ofs = VMOffsets::new(target_config.pointer_bytes(), &module); @@ -215,6 +233,7 @@ impl Compiler { signature, value_size, )?; + entry.insert(body); body } @@ -266,6 +285,7 @@ fn make_trampoline( let mut context = Context::new(); context.func = ir::Function::with_name_signature(ir::ExternalName::user(0, 0), wrapper_sig); + context.func.collect_frame_layout_info(); { let mut builder = FunctionBuilder::new(&mut context.func, fn_builder_ctx); @@ -326,7 +346,6 @@ fn make_trampoline( } 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 {}; @@ -346,7 +365,7 @@ fn make_trampoline( ))) })?; - context.emit_unwind_info(isa, &mut unwind_info); + let unwind_info = CompiledFunctionUnwindInfo::new(isa, &context); Ok(code_memory .allocate_for_function(&CompiledFunction { diff --git a/crates/jit/src/function_table.rs b/crates/jit/src/function_table.rs index 3e004e0e28..ffa37ce953 100644 --- a/crates/jit/src/function_table.rs +++ b/crates/jit/src/function_table.rs @@ -2,42 +2,7 @@ //! //! 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(()) - } -} +type FunctionTableReloc = wasmtime_environ::CompiledFunctionUnwindInfoReloc; /// Represents a runtime function table. /// @@ -66,7 +31,14 @@ impl FunctionTable { /// 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) { + pub fn add_function( + &mut self, + start: u32, + end: u32, + unwind: u32, + _relocs: &[FunctionTableReloc], + ) { + assert_eq!(_relocs.len(), 0); use winapi::um::winnt; assert!(!self.published, "table has already been published"); @@ -133,3 +105,106 @@ impl Drop for FunctionTable { } } } + +/// Represents a runtime function table. +/// +/// This is used to register JIT code with the operating system to enable stack walking and unwinding. +#[cfg(any(target_os = "macos", target_os = "linux"))] +pub(crate) struct FunctionTable { + functions: Vec, + relocs: Vec, + published: Option>, +} + +#[cfg(any(target_os = "macos", target_os = "linux"))] +impl FunctionTable { + /// Creates a new function table. + pub fn new() -> Self { + Self { + functions: Vec::new(), + relocs: Vec::new(), + published: None, + } + } + + /// 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, + relocs: &[FunctionTableReloc], + ) { + assert!(self.published.is_none(), "table has already been published"); + self.functions.push(unwind); + self.relocs.extend_from_slice(relocs); + } + + /// 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> { + if self.published.is_some() { + return Err("function table was already published".into()); + } + + if self.functions.is_empty() { + assert_eq!(self.relocs.len(), 0); + self.published = Some(vec![]); + return Ok(()); + } + + extern "C" { + // libunwind import + fn __register_frame(fde: *const u8); + } + + for reloc in self.relocs.iter() { + let addr = base_address + (reloc.offset as u64); + let target = base_address + (reloc.addend as u64); + unsafe { + std::ptr::write(addr as *mut u64, target); + } + } + + let mut fdes = Vec::with_capacity(self.functions.len()); + for unwind_offset in self.functions.iter() { + let addr = base_address + (*unwind_offset as u64); + let off = unsafe { std::ptr::read::(addr as *const u32) } as usize + 4; + + let fde = (addr + off as u64) as usize; + unsafe { + __register_frame(fde as *const _); + } + fdes.push(fde); + } + + self.published = Some(fdes); + Ok(()) + } +} + +#[cfg(any(target_os = "macos", target_os = "linux"))] +impl Drop for FunctionTable { + fn drop(&mut self) { + extern "C" { + // libunwind import + fn __deregister_frame(fde: *const u8); + } + + if self.published.is_some() { + unsafe { + for fde in self.published.as_ref().unwrap() { + __deregister_frame(*fde as *const _); + } + } + } + } +} diff --git a/crates/lightbeam/Cargo.toml b/crates/lightbeam/Cargo.toml index 225a73c311..f3e140f070 100644 --- a/crates/lightbeam/Cargo.toml +++ b/crates/lightbeam/Cargo.toml @@ -19,7 +19,7 @@ memoffset = "0.5.3" itertools = "0.8.2" capstone = "0.6.0" thiserror = "1.0.9" -cranelift-codegen = "0.54" +cranelift-codegen = "0.55" multi_mut = "0.1" either = "1.5" typemap = "0.3" diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 21c62fcf42..edee855abd 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -20,6 +20,7 @@ indexmap = "1.0.2" thiserror = "1.0.4" more-asserts = "0.2.1" cfg-if = "0.1.9" +backtrace = "0.3.40" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] } diff --git a/crates/runtime/src/backtrace.rs b/crates/runtime/src/backtrace.rs new file mode 100644 index 0000000000..c8cba1eb89 --- /dev/null +++ b/crates/runtime/src/backtrace.rs @@ -0,0 +1,148 @@ +//! Backtrace object and utilities. + +use crate::jit_function_registry; +use std::sync::Arc; + +/// Information about backtrace frame. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct BacktraceFrame { + pc: usize, +} + +impl Default for BacktraceFrame { + fn default() -> Self { + Self { pc: 0 } + } +} + +impl BacktraceFrame { + /// Current PC or IP pointer for the frame. + pub fn pc(&self) -> usize { + self.pc + } + /// Additinal frame information. + pub fn tag(&self) -> Option> { + jit_function_registry::find(self.pc) + } +} + +const BACKTRACE_LIMIT: usize = 32; + +/// Backtrace during WebAssembly trap. +#[derive(Copy, Clone, PartialEq, Eq)] +pub struct Backtrace { + len: usize, + frames: [BacktraceFrame; BACKTRACE_LIMIT], +} + +impl Backtrace { + fn new() -> Self { + Self { + len: 0, + frames: [Default::default(); BACKTRACE_LIMIT], + } + } + fn full(&self) -> bool { + self.len >= BACKTRACE_LIMIT + } + fn push(&mut self, frame: BacktraceFrame) { + assert!(self.len < BACKTRACE_LIMIT); + self.frames[self.len] = frame; + self.len += 1; + } + /// Amount of the backtrace frames. + pub fn len(&self) -> usize { + self.len + } +} + +impl std::ops::Index for Backtrace { + type Output = BacktraceFrame; + fn index(&self, index: usize) -> &Self::Output { + assert!(index < self.len); + &self.frames[index] + } +} + +impl std::fmt::Debug for Backtrace { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Backtrace![")?; + for i in 0..self.len() { + let frame = &self.frames[i]; + writeln!(f, " {:x}: {:?}", frame.pc(), frame.tag())?; + } + write!(f, "]")?; + Ok(()) + } +} + +#[cfg(not(all(target_os = "windows", target_arch = "x86_64")))] +fn capture_stack(mut f: F) +where + F: FnMut(usize) -> bool, +{ + use backtrace::trace; + trace(|frame| { + let pc = frame.ip() as usize; + f(pc) + }); +} + +#[cfg(all(target_os = "windows", target_arch = "x86_64"))] +fn capture_stack(mut f: F) +where + F: FnMut(usize) -> bool, +{ + use std::mem::MaybeUninit; + use std::ptr; + use winapi::um::winnt::{ + RtlCaptureContext, RtlLookupFunctionEntry, RtlVirtualUnwind, CONTEXT, UNW_FLAG_NHANDLER, + }; + + #[repr(C, align(16))] + struct WrappedContext(CONTEXT); + + unsafe { + let mut ctx = WrappedContext(MaybeUninit::uninit().assume_init()); + RtlCaptureContext(&mut ctx.0); + let mut unwind_history_table = MaybeUninit::zeroed().assume_init(); + while ctx.0.Rip != 0 { + let cont = f(ctx.0.Rip as usize); + if !cont { + break; + } + + let mut image_base: u64 = 0; + let mut handler_data: *mut core::ffi::c_void = ptr::null_mut(); + let mut establisher_frame: u64 = 0; + let rf = RtlLookupFunctionEntry(ctx.0.Rip, &mut image_base, &mut unwind_history_table); + if rf.is_null() { + ctx.0.Rip = ptr::read(ctx.0.Rsp as *const u64); + ctx.0.Rsp += 8; + } else { + RtlVirtualUnwind( + UNW_FLAG_NHANDLER, + image_base, + ctx.0.Rip, + rf, + &mut ctx.0, + &mut handler_data, + &mut establisher_frame, + ptr::null_mut(), + ); + } + } + } +} + +/// Returns current backtrace. Only registered wasmtime functions will be listed. +pub fn get_backtrace() -> Backtrace { + let mut frames = Backtrace::new(); + capture_stack(|pc| { + if let Some(_) = jit_function_registry::find(pc) { + frames.push(BacktraceFrame { pc }); + } + !frames.full() + }); + frames +} diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 4fe112281d..2c7710e433 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -9,7 +9,7 @@ use crate::memory::LinearMemory; use crate::mmap::Mmap; use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; use crate::table::Table; -use crate::traphandlers::wasmtime_call; +use crate::traphandlers::{wasmtime_call, TrapMessageAndStack}; use crate::vmcontext::{ VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, @@ -568,7 +568,7 @@ impl Instance { // Make the call. unsafe { wasmtime_call(callee_vmctx, callee_address) } - .map_err(InstantiationError::StartTrap) + .map_err(|TrapMessageAndStack(msg, _)| InstantiationError::StartTrap(msg)) } /// Invoke the WebAssembly start function of the instance, if one is present. diff --git a/crates/runtime/src/jit_function_registry.rs b/crates/runtime/src/jit_function_registry.rs new file mode 100644 index 0000000000..60d6d73db0 --- /dev/null +++ b/crates/runtime/src/jit_function_registry.rs @@ -0,0 +1,83 @@ +#![allow(missing_docs)] + +use lazy_static::lazy_static; +use std::collections::BTreeMap; +use std::sync::{Arc, RwLock}; + +lazy_static! { + static ref REGISTRY: RwLock = RwLock::new(JITFunctionRegistry::default()); +} + +#[derive(Clone)] +pub struct JITFunctionTag { + pub module_id: Option, + pub func_index: usize, +} + +impl std::fmt::Debug for JITFunctionTag { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(ref module_id) = self.module_id { + write!(f, "{}", module_id)?; + } else { + write!(f, "(module)")?; + } + write!(f, ":{}", self.func_index) + } +} + +struct JITFunctionRegistry { + ranges: BTreeMap)>, +} + +impl Default for JITFunctionRegistry { + fn default() -> Self { + Self { + ranges: Default::default(), + } + } +} + +impl JITFunctionRegistry { + fn register(&mut self, fn_start: usize, fn_end: usize, tag: JITFunctionTag) { + self.ranges.insert(fn_end, (fn_start, Arc::new(tag))); + } + + fn unregister(&mut self, fn_end: usize) { + self.ranges.remove(&fn_end); + } + + fn find(&self, pc: usize) -> Option<&Arc> { + self.ranges + .range(pc..) + .next() + .and_then(|(end, (start, s))| { + if *start <= pc && pc < *end { + Some(s) + } else { + None + } + }) + } +} + +pub fn register(fn_start: usize, fn_end: usize, tag: JITFunctionTag) { + REGISTRY + .write() + .expect("jit function registry lock got poisoned") + .register(fn_start, fn_end, tag); +} + +pub fn unregister(_fn_start: usize, fn_end: usize) { + REGISTRY + .write() + .expect("jit function registry lock got poisoned") + .unregister(fn_end); +} + +pub fn find(pc: usize) -> Option> { + REGISTRY + .read() + .expect("jit function registry lock got poisoned") + .find(pc) + .cloned() +} diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 5a48273e12..29996f8550 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -21,6 +21,7 @@ ) )] +mod backtrace; mod export; mod imports; mod instance; @@ -34,8 +35,10 @@ mod trap_registry; mod traphandlers; mod vmcontext; +pub mod jit_function_registry; pub mod libcalls; +pub use crate::backtrace::{get_backtrace, Backtrace, BacktraceFrame}; pub use crate::export::Export; pub use crate::imports::Imports; pub use crate::instance::{InstanceHandle, InstantiationError, LinkError}; @@ -44,7 +47,7 @@ pub use crate::mmap::Mmap; pub use crate::sig_registry::SignatureRegistry; pub use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; pub use crate::trap_registry::{get_mut_trap_registry, get_trap_registry, TrapRegistrationGuard}; -pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline}; +pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline, TrapMessageAndStack}; pub use crate::vmcontext::{ VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index a2166f20b7..ae89138172 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -1,6 +1,7 @@ //! WebAssembly trap handling, which is built on top of the lower-level //! signalhandling mechanisms. +use crate::backtrace::{get_backtrace, Backtrace}; use crate::trap_registry::get_trap_registry; use crate::trap_registry::TrapDescription; use crate::vmcontext::{VMContext, VMFunctionBody}; @@ -18,7 +19,7 @@ extern "C" { } thread_local! { - static RECORDED_TRAP: Cell> = Cell::new(None); + 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); } @@ -50,6 +51,8 @@ pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) { trap_code: ir::TrapCode::StackOverflow, }); + let trap_backtrace = get_backtrace(); + if reset_guard_page { RESET_GUARD_PAGE.with(|v| v.set(true)); } @@ -60,7 +63,7 @@ pub extern "C" fn RecordTrap(pc: *const u8, reset_guard_page: bool) { None, "Only one trap per thread can be recorded at a moment!" ); - data.set(Some(trap_desc)) + data.set(Some((trap_desc, trap_backtrace))) }); } @@ -108,15 +111,22 @@ fn reset_guard_page() { #[cfg(not(target_os = "windows"))] fn reset_guard_page() {} -fn trap_message() -> String { +/// Stores trace message with backtrace. +#[derive(Debug)] +pub struct TrapMessageAndStack(pub String, pub Backtrace); + +fn trap_message_and_stack() -> TrapMessageAndStack { let trap_desc = RECORDED_TRAP .with(|data| data.replace(None)) .expect("trap_message must be called after trap occurred"); - format!( - "wasm trap: {}, source location: {}", - trap_code_to_expected_string(trap_desc.trap_code), - trap_desc.source_loc, + TrapMessageAndStack( + format!( + "wasm trap: {}, source location: {}", + trap_code_to_expected_string(trap_desc.0.trap_code), + trap_desc.0.source_loc, + ), + trap_desc.1, ) } @@ -146,9 +156,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline( vmctx: *mut VMContext, callee: *const VMFunctionBody, values_vec: *mut u8, -) -> Result<(), String> { +) -> Result<(), TrapMessageAndStack> { if WasmtimeCallTrampoline(vmctx as *mut u8, callee, values_vec) == 0 { - Err(trap_message()) + Err(trap_message_and_stack()) } else { Ok(()) } @@ -160,9 +170,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline( pub unsafe extern "C" fn wasmtime_call( vmctx: *mut VMContext, callee: *const VMFunctionBody, -) -> Result<(), String> { +) -> Result<(), TrapMessageAndStack> { if WasmtimeCall(vmctx as *mut u8, callee) == 0 { - Err(trap_message()) + Err(trap_message_and_stack()) } else { Ok(()) } diff --git a/crates/wasi-c/Cargo.toml b/crates/wasi-c/Cargo.toml index d169491a89..c52e5244db 100644 --- a/crates/wasi-c/Cargo.toml +++ b/crates/wasi-c/Cargo.toml @@ -14,9 +14,9 @@ edition = "2018" wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-environ = { path = "../environ", version = "0.9.0" } wasmtime-jit = { path = "../jit", version = "0.9.0" } -cranelift-codegen = { version = "0.54", features = ["enable-serde"] } -cranelift-entity = { version = "0.54", features = ["enable-serde"] } -cranelift-wasm = { version = "0.54", features = ["enable-serde"] } +cranelift-codegen = { version = "0.55", features = ["enable-serde"] } +cranelift-entity = { version = "0.55", features = ["enable-serde"] } +cranelift-wasm = { version = "0.55", features = ["enable-serde"] } target-lexicon = "0.10.0" log = { version = "0.4.8", default-features = false } libc = "0.2.60" diff --git a/crates/wasi/Cargo.toml b/crates/wasi/Cargo.toml index 4aaca18c0b..1ea8761523 100644 --- a/crates/wasi/Cargo.toml +++ b/crates/wasi/Cargo.toml @@ -16,9 +16,9 @@ wasmtime-runtime = { path = "../runtime", version = "0.9.0" } wasmtime-environ = { path = "../environ", version = "0.9.0" } wasmtime-jit = { path = "../jit", version = "0.9.0" } wasi-common = { path = "../wasi-common", version = "0.9.0" } -cranelift-codegen = { version = "0.54", features = ["enable-serde"] } -cranelift-entity = { version = "0.54", features = ["enable-serde"] } -cranelift-wasm = { version = "0.54", features = ["enable-serde"] } +cranelift-codegen = { version = "0.55", features = ["enable-serde"] } +cranelift-entity = { version = "0.55", features = ["enable-serde"] } +cranelift-wasm = { version = "0.55", features = ["enable-serde"] } target-lexicon = "0.10.0" log = { version = "0.4.8", default-features = false } wig = { path = "../wasi-common/wig", version = "0.9.2" } diff --git a/scripts/cranelift-version.sh b/scripts/cranelift-version.sh index 7af91d26cd..8bcba364fa 100755 --- a/scripts/cranelift-version.sh +++ b/scripts/cranelift-version.sh @@ -9,7 +9,7 @@ topdir=$(dirname "$0")/.. cd "$topdir" # All the cranelift-* crates have the same version number -version="0.53" +version="0.55" # Update all of the Cargo.toml files. echo "Updating crate versions to $version" From b8e4354efc53398ecf8f5a64c89ee89d5d57c004 Mon Sep 17 00:00:00 2001 From: Dan Gohman Date: Thu, 9 Jan 2020 10:35:18 -0800 Subject: [PATCH 5/9] Implement `write_vectored` for `SandboxedTTYWriter`. Fixes #629. --- .../wasi-common/src/sandboxed_tty_writer.rs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/wasi-common/src/sandboxed_tty_writer.rs b/crates/wasi-common/src/sandboxed_tty_writer.rs index 2f927d0d2f..3152926157 100644 --- a/crates/wasi-common/src/sandboxed_tty_writer.rs +++ b/crates/wasi-common/src/sandboxed_tty_writer.rs @@ -1,4 +1,4 @@ -use std::io::{Result, Write}; +use std::io::{IoSlice, Result, Write}; /// An adapter around a `Write` stream that guarantees that its output /// is valid UTF-8 and contains no control characters. It does this by @@ -124,6 +124,27 @@ where return Ok(result); } + fn write_vectored(&mut self, bufs: &[IoSlice]) -> Result { + // Terminal output is [not expected to be atomic], so just write all the + // individual buffers in sequence. + // + // [not expected to be atomic]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html#tag_16_474_08 + let mut total_written = 0; + + for buf in bufs { + let written = self.write(buf)?; + + total_written += written; + + // Stop at the first point where the OS writes less than we asked. + if written < buf.len() { + break; + } + } + + Ok(total_written) + } + fn flush(&mut self) -> Result<()> { self.inner.flush() } From e7e08f162df5c184de78f5cbe3e2442f69b7a6e1 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 15 Jan 2020 15:30:17 -0600 Subject: [PATCH 6/9] Preserve full native stack traces in errors (#823) * Preserve full native stack traces in errors This commit builds on #759 by performing a few refactorings: * The `backtrace` crate is updated to 0.3.42 which incorporates the Windows-specific stack-walking code, so that's no longer needed. * A full `backtrace::Backtrace` type is held in a trap at all times. * The trap structures in the `wasmtime-*` internal crates were refactored a bit to preserve more information and deal with raw values rather than converting between various types and strings. * The `wasmtime::Trap` type has been updated with these various changes. Eventually I think we'll want to likely render full stack traces (and/or partial wasm ones) into error messages, but for now that's left as-is and we can always improve it later. I suspect the most relevant thing we need to do is to implement function name symbolication for wasm functions first, and then afterwards we can incorporate native function names! * Fix some test suite assertions --- Cargo.lock | 5 +- crates/api/Cargo.toml | 1 + crates/api/src/callable.rs | 7 +- crates/api/src/instance.rs | 4 +- crates/api/src/trap.rs | 49 +++++----- crates/jit/src/action.rs | 16 +--- crates/runtime/Cargo.toml | 2 +- crates/runtime/src/backtrace.rs | 148 ----------------------------- crates/runtime/src/instance.rs | 8 +- crates/runtime/src/lib.rs | 4 +- crates/runtime/src/traphandlers.rs | 73 ++++++++------ tests/custom_signal_handler.rs | 11 ++- 12 files changed, 89 insertions(+), 239 deletions(-) delete mode 100644 crates/runtime/src/backtrace.rs diff --git a/Cargo.lock b/Cargo.lock index 0b60270a40..1f24462c3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -76,9 +76,9 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.40" +version = "0.3.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea" +checksum = "b4b1549d804b6c73f4817df2ba073709e96e426f12987127c48e6745568c350b" dependencies = [ "backtrace-sys", "cfg-if", @@ -1986,6 +1986,7 @@ name = "wasmtime" version = "0.9.0" dependencies = [ "anyhow", + "backtrace", "cfg-if", "file-per-thread-logger", "libc", diff --git a/crates/api/Cargo.toml b/crates/api/Cargo.toml index f524f3d036..66d2e42024 100644 --- a/crates/api/Cargo.toml +++ b/crates/api/Cargo.toml @@ -18,6 +18,7 @@ anyhow = "1.0.19" region = "2.0.0" libc = "0.2" cfg-if = "0.1.9" +backtrace = "0.3.42" [target.'cfg(target_os = "windows")'.dependencies] winapi = "0.3.7" diff --git a/crates/api/src/callable.rs b/crates/api/src/callable.rs index 545eafde80..8425d1d571 100644 --- a/crates/api/src/callable.rs +++ b/crates/api/src/callable.rs @@ -156,12 +156,7 @@ impl WrappedCallable for WasmtimeFn { ) }) } { - let message = error.0; - let backtrace = error.1; - - let trap = take_api_trap().unwrap_or_else(|| { - Trap::new_with_trace(format!("call error: {}", message), backtrace) - }); + let trap = take_api_trap().unwrap_or_else(|| Trap::from_jit(error)); return Err(trap); } diff --git a/crates/api/src/instance.rs b/crates/api/src/instance.rs index 48b080528b..82e125b5a5 100644 --- a/crates/api/src/instance.rs +++ b/crates/api/src/instance.rs @@ -52,8 +52,8 @@ fn instantiate_in_context( let instance = compiled_module.instantiate().map_err(|e| -> Error { if let Some(trap) = take_api_trap() { trap.into() - } else if let InstantiationError::StartTrap(msg) = e { - Trap::new(msg).into() + } else if let InstantiationError::StartTrap(trap) = e { + Trap::from_jit(trap).into() } else { e.into() } diff --git a/crates/api/src/trap.rs b/crates/api/src/trap.rs index ae74c00af3..0672b661f4 100644 --- a/crates/api/src/trap.rs +++ b/crates/api/src/trap.rs @@ -1,7 +1,7 @@ use crate::instance::Instance; +use backtrace::Backtrace; use std::fmt; use std::sync::Arc; -use wasmtime_runtime::{get_backtrace, Backtrace, BacktraceFrame}; /// A struct representing an aborted instruction execution, with a message /// indicating the cause. @@ -12,7 +12,8 @@ pub struct Trap { struct TrapInner { message: String, - trace: Vec, + wasm_trace: Vec, + native_trace: Backtrace, } fn _assert_trap_is_sync_and_send(t: &Trap) -> (&dyn Sync, &dyn Send) { @@ -27,21 +28,29 @@ impl Trap { /// assert_eq!("unexpected error", trap.message()); /// ``` pub fn new>(message: I) -> Self { - Self::new_with_trace(message, get_backtrace()) + Trap::new_with_trace(message.into(), Backtrace::new_unresolved()) } - pub(crate) fn new_with_trace>(message: I, backtrace: Backtrace) -> Self { - let mut trace = Vec::with_capacity(backtrace.len()); - for i in 0..backtrace.len() { - // Don't include frames without backtrace info. - if let Some(info) = FrameInfo::try_from(backtrace[i]) { - trace.push(info); + pub(crate) fn from_jit(jit: wasmtime_runtime::Trap) -> Self { + Trap::new_with_trace(jit.to_string(), jit.backtrace) + } + + fn new_with_trace(message: String, native_trace: Backtrace) -> Self { + let mut wasm_trace = Vec::new(); + for frame in native_trace.frames() { + let pc = frame.ip() as usize; + if let Some(info) = wasmtime_runtime::jit_function_registry::find(pc) { + wasm_trace.push(FrameInfo { + func_index: info.func_index as u32, + module_name: info.module_id.clone(), + }) } } Trap { inner: Arc::new(TrapInner { - message: message.into(), - trace, + message, + wasm_trace, + native_trace, }), } } @@ -52,7 +61,7 @@ impl Trap { } pub fn trace(&self) -> &[FrameInfo] { - &self.inner.trace + &self.inner.wasm_trace } } @@ -60,7 +69,8 @@ impl fmt::Debug for Trap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Trap") .field("message", &self.inner.message) - .field("trace", &self.inner.trace) + .field("wasm_trace", &self.inner.wasm_trace) + .field("native_trace", &self.inner.native_trace) .finish() } } @@ -99,17 +109,4 @@ impl FrameInfo { pub fn module_name(&self) -> Option<&str> { self.module_name.as_deref() } - - pub(crate) fn try_from(backtrace: BacktraceFrame) -> Option { - if let Some(tag) = backtrace.tag() { - let func_index = tag.func_index as u32; - let module_name = tag.module_id.clone(); - Some(FrameInfo { - func_index, - module_name, - }) - } else { - None - } - } } diff --git a/crates/jit/src/action.rs b/crates/jit/src/action.rs index 385ff32aa3..c836da8b26 100644 --- a/crates/jit/src/action.rs +++ b/crates/jit/src/action.rs @@ -6,10 +6,7 @@ use std::cmp::max; use std::{fmt, mem, ptr, slice}; use thiserror::Error; use wasmtime_environ::ir; -use wasmtime_runtime::{ - wasmtime_call_trampoline, Backtrace, Export, InstanceHandle, TrapMessageAndStack, - VMInvokeArgument, -}; +use wasmtime_runtime::{wasmtime_call_trampoline, Export, InstanceHandle, Trap, VMInvokeArgument}; /// A runtime value. #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -103,12 +100,7 @@ pub enum ActionOutcome { }, /// A trap occurred while the action was executing. - Trapped { - /// The trap message. - message: String, - /// Backtrace. - trace: Backtrace, - }, + Trapped(Trap), } /// An error detected while invoking a wasm function or reading a wasm global. @@ -196,7 +188,7 @@ pub fn invoke( compiler.publish_compiled_code(); // Call the trampoline. - if let Err(TrapMessageAndStack(message, trace)) = unsafe { + if let Err(trap) = unsafe { instance.with_signals_on(|| { wasmtime_call_trampoline( callee_vmctx, @@ -205,7 +197,7 @@ pub fn invoke( ) }) } { - return Ok(ActionOutcome::Trapped { message, trace }); + return Ok(ActionOutcome::Trapped(trap)); } // Load the return values out of `values_vec`. diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index edee855abd..d37f6cc9a5 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -20,7 +20,7 @@ indexmap = "1.0.2" thiserror = "1.0.4" more-asserts = "0.2.1" cfg-if = "0.1.9" -backtrace = "0.3.40" +backtrace = "0.3.42" [target.'cfg(target_os = "windows")'.dependencies] winapi = { version = "0.3.7", features = ["winbase", "memoryapi"] } diff --git a/crates/runtime/src/backtrace.rs b/crates/runtime/src/backtrace.rs deleted file mode 100644 index c8cba1eb89..0000000000 --- a/crates/runtime/src/backtrace.rs +++ /dev/null @@ -1,148 +0,0 @@ -//! Backtrace object and utilities. - -use crate::jit_function_registry; -use std::sync::Arc; - -/// Information about backtrace frame. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct BacktraceFrame { - pc: usize, -} - -impl Default for BacktraceFrame { - fn default() -> Self { - Self { pc: 0 } - } -} - -impl BacktraceFrame { - /// Current PC or IP pointer for the frame. - pub fn pc(&self) -> usize { - self.pc - } - /// Additinal frame information. - pub fn tag(&self) -> Option> { - jit_function_registry::find(self.pc) - } -} - -const BACKTRACE_LIMIT: usize = 32; - -/// Backtrace during WebAssembly trap. -#[derive(Copy, Clone, PartialEq, Eq)] -pub struct Backtrace { - len: usize, - frames: [BacktraceFrame; BACKTRACE_LIMIT], -} - -impl Backtrace { - fn new() -> Self { - Self { - len: 0, - frames: [Default::default(); BACKTRACE_LIMIT], - } - } - fn full(&self) -> bool { - self.len >= BACKTRACE_LIMIT - } - fn push(&mut self, frame: BacktraceFrame) { - assert!(self.len < BACKTRACE_LIMIT); - self.frames[self.len] = frame; - self.len += 1; - } - /// Amount of the backtrace frames. - pub fn len(&self) -> usize { - self.len - } -} - -impl std::ops::Index for Backtrace { - type Output = BacktraceFrame; - fn index(&self, index: usize) -> &Self::Output { - assert!(index < self.len); - &self.frames[index] - } -} - -impl std::fmt::Debug for Backtrace { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - writeln!(f, "Backtrace![")?; - for i in 0..self.len() { - let frame = &self.frames[i]; - writeln!(f, " {:x}: {:?}", frame.pc(), frame.tag())?; - } - write!(f, "]")?; - Ok(()) - } -} - -#[cfg(not(all(target_os = "windows", target_arch = "x86_64")))] -fn capture_stack(mut f: F) -where - F: FnMut(usize) -> bool, -{ - use backtrace::trace; - trace(|frame| { - let pc = frame.ip() as usize; - f(pc) - }); -} - -#[cfg(all(target_os = "windows", target_arch = "x86_64"))] -fn capture_stack(mut f: F) -where - F: FnMut(usize) -> bool, -{ - use std::mem::MaybeUninit; - use std::ptr; - use winapi::um::winnt::{ - RtlCaptureContext, RtlLookupFunctionEntry, RtlVirtualUnwind, CONTEXT, UNW_FLAG_NHANDLER, - }; - - #[repr(C, align(16))] - struct WrappedContext(CONTEXT); - - unsafe { - let mut ctx = WrappedContext(MaybeUninit::uninit().assume_init()); - RtlCaptureContext(&mut ctx.0); - let mut unwind_history_table = MaybeUninit::zeroed().assume_init(); - while ctx.0.Rip != 0 { - let cont = f(ctx.0.Rip as usize); - if !cont { - break; - } - - let mut image_base: u64 = 0; - let mut handler_data: *mut core::ffi::c_void = ptr::null_mut(); - let mut establisher_frame: u64 = 0; - let rf = RtlLookupFunctionEntry(ctx.0.Rip, &mut image_base, &mut unwind_history_table); - if rf.is_null() { - ctx.0.Rip = ptr::read(ctx.0.Rsp as *const u64); - ctx.0.Rsp += 8; - } else { - RtlVirtualUnwind( - UNW_FLAG_NHANDLER, - image_base, - ctx.0.Rip, - rf, - &mut ctx.0, - &mut handler_data, - &mut establisher_frame, - ptr::null_mut(), - ); - } - } - } -} - -/// Returns current backtrace. Only registered wasmtime functions will be listed. -pub fn get_backtrace() -> Backtrace { - let mut frames = Backtrace::new(); - capture_stack(|pc| { - if let Some(_) = jit_function_registry::find(pc) { - frames.push(BacktraceFrame { pc }); - } - !frames.full() - }); - frames -} diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index 2c7710e433..d6032faec5 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -9,7 +9,7 @@ use crate::memory::LinearMemory; use crate::mmap::Mmap; use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; use crate::table::Table; -use crate::traphandlers::{wasmtime_call, TrapMessageAndStack}; +use crate::traphandlers::{wasmtime_call, Trap}; use crate::vmcontext::{ VMBuiltinFunctionsArray, VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, @@ -568,7 +568,7 @@ impl Instance { // Make the call. unsafe { wasmtime_call(callee_vmctx, callee_address) } - .map_err(|TrapMessageAndStack(msg, _)| InstantiationError::StartTrap(msg)) + .map_err(InstantiationError::StartTrap) } /// Invoke the WebAssembly start function of the instance, if one is present. @@ -1398,6 +1398,6 @@ pub enum InstantiationError { Link(#[from] LinkError), /// A compilation error occured. - #[error("Trap occurred while invoking start function: {0}")] - StartTrap(String), + #[error("Trap occurred while invoking start function")] + StartTrap(#[source] Trap), } diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 29996f8550..6d5dac0ca7 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -21,7 +21,6 @@ ) )] -mod backtrace; mod export; mod imports; mod instance; @@ -38,7 +37,6 @@ mod vmcontext; pub mod jit_function_registry; pub mod libcalls; -pub use crate::backtrace::{get_backtrace, Backtrace, BacktraceFrame}; pub use crate::export::Export; pub use crate::imports::Imports; pub use crate::instance::{InstanceHandle, InstantiationError, LinkError}; @@ -47,7 +45,7 @@ pub use crate::mmap::Mmap; pub use crate::sig_registry::SignatureRegistry; pub use crate::signalhandlers::{wasmtime_init_eager, wasmtime_init_finish}; pub use crate::trap_registry::{get_mut_trap_registry, get_trap_registry, TrapRegistrationGuard}; -pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline, TrapMessageAndStack}; +pub use crate::traphandlers::{wasmtime_call, wasmtime_call_trampoline, Trap}; pub use crate::vmcontext::{ VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMInvokeArgument, VMMemoryDefinition, VMMemoryImport, VMSharedSignatureIndex, diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index ae89138172..a055ebc722 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -1,11 +1,12 @@ //! WebAssembly trap handling, which is built on top of the lower-level //! signalhandling mechanisms. -use crate::backtrace::{get_backtrace, Backtrace}; use crate::trap_registry::get_trap_registry; use crate::trap_registry::TrapDescription; use crate::vmcontext::{VMContext, VMFunctionBody}; +use backtrace::Backtrace; use std::cell::Cell; +use std::fmt; use std::ptr; use wasmtime_environ::ir; @@ -19,7 +20,7 @@ extern "C" { } thread_local! { - static RECORDED_TRAP: Cell> = Cell::new(None); + 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); } @@ -44,26 +45,26 @@ pub extern "C" fn CheckIfTrapAtAddress(_pc: *const u8) -> i8 { 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 - .get_trap(pc as usize) - .unwrap_or_else(|| TrapDescription { - source_loc: ir::SourceLoc::default(), - trap_code: ir::TrapCode::StackOverflow, - }); - - let trap_backtrace = get_backtrace(); + let trap = Trap { + desc: registry + .get_trap(pc as usize) + .unwrap_or_else(|| TrapDescription { + source_loc: ir::SourceLoc::default(), + trap_code: ir::TrapCode::StackOverflow, + }), + backtrace: Backtrace::new_unresolved(), + }; if reset_guard_page { RESET_GUARD_PAGE.with(|v| v.set(true)); } RECORDED_TRAP.with(|data| { - assert_eq!( - data.get(), - None, + let prev = data.replace(Some(trap)); + assert!( + prev.is_none(), "Only one trap per thread can be recorded at a moment!" ); - data.set(Some((trap_desc, trap_backtrace))) }); } @@ -113,21 +114,31 @@ fn reset_guard_page() {} /// Stores trace message with backtrace. #[derive(Debug)] -pub struct TrapMessageAndStack(pub String, pub Backtrace); +pub struct Trap { + /// What sort of trap happened, as well as where in the original wasm module + /// it happened. + pub desc: TrapDescription, + /// Native stack backtrace at the time the trap occurred + pub backtrace: Backtrace, +} -fn trap_message_and_stack() -> TrapMessageAndStack { - let trap_desc = RECORDED_TRAP - .with(|data| data.replace(None)) - .expect("trap_message must be called after trap occurred"); - - TrapMessageAndStack( - format!( +impl fmt::Display for Trap { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, "wasm trap: {}, source location: {}", - trap_code_to_expected_string(trap_desc.0.trap_code), - trap_desc.0.source_loc, - ), - trap_desc.1, - ) + trap_code_to_expected_string(self.desc.trap_code), + self.desc.source_loc + ) + } +} + +impl std::error::Error for Trap {} + +fn last_trap() -> Trap { + RECORDED_TRAP + .with(|data| data.replace(None)) + .expect("trap_message must be called after trap occurred") } fn trap_code_to_expected_string(trap_code: ir::TrapCode) -> String { @@ -156,9 +167,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline( vmctx: *mut VMContext, callee: *const VMFunctionBody, values_vec: *mut u8, -) -> Result<(), TrapMessageAndStack> { +) -> Result<(), Trap> { if WasmtimeCallTrampoline(vmctx as *mut u8, callee, values_vec) == 0 { - Err(trap_message_and_stack()) + Err(last_trap()) } else { Ok(()) } @@ -170,9 +181,9 @@ pub unsafe extern "C" fn wasmtime_call_trampoline( pub unsafe extern "C" fn wasmtime_call( vmctx: *mut VMContext, callee: *const VMFunctionBody, -) -> Result<(), TrapMessageAndStack> { +) -> Result<(), Trap> { if WasmtimeCall(vmctx as *mut u8, callee) == 0 { - Err(trap_message_and_stack()) + Err(last_trap()) } else { Ok(()) } diff --git a/tests/custom_signal_handler.rs b/tests/custom_signal_handler.rs index 40f9080998..3bbb590424 100644 --- a/tests/custom_signal_handler.rs +++ b/tests/custom_signal_handler.rs @@ -128,9 +128,12 @@ mod tests { { println!("calling read_out_of_bounds..."); let trap = invoke_export(&instance, "read_out_of_bounds").unwrap_err(); - assert!(trap - .message() - .starts_with("call error: wasm trap: out of bounds memory access")); + assert!( + trap.message() + .starts_with("wasm trap: out of bounds memory access"), + "bad trap message: {:?}", + trap.message() + ); } // these invoke wasmtime_call_trampoline from callable.rs @@ -151,7 +154,7 @@ mod tests { let trap = read_out_of_bounds_func.call(&[]).unwrap_err(); assert!(trap .message() - .starts_with("call error: wasm trap: out of bounds memory access")); + .starts_with("wasm trap: out of bounds memory access")); } Ok(()) } From b4dccc04869d8a2c543b3bf2bf95b1070c3aaf0d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 15 Jan 2020 15:44:26 -0600 Subject: [PATCH 7/9] Assert on CI `wasi-tests` workspace is locked (#795) Similar to the main workspace, assert that CI doesn't need to change the manifest to ensure that PRs reflect any manifest updates necessary. --- .github/workflows/main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 192e9f9141..767300736a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -165,6 +165,7 @@ jobs: - run: rustup target add wasm32-wasi - run: cargo fetch --locked + - run: cargo fetch --locked --manifest-path crates/test-programs/wasi-tests/Cargo.toml # Build and test all features except for lightbeam - run: cargo test --features test_programs --all --exclude lightbeam -- --nocapture From 0be3c2983c1e8c085fd98e9145638acadb91a110 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 15 Jan 2020 16:54:57 -0600 Subject: [PATCH 8/9] Remove the `Context` type from `wasmtime` (#825) * Remove the `Context` type from `wasmtime` This hasn't really ended up being used in all that many places and the dependencies on it were pretty minimal. This commit removes it in favor of simplifying its various users a bit and/or leveraging the `Engine`/`Store` structures where possible. * Run rustfmt --- crates/api/src/callable.rs | 3 +-- crates/api/src/context.rs | 53 -------------------------------------- crates/api/src/instance.rs | 36 +++++--------------------- crates/api/src/lib.rs | 1 - crates/api/src/module.rs | 2 +- crates/api/src/runtime.rs | 22 ++++++++++------ 6 files changed, 23 insertions(+), 94 deletions(-) delete mode 100644 crates/api/src/context.rs diff --git a/crates/api/src/callable.rs b/crates/api/src/callable.rs index 8425d1d571..732f958981 100644 --- a/crates/api/src/callable.rs +++ b/crates/api/src/callable.rs @@ -141,8 +141,7 @@ impl WrappedCallable for WasmtimeFn { // Get the trampoline to call for this function. let exec_code_buf = self .store - .context() - .compiler() + .compiler_mut() .get_published_trampoline(body, &signature, value_size) .map_err(|e| Trap::new(format!("trampoline error: {:?}", e)))?; diff --git a/crates/api/src/context.rs b/crates/api/src/context.rs deleted file mode 100644 index 05f9ecf02f..0000000000 --- a/crates/api/src/context.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::Config; -use std::cell::{RefCell, RefMut}; -use std::hash::{Hash, Hasher}; -use std::rc::Rc; -use wasmtime_environ::settings; -use wasmtime_jit::{native, Compiler, Features}; - -#[derive(Clone)] -pub struct Context { - compiler: Rc>, - features: Features, - debug_info: bool, -} - -impl Context { - pub fn new(config: &Config) -> Context { - let isa = native::builder().finish(settings::Flags::new(config.flags.clone())); - Context::new_with_compiler(config, Compiler::new(isa, config.strategy)) - } - - pub fn new_with_compiler(config: &Config, compiler: Compiler) -> Context { - Context { - compiler: Rc::new(RefCell::new(compiler)), - features: config.features.clone(), - debug_info: config.debug_info, - } - } - - pub(crate) fn debug_info(&self) -> bool { - self.debug_info - } - - pub(crate) fn compiler(&self) -> RefMut { - self.compiler.borrow_mut() - } -} - -impl Hash for Context { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - self.compiler.as_ptr().hash(state) - } -} - -impl Eq for Context {} - -impl PartialEq for Context { - fn eq(&self, other: &Context) -> bool { - Rc::ptr_eq(&self.compiler, &other.compiler) - } -} diff --git a/crates/api/src/instance.rs b/crates/api/src/instance.rs index 82e125b5a5..3299b16382 100644 --- a/crates/api/src/instance.rs +++ b/crates/api/src/instance.rs @@ -1,4 +1,3 @@ -use crate::context::Context; use crate::externals::Extern; use crate::module::Module; use crate::runtime::Store; @@ -6,9 +5,6 @@ use crate::trampoline::take_api_trap; use crate::trap::Trap; use crate::types::{ExportType, ExternType}; use anyhow::{Error, Result}; -use std::cell::RefCell; -use std::collections::{HashMap, HashSet}; -use std::rc::Rc; use wasmtime_jit::{CompiledModule, Resolver}; use wasmtime_runtime::{Export, InstanceHandle, InstantiationError}; @@ -29,19 +25,15 @@ fn instantiate_in_context( data: &[u8], imports: &[Extern], module_name: Option<&str>, - context: Context, - exports: Rc>>>, -) -> Result<(InstanceHandle, HashSet), Error> { - let mut contexts = HashSet::new(); - let debug_info = context.debug_info(); +) -> Result { let mut resolver = SimpleResolver { imports }; let mut compiled_module = CompiledModule::new( - &mut context.compiler(), + &mut store.compiler_mut(), data, module_name, &mut resolver, - exports, - debug_info, + store.global_exports().clone(), + store.engine().config().debug_info, )?; // Register all module signatures @@ -58,34 +50,24 @@ fn instantiate_in_context( e.into() } })?; - contexts.insert(context); - Ok((instance, contexts)) + Ok(instance) } #[derive(Clone)] pub struct Instance { instance_handle: InstanceHandle, - module: Module, - - // We need to keep CodeMemory alive. - contexts: HashSet, - exports: Box<[Extern]>, } impl Instance { pub fn new(module: &Module, externs: &[Extern]) -> Result { let store = module.store(); - let context = store.context().clone(); - let exports = store.global_exports().clone(); - let (mut instance_handle, contexts) = instantiate_in_context( - module.store(), + let mut instance_handle = instantiate_in_context( + store, module.binary().expect("binary"), externs, module.name(), - context, - exports, )?; let exports = { @@ -104,7 +86,6 @@ impl Instance { Ok(Instance { instance_handle, module: module.clone(), - contexts, exports, }) } @@ -141,8 +122,6 @@ impl Instance { } pub fn from_handle(store: &Store, instance_handle: InstanceHandle) -> Instance { - let contexts = HashSet::new(); - let mut exports = Vec::new(); let mut exports_types = Vec::new(); let mut mutable = instance_handle.clone(); @@ -175,7 +154,6 @@ impl Instance { Instance { instance_handle, module, - contexts, exports: exports.into_boxed_slice(), } } diff --git a/crates/api/src/lib.rs b/crates/api/src/lib.rs index d02a2c16a6..7d1dc17588 100644 --- a/crates/api/src/lib.rs +++ b/crates/api/src/lib.rs @@ -7,7 +7,6 @@ //! itself for consumption from other languages. mod callable; -mod context; mod externals; mod instance; mod module; diff --git a/crates/api/src/module.rs b/crates/api/src/module.rs index 3704f11e14..d6fd7f0579 100644 --- a/crates/api/src/module.rs +++ b/crates/api/src/module.rs @@ -196,7 +196,7 @@ impl Module { /// /// [binary]: https://webassembly.github.io/spec/core/binary/index.html pub fn validate(store: &Store, binary: &[u8]) -> Result<()> { - let features = store.engine().config.features.clone(); + let features = store.engine().config().features.clone(); let config = ValidatingParserConfig { operator_config: OperatorValidatorConfig { enable_threads: features.threads, diff --git a/crates/api/src/runtime.rs b/crates/api/src/runtime.rs index 76a599d7c2..5563be02b0 100644 --- a/crates/api/src/runtime.rs +++ b/crates/api/src/runtime.rs @@ -1,4 +1,3 @@ -use crate::context::Context; use anyhow::Result; use std::cell::RefCell; use std::collections::HashMap; @@ -8,7 +7,7 @@ use wasmtime_environ::{ ir, settings::{self, Configurable}, }; -use wasmtime_jit::{CompilationStrategy, Features}; +use wasmtime_jit::{native, CompilationStrategy, Compiler, Features}; // Runtime Environment @@ -297,7 +296,7 @@ pub enum OptLevel { /// default settings. #[derive(Default, Clone)] pub struct Engine { - pub(crate) config: Arc, + config: Arc, } impl Engine { @@ -308,6 +307,11 @@ impl Engine { config: Arc::new(config.clone()), } } + + /// Returns the configuration settings that this engine is using. + pub fn config(&self) -> &Config { + &self.config + } } // Store @@ -337,7 +341,7 @@ pub struct Store { struct StoreInner { engine: Engine, - context: Context, + compiler: RefCell, global_exports: Rc>>>, signature_cache: RefCell>, } @@ -345,10 +349,12 @@ struct StoreInner { impl Store { /// Creates a new store to be associated with the given [`Engine`]. pub fn new(engine: &Engine) -> Store { + let isa = native::builder().finish(settings::Flags::new(engine.config.flags.clone())); + let compiler = Compiler::new(isa, engine.config.strategy); Store { inner: Rc::new(StoreInner { engine: engine.clone(), - context: Context::new(&engine.config), + compiler: RefCell::new(compiler), global_exports: Rc::new(RefCell::new(HashMap::new())), signature_cache: RefCell::new(HashMap::new()), }), @@ -360,8 +366,8 @@ impl Store { &self.inner.engine } - pub(crate) fn context(&self) -> &Context { - &self.inner.context + pub(crate) fn compiler_mut(&self) -> std::cell::RefMut<'_, Compiler> { + self.inner.compiler.borrow_mut() } // Specific to wasmtime: hack to pass memory around to wasi @@ -377,7 +383,7 @@ impl Store { signature: &ir::Signature, ) -> wasmtime_runtime::VMSharedSignatureIndex { use std::collections::hash_map::Entry; - let index = self.context().compiler().signatures().register(signature); + let index = self.compiler_mut().signatures().register(signature); match self.inner.signature_cache.borrow_mut().entry(index) { Entry::Vacant(v) => { v.insert(signature.clone()); From adcc047f4a38dee719532025a875276c0c03349b Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Wed, 15 Jan 2020 21:05:37 -0800 Subject: [PATCH 9/9] Update fuzz crates (#826) * deps: Update to arbitrary 0.3.x and libfuzzer-sys 0.2.0 * ci: Use cargo-fuzz 0.7.x in CI --- .github/workflows/main.yml | 3 +- Cargo.lock | 230 +++++++++++++++++---------- crates/fuzzing/Cargo.toml | 2 +- crates/fuzzing/src/generators.rs | 12 +- crates/fuzzing/src/generators/api.rs | 69 +++----- fuzz/Cargo.toml | 2 +- 6 files changed, 178 insertions(+), 140 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 767300736a..1dd398f965 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -71,7 +71,7 @@ jobs: - uses: ./.github/actions/install-rust with: toolchain: nightly - - run: cargo install cargo-fuzz --vers "^0.6" + - run: cargo install cargo-fuzz --vers "^0.7" - run: cargo fetch working-directory: ./fuzz - run: cargo fuzz build --release --debug-assertions @@ -512,4 +512,3 @@ jobs: files: "dist/*" name: ${{ steps.tagname.outputs.val }} token: ${{ secrets.GITHUB_TOKEN }} - diff --git a/Cargo.lock b/Cargo.lock index 1f24462c3b..f829cc5bff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -30,6 +30,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64cf76cb6e2222ed0ea86b2b0ee2f71c96ec6edd5af42e84d59160e91b836ec4" +[[package]] +name = "arbitrary" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b93b22576af8f14bb2bad6a5dc09c4f80539a801f7ea340c798e222f2ce49859" +dependencies = [ + "derive_arbitrary", +] + [[package]] name = "arrayref" version = "0.3.5" @@ -159,8 +168,8 @@ dependencies = [ "lazycell", "log", "peeking_take_while", - "proc-macro2", - "quote", + "proc-macro2 1.0.7", + "quote 1.0.2", "regex", "rustc-hash", "shlex", @@ -491,8 +500,8 @@ version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd8ce37ad4184ab2ce004c33bf6379185d3b1c95801cab51026bd271bf68eedc" dependencies = [ - "quote", - "syn", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -504,6 +513,17 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "derive_arbitrary" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b979a9f98d526ac07489ae2879e04d948ceeb195d777742997a30c3d1ab1aad6" +dependencies = [ + "proc-macro2 0.4.30", + "syn 0.14.9", + "synstructure 0.9.0", +] + [[package]] name = "digest" version = "0.8.1" @@ -545,9 +565,9 @@ dependencies = [ "byteorder", "lazy_static", "owning_ref", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -645,10 +665,10 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bc225b78e0391e4b8683440bf2e63c2deeeb2ce5189eab46e2b68c6d3725d08" dependencies = [ - "proc-macro2", - "quote", - "syn", - "synstructure", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", + "synstructure 0.12.3", ] [[package]] @@ -723,9 +743,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a36606a68532b5640dc86bb1f33c64b45c4682aad4c50f3937b317ea387f3d6" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -817,9 +837,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b714fc08d0961716390977cdff1536234415ac37b509e34e5a983def8340fb75" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", "unindent", ] @@ -840,9 +860,9 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a8e30575afe28eea36a9a39136b70b2fb6b0dd0a212a5bd1f30a498395c0274" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -895,10 +915,11 @@ checksum = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" [[package]] name = "libfuzzer-sys" -version = "0.1.0" -source = "git+https://github.com/rust-fuzz/libfuzzer-sys.git#0c4507533a79e85e1984f59765bdd35fbdaa7f1b" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f9e4f036a9cb9f43c637990c03fe045425a33c1c44abf9bc6f555671be5969" dependencies = [ - "arbitrary", + "arbitrary 0.3.2", "cc", ] @@ -1122,9 +1143,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4214c9e912ef61bf42b81ba9a47e8aad1b2ffaf739ab162bf96d1e011f54e6c5" dependencies = [ "proc-macro-hack", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -1163,10 +1184,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53c98547ceaea14eeb26fcadf51dc70d01a2479a7839170eae133721105e4428" dependencies = [ "proc-macro-error-attr", - "proc-macro2", - "quote", + "proc-macro2 1.0.7", + "quote 1.0.2", "rustversion", - "syn", + "syn 1.0.13", ] [[package]] @@ -1175,10 +1196,10 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2bf5d493cf5d3e296beccfd61794e445e830dfc8070a9c248ad3ee071392c6c" dependencies = [ - "proc-macro2", - "quote", + "proc-macro2 1.0.7", + "quote 1.0.2", "rustversion", - "syn", + "syn 1.0.13", "syn-mid", ] @@ -1188,9 +1209,18 @@ version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + +[[package]] +name = "proc-macro2" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" +dependencies = [ + "unicode-xid 0.1.0", ] [[package]] @@ -1199,7 +1229,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0319972dcae462681daf4da1adeeaa066e3ebd29c69be96c6abb1259d2ee2bcc" dependencies = [ - "unicode-xid", + "unicode-xid 0.2.0", ] [[package]] @@ -1229,9 +1259,9 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4882d8237fd8c7373cc25cb802fe0dab9ff70830fd56f47ef6c7f3f287fcc057" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -1240,10 +1270,10 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdf321cfab555f7411298733c86d21e5136f5ded13f5872fabf9de3337beecda" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.7", "pyo3-derive-backend", - "quote", - "syn", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -1264,13 +1294,22 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "quote" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" +dependencies = [ + "proc-macro2 0.4.30", +] + [[package]] name = "quote" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053a8c8bcc71fcce321828dc897a98ab9760bef03a4fc36693c231e5b3216cfe" dependencies = [ - "proc-macro2", + "proc-macro2 1.0.7", ] [[package]] @@ -1495,9 +1534,9 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a0538bd897e17257b0128d2fd95c2ed6df939374073a36166051a79e2eb7986" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -1527,9 +1566,9 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8584eea9b9ff42825b46faf46a8c24d2cff13ec152fa2a50df788b87c07ee28" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -1562,9 +1601,9 @@ version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "128f9e303a5a29922045a830221b8f78ec74a5f544944f3d5984f8ec3895ef64" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -1647,9 +1686,20 @@ checksum = "0a97f829a34a0a9d5b353a881025a23b8c9fd09d46be6045df6b22920dbd7a93" dependencies = [ "heck", "proc-macro-error", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + +[[package]] +name = "syn" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261ae9ecaa397c42b960649561949d69311f08eeaea86a65696e6e46517cf741" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "unicode-xid 0.1.0", ] [[package]] @@ -1658,9 +1708,9 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e4ff033220a41d1a57d8125eab57bf5263783dfdcc18688b1dacc6ce9651ef8" dependencies = [ - "proc-macro2", - "quote", - "unicode-xid", + "proc-macro2 1.0.7", + "quote 1.0.2", + "unicode-xid 0.2.0", ] [[package]] @@ -1669,9 +1719,21 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fd3937748a7eccff61ba5b90af1a20dbf610858923a9192ea0ecb0cb77db1d0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", +] + +[[package]] +name = "synstructure" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bb9b7550d063ea184027c9b8c20ac167cd36d3e06b3a40bceb9d746dc1a7b7" +dependencies = [ + "proc-macro2 0.4.30", + "quote 0.6.13", + "syn 0.14.9", + "unicode-xid 0.1.0", ] [[package]] @@ -1680,10 +1742,10 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545" dependencies = [ - "proc-macro2", - "quote", - "syn", - "unicode-xid", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", + "unicode-xid 0.2.0", ] [[package]] @@ -1758,9 +1820,9 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb2e25d25307eb8436894f727aba8f65d07adf02e5b35a13cebed48bd282bfef" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -1839,6 +1901,12 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" + [[package]] name = "unicode-xid" version = "0.2.0" @@ -1899,9 +1967,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8757b0da38353d55a9687f4dee68a8f441f980dd36e16ab07d6e6c673f505f76" dependencies = [ "heck", - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -1935,9 +2003,9 @@ dependencies = [ name = "wasi-common-cbindgen" version = "0.9.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", "trybuild", ] @@ -2092,7 +2160,7 @@ dependencies = [ name = "wasmtime-fuzz" version = "0.9.0" dependencies = [ - "arbitrary", + "arbitrary 0.2.0", "env_logger 0.7.1", "libfuzzer-sys", "log", @@ -2106,7 +2174,7 @@ name = "wasmtime-fuzzing" version = "0.9.0" dependencies = [ "anyhow", - "arbitrary", + "arbitrary 0.3.2", "binaryen", "env_logger 0.7.1", "log", @@ -2213,9 +2281,9 @@ dependencies = [ name = "wasmtime-rust-macro" version = "0.9.0" dependencies = [ - "proc-macro2", - "quote", - "syn", + "proc-macro2 1.0.7", + "quote 1.0.2", + "syn 1.0.13", ] [[package]] @@ -2303,8 +2371,8 @@ name = "wig" version = "0.9.2" dependencies = [ "heck", - "proc-macro2", - "quote", + "proc-macro2 1.0.7", + "quote 1.0.2", "witx", ] diff --git a/crates/fuzzing/Cargo.toml b/crates/fuzzing/Cargo.toml index e6eefeafe7..81b1d471b3 100644 --- a/crates/fuzzing/Cargo.toml +++ b/crates/fuzzing/Cargo.toml @@ -10,7 +10,7 @@ version = "0.9.0" [dependencies] anyhow = "1.0.22" -arbitrary = "0.2.0" +arbitrary = { version = "0.3.2", features = ["derive"] } binaryen = "0.8.2" env_logger = { version = "0.7.1", optional = true } log = "0.4.8" diff --git a/crates/fuzzing/src/generators.rs b/crates/fuzzing/src/generators.rs index 1fb6a29c0f..62ed219aef 100644 --- a/crates/fuzzing/src/generators.rs +++ b/crates/fuzzing/src/generators.rs @@ -31,13 +31,17 @@ impl fmt::Debug for WasmOptTtf { } impl Arbitrary for WasmOptTtf { - fn arbitrary(input: &mut U) -> Result - where - U: Unstructured + ?Sized, - { + fn arbitrary(input: &mut Unstructured) -> arbitrary::Result { let seed: Vec = Arbitrary::arbitrary(input)?; let module = binaryen::tools::translate_to_fuzz_mvp(&seed); let wasm = module.write(); Ok(WasmOptTtf { wasm }) } + + fn arbitrary_take_rest(input: Unstructured) -> arbitrary::Result { + let seed: Vec = Arbitrary::arbitrary_take_rest(input)?; + let module = binaryen::tools::translate_to_fuzz_mvp(&seed); + let wasm = module.write(); + Ok(WasmOptTtf { wasm }) + } } diff --git a/crates/fuzzing/src/generators/api.rs b/crates/fuzzing/src/generators/api.rs index debae64f05..1e59fea39f 100644 --- a/crates/fuzzing/src/generators/api.rs +++ b/crates/fuzzing/src/generators/api.rs @@ -15,8 +15,9 @@ //! [swarm testing]: https://www.cs.utah.edu/~regehr/papers/swarm12.pdf use arbitrary::{Arbitrary, Unstructured}; -use std::collections::HashSet; +use std::collections::BTreeSet; +#[derive(Arbitrary, Debug)] struct Swarm { config_debug_info: bool, module_new: bool, @@ -26,24 +27,8 @@ struct Swarm { call_exported_func: bool, } -impl Arbitrary for Swarm { - fn arbitrary(input: &mut U) -> Result - where - U: Unstructured + ?Sized, - { - Ok(Swarm { - config_debug_info: bool::arbitrary(input)?, - module_new: bool::arbitrary(input)?, - module_drop: bool::arbitrary(input)?, - instance_new: bool::arbitrary(input)?, - instance_drop: bool::arbitrary(input)?, - call_exported_func: bool::arbitrary(input)?, - }) - } -} - /// A call to one of Wasmtime's public APIs. -#[derive(Clone, Debug)] +#[derive(Arbitrary, Clone, Debug)] #[allow(missing_docs)] pub enum ApiCall { ConfigNew, @@ -61,8 +46,8 @@ use ApiCall::*; #[derive(Default)] struct Scope { id_counter: usize, - modules: HashSet, - instances: HashSet, + modules: BTreeSet, + instances: BTreeSet, } impl Scope { @@ -81,10 +66,7 @@ pub struct ApiCalls { } impl Arbitrary for ApiCalls { - fn arbitrary(input: &mut U) -> Result - where - U: Unstructured + ?Sized, - { + fn arbitrary(input: &mut Unstructured) -> arbitrary::Result { let swarm = Swarm::arbitrary(input)?; let mut calls = vec![]; @@ -94,8 +76,8 @@ impl Arbitrary for ApiCalls { let mut scope = Scope::default(); - for _ in 0..input.container_size()? { - let mut choices: Vec Result> = vec![]; + for _ in 0..input.arbitrary_len::()? { + let mut choices: Vec arbitrary::Result> = vec![]; if swarm.module_new { choices.push(|input, scope| { @@ -108,7 +90,7 @@ impl Arbitrary for ApiCalls { if swarm.module_drop && !scope.modules.is_empty() { choices.push(|input, scope| { let modules: Vec<_> = scope.modules.iter().cloned().collect(); - let id = arbitrary_choice(input, &modules)?.cloned().unwrap(); + let id = *input.choose(&modules)?; scope.modules.remove(&id); Ok(ModuleDrop { id }) }); @@ -116,7 +98,7 @@ impl Arbitrary for ApiCalls { if swarm.instance_new && !scope.modules.is_empty() { choices.push(|input, scope| { let modules: Vec<_> = scope.modules.iter().cloned().collect(); - let module = arbitrary_choice(input, &modules)?.cloned().unwrap(); + let module = *input.choose(&modules)?; let id = scope.next_id(); scope.instances.insert(id); Ok(InstanceNew { id, module }) @@ -125,7 +107,7 @@ impl Arbitrary for ApiCalls { if swarm.instance_drop && !scope.instances.is_empty() { choices.push(|input, scope| { let instances: Vec<_> = scope.instances.iter().cloned().collect(); - let id = arbitrary_choice(input, &instances)?.cloned().unwrap(); + let id = *input.choose(&instances)?; scope.instances.remove(&id); Ok(InstanceDrop { id }) }); @@ -133,43 +115,28 @@ impl Arbitrary for ApiCalls { if swarm.call_exported_func && !scope.instances.is_empty() { choices.push(|input, scope| { let instances: Vec<_> = scope.instances.iter().cloned().collect(); - let instance = arbitrary_choice(input, &instances)?.cloned().unwrap(); + let instance = *input.choose(&instances)?; let nth = usize::arbitrary(input)?; Ok(CallExportedFunc { instance, nth }) }); } - if let Some(c) = arbitrary_choice(input, &choices)? { - calls.push(c(input, &mut scope)?); - } else { + if choices.is_empty() { break; } + let c = input.choose(&choices)?; + calls.push(c(input, &mut scope)?); } Ok(ApiCalls { calls }) } } -fn arbitrary_choice<'a, T, U>(input: &mut U, choices: &'a [T]) -> Result, U::Error> -where - U: Unstructured + ?Sized, -{ - if choices.is_empty() { - Ok(None) - } else { - let i = usize::arbitrary(input)? % choices.len(); - Ok(Some(&choices[i])) - } -} - -fn arbitrary_config( - input: &mut U, +fn arbitrary_config( + input: &mut Unstructured, swarm: &Swarm, calls: &mut Vec, -) -> Result<(), U::Error> -where - U: Unstructured + ?Sized, -{ +) -> arbitrary::Result<()> { calls.push(ConfigNew); if swarm.config_debug_info && bool::arbitrary(input)? { diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 996c53b2f3..50dfe42fae 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -15,7 +15,7 @@ log = "0.4.8" wasmtime-fuzzing = { path = "../crates/fuzzing", features = ["env_logger"] } wasmtime-jit = { path = "../crates/jit" } wasmtime = { path = "../crates/api" } -libfuzzer-sys = { git = "https://github.com/rust-fuzz/libfuzzer-sys.git" } +libfuzzer-sys = "0.2.0" [[bin]] name = "compile"