Improve handling of strings for backtraces (#843)
* Improve handling of strings for backtraces Largely avoid storing strings at all in the `wasmtime-*` internal crates, and instead only store strings in a separate global cache specific to the `wasmtime` crate itself. This global cache is inserted and removed from dynamically as modules are created and deallocated, and the global cache is consulted whenever a `Trap` is created to symbolicate any wasm frames. This also avoids the need to thread `module_name` through the jit crates and back, and additionally removes the need for `ModuleSyncString`. * Run rustfmt
This commit is contained in:
@@ -20,6 +20,7 @@ libc = "0.2"
|
||||
cfg-if = "0.1.9"
|
||||
backtrace = "0.3.42"
|
||||
rustc-demangle = "0.1.16"
|
||||
lazy_static = "1.4"
|
||||
|
||||
[target.'cfg(target_os = "windows")'.dependencies]
|
||||
winapi = "0.3.7"
|
||||
|
||||
@@ -108,8 +108,7 @@ impl Instance {
|
||||
/// [issue]: https://github.com/bytecodealliance/wasmtime/issues/727
|
||||
pub fn new(module: &Module, imports: &[Extern]) -> Result<Instance, Error> {
|
||||
let store = module.store();
|
||||
let mut instance_handle =
|
||||
instantiate(module.compiled_module().expect("compiled_module"), imports)?;
|
||||
let mut instance_handle = instantiate(module.compiled_module(), imports)?;
|
||||
|
||||
let exports = {
|
||||
let mut exports = Vec::with_capacity(module.exports().len());
|
||||
@@ -124,6 +123,7 @@ impl Instance {
|
||||
}
|
||||
exports.into_boxed_slice()
|
||||
};
|
||||
module.register_names();
|
||||
Ok(Instance {
|
||||
instance_handle,
|
||||
module: module.clone(),
|
||||
|
||||
@@ -4,7 +4,11 @@ use crate::types::{
|
||||
TableType, ValType,
|
||||
};
|
||||
use anyhow::{Error, Result};
|
||||
use lazy_static::lazy_static;
|
||||
use std::cell::Cell;
|
||||
use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use wasmparser::{
|
||||
validate, CustomSectionKind, ExternalKind, ImportSectionEntryType, ModuleReader, Name,
|
||||
OperatorValidatorConfig, SectionCode, ValidatingParserConfig,
|
||||
@@ -81,8 +85,25 @@ struct ModuleInner {
|
||||
store: Store,
|
||||
imports: Box<[ImportType]>,
|
||||
exports: Box<[ExportType]>,
|
||||
name: Option<String>,
|
||||
compiled: Option<CompiledModule>,
|
||||
compiled: CompiledModule,
|
||||
registered_names: Cell<bool>,
|
||||
names: Arc<Names>,
|
||||
}
|
||||
|
||||
pub struct Names {
|
||||
pub module: Arc<wasmtime_environ::Module>,
|
||||
pub module_name: Option<String>,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// This is a global cache of names known for all compiled modules in this
|
||||
/// process.
|
||||
///
|
||||
/// This global cache is used during `Trap` creation to symbolicate frames.
|
||||
/// This is populated on module compilation, and it is cleared out whenever
|
||||
/// all references to a module are dropped, aka the `Drop for ModuleInner`
|
||||
/// below.
|
||||
pub static ref NAMES: RwLock<HashMap<usize, Arc<Names>>> = RwLock::default();
|
||||
}
|
||||
|
||||
impl Module {
|
||||
@@ -123,10 +144,7 @@ impl Module {
|
||||
/// [binary]: https://webassembly.github.io/spec/core/binary/index.html
|
||||
pub fn new(store: &Store, binary: &[u8]) -> Result<Module> {
|
||||
Module::validate(store, binary)?;
|
||||
// Note that the call to `unsafe` here should be ok because we
|
||||
// previously validated the binary, meaning we're guaranteed to pass a
|
||||
// valid binary for `store`.
|
||||
unsafe { Module::new_internal(store, binary, None) }
|
||||
unsafe { Module::new_unchecked(store, binary) }
|
||||
}
|
||||
|
||||
/// Creates a new WebAssembly `Module` from the given in-memory `binary`
|
||||
@@ -134,11 +152,10 @@ impl Module {
|
||||
///
|
||||
/// See [`Module::new`] for other details.
|
||||
pub fn new_with_name(store: &Store, binary: &[u8], name: &str) -> Result<Module> {
|
||||
Module::validate(store, binary)?;
|
||||
// Note that the call to `unsafe` here should be ok because we
|
||||
// previously validated the binary, meaning we're guaranteed to pass a
|
||||
// valid binary for `store`.
|
||||
unsafe { Module::new_internal(store, binary, Some(name)) }
|
||||
let mut module = Module::new(store, binary)?;
|
||||
let inner = Rc::get_mut(&mut module.inner).unwrap();
|
||||
Arc::get_mut(&mut inner.names).unwrap().module_name = Some(name.to_string());
|
||||
Ok(module)
|
||||
}
|
||||
|
||||
/// Creates a new WebAssembly `Module` from the given in-memory `binary`
|
||||
@@ -168,20 +185,8 @@ impl Module {
|
||||
/// be somewhat valid for decoding purposes, and the basics of decoding can
|
||||
/// still fail.
|
||||
pub unsafe fn new_unchecked(store: &Store, binary: &[u8]) -> Result<Module> {
|
||||
Module::new_internal(store, binary, None)
|
||||
}
|
||||
|
||||
/// Creates a new `Module` and compiles it without doing any validation.
|
||||
unsafe fn new_internal(store: &Store, binary: &[u8], name: Option<&str>) -> Result<Module> {
|
||||
let mut ret = Module::empty(store);
|
||||
let mut ret = Module::compile(store, binary)?;
|
||||
ret.read_imports_and_exports(binary)?;
|
||||
|
||||
let inner = Rc::get_mut(&mut ret.inner).unwrap();
|
||||
if let Some(name) = name {
|
||||
// Assign or override the module's name if supplied.
|
||||
inner.name = Some(name.to_string());
|
||||
}
|
||||
inner.compiled = Some(compile(store, binary, inner.name.as_deref())?);
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
@@ -220,31 +225,42 @@ impl Module {
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn from_exports(store: &Store, exports: Box<[ExportType]>) -> Self {
|
||||
let mut ret = Module::empty(store);
|
||||
let mut ret = unsafe { Module::compile(store, b"\0asm\x01\0\0\0").unwrap() };
|
||||
Rc::get_mut(&mut ret.inner).unwrap().exports = exports;
|
||||
return ret;
|
||||
}
|
||||
|
||||
fn empty(store: &Store) -> Self {
|
||||
Module {
|
||||
unsafe fn compile(store: &Store, binary: &[u8]) -> Result<Self> {
|
||||
let compiled = CompiledModule::new(
|
||||
&mut store.compiler_mut(),
|
||||
binary,
|
||||
store.engine().config().debug_info,
|
||||
)?;
|
||||
|
||||
let names = Arc::new(Names {
|
||||
module_name: None,
|
||||
module: compiled.module().clone(),
|
||||
});
|
||||
Ok(Module {
|
||||
inner: Rc::new(ModuleInner {
|
||||
store: store.clone(),
|
||||
imports: Box::new([]),
|
||||
exports: Box::new([]),
|
||||
name: None,
|
||||
compiled: None,
|
||||
names,
|
||||
compiled,
|
||||
registered_names: Cell::new(false),
|
||||
}),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn compiled_module(&self) -> Option<&CompiledModule> {
|
||||
self.inner.compiled.as_ref()
|
||||
pub(crate) fn compiled_module(&self) -> &CompiledModule {
|
||||
&self.inner.compiled
|
||||
}
|
||||
|
||||
/// Returns identifier/name that this [`Module`] has. This name
|
||||
/// is used in traps/backtrace details.
|
||||
pub fn name(&self) -> Option<&str> {
|
||||
self.inner.name.as_deref()
|
||||
self.inner.names.module_name.as_deref()
|
||||
}
|
||||
|
||||
/// Returns the list of imports that this [`Module`] has and must be
|
||||
@@ -375,7 +391,8 @@ impl Module {
|
||||
while let Ok(entry) = reader.read() {
|
||||
if let Name::Module(name) = entry {
|
||||
if let Ok(name) = name.get_name() {
|
||||
inner.name = Some(name.to_string());
|
||||
Arc::get_mut(&mut inner.names).unwrap().module_name =
|
||||
Some(name.to_string());
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -392,14 +409,24 @@ impl Module {
|
||||
inner.exports = exports.into();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Register this module's names in the global map of module names.
|
||||
///
|
||||
/// This is required to ensure that any traps can be properly symbolicated.
|
||||
pub(crate) fn register_names(&self) {
|
||||
if self.inner.registered_names.get() {
|
||||
return;
|
||||
}
|
||||
let names = self.inner.names.clone();
|
||||
NAMES.write().unwrap().insert(names.module.id, names);
|
||||
self.inner.registered_names.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
fn compile(store: &Store, binary: &[u8], module_name: Option<&str>) -> Result<CompiledModule> {
|
||||
let compiled_module = CompiledModule::new(
|
||||
&mut store.compiler_mut(),
|
||||
binary,
|
||||
module_name,
|
||||
store.engine().config().debug_info,
|
||||
)?;
|
||||
Ok(compiled_module)
|
||||
impl Drop for ModuleInner {
|
||||
fn drop(&mut self) {
|
||||
if self.registered_names.get() {
|
||||
NAMES.write().unwrap().remove(&self.names.module.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use crate::runtime::Store;
|
||||
use anyhow::Result;
|
||||
use std::any::Any;
|
||||
use std::collections::HashSet;
|
||||
use std::rc::Rc;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::entity::PrimaryMap;
|
||||
use wasmtime_environ::wasm::DefinedFuncIndex;
|
||||
use wasmtime_environ::Module;
|
||||
@@ -37,7 +37,7 @@ pub(crate) fn create_handle(
|
||||
.unwrap_or_else(PrimaryMap::new);
|
||||
|
||||
Ok(InstanceHandle::new(
|
||||
Rc::new(module),
|
||||
Arc::new(module),
|
||||
finished_functions.into_boxed_slice(),
|
||||
imports,
|
||||
&data_initializers,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use crate::module::NAMES;
|
||||
use backtrace::Backtrace;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::entity::EntityRef;
|
||||
|
||||
/// A struct representing an aborted instruction execution, with a message
|
||||
/// indicating the cause.
|
||||
@@ -36,15 +38,22 @@ impl Trap {
|
||||
|
||||
fn new_with_trace(message: String, native_trace: Backtrace) -> Self {
|
||||
let mut wasm_trace = Vec::new();
|
||||
let names = NAMES.read().unwrap();
|
||||
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.get().map(|s| s.to_string()),
|
||||
func_name: info.func_name.get().map(|s| s.to_string()),
|
||||
})
|
||||
}
|
||||
let info = match wasmtime_runtime::jit_function_registry::find(pc) {
|
||||
Some(info) => info,
|
||||
None => continue,
|
||||
};
|
||||
let names = match names.get(&info.module_id) {
|
||||
Some(names) => names,
|
||||
None => continue,
|
||||
};
|
||||
wasm_trace.push(FrameInfo {
|
||||
func_index: info.func_index.index() as u32,
|
||||
module_name: names.module_name.clone(),
|
||||
func_name: names.module.func_names.get(&info.func_index).cloned(),
|
||||
})
|
||||
}
|
||||
Trap {
|
||||
inner: Arc::new(TrapInner {
|
||||
|
||||
Reference in New Issue
Block a user