From 3f9bff17c81c84f09e0cc357cad82ac76d40f993 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 16 Mar 2022 09:18:16 -0500 Subject: [PATCH] Support disabling backtraces at compile time (#3932) * Support disabling backtraces at compile time This commit adds support to Wasmtime to disable, at compile time, the gathering of backtraces on traps. The `wasmtime` crate now sports a `wasm-backtrace` feature which, when disabled, will mean that backtraces are never collected at compile time nor are unwinding tables inserted into compiled objects. The motivation for this commit stems from the fact that generating a backtrace is quite a slow operation. Currently backtrace generation is done with libunwind and `_Unwind_Backtrace` typically found in glibc or other system libraries. When thousands of modules are loaded into the same process though this means that the initial backtrace can take nearly half a second and all subsequent backtraces can take upwards of hundreds of milliseconds. Relative to all other operations in Wasmtime this is extremely expensive at this time. In the future we'd like to implement a more performant backtrace scheme but such an implementation would require coordination with Cranelift and is a big chunk of work that may take some time, so in the meantime if embedders don't need a backtrace they can still use this option to disable backtraces at compile time and avoid the performance pitfalls of collecting backtraces. In general I tried to originally make this a runtime configuration option but ended up opting for a compile-time option because `Trap::new` otherwise has no arguments and always captures a backtrace. By making this a compile-time option it was possible to configure, statically, the behavior of `Trap::new`. Additionally I also tried to minimize the amount of `#[cfg]` necessary by largely only having it at the producer and consumer sites. Also a noteworthy restriction of this implementation is that if backtrace support is disabled at compile time then reference types support will be unconditionally disabled at runtime. With backtrace support disabled there's no way to trace the stack of wasm frames which means that GC can't happen given our current implementation. * Always enable backtraces for the C API --- .github/workflows/main.yml | 3 +- Cargo.toml | 2 + crates/c-api/Cargo.toml | 2 +- crates/cranelift/src/compiler.rs | 20 +++-- crates/runtime/Cargo.toml | 4 +- crates/runtime/src/externref.rs | 6 +- crates/runtime/src/lib.rs | 2 +- crates/runtime/src/libcalls.rs | 6 +- crates/runtime/src/traphandlers.rs | 40 ++++++++-- crates/wasmtime/Cargo.toml | 9 ++- crates/wasmtime/src/config.rs | 27 +++++-- crates/wasmtime/src/engine.rs | 2 +- crates/wasmtime/src/lib.rs | 8 ++ crates/wasmtime/src/trap.rs | 124 ++++++++++++++++------------- src/lib.rs | 2 + 15 files changed, 173 insertions(+), 84 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7e834ffd61..cca76a9f12 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -137,7 +137,8 @@ jobs: - run: cargo check -p wasmtime --no-default-features --features uffd - run: cargo check -p wasmtime --no-default-features --features pooling-allocator - run: cargo check -p wasmtime --no-default-features --features cranelift - - run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache + - run: cargo check -p wasmtime --no-default-features --features wasm-backtrace + - run: cargo check -p wasmtime --no-default-features --features cranelift,wat,async,cache,wasm-backtrace # Check that benchmarks of the cranelift project build - run: cargo check --benches -p cranelift-codegen diff --git a/Cargo.toml b/Cargo.toml index 0ba738d8fd..824130ea3b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -99,6 +99,7 @@ default = [ "wasi-nn", "pooling-allocator", "memory-init-cow", + "wasm-backtrace", ] jitdump = ["wasmtime/jitdump"] vtune = ["wasmtime/vtune"] @@ -109,6 +110,7 @@ memory-init-cow = ["wasmtime/memory-init-cow"] pooling-allocator = ["wasmtime/pooling-allocator"] all-arch = ["wasmtime/all-arch"] posix-signals-on-macos = ["wasmtime/posix-signals-on-macos"] +wasm-backtrace = ["wasmtime/wasm-backtrace"] # Stub feature that does nothing, for Cargo-features compatibility: the new # backend is the default now. diff --git a/crates/c-api/Cargo.toml b/crates/c-api/Cargo.toml index e431da2c8b..ed11a4bdc7 100644 --- a/crates/c-api/Cargo.toml +++ b/crates/c-api/Cargo.toml @@ -20,7 +20,7 @@ doctest = false env_logger = "0.8" anyhow = "1.0" once_cell = "1.3" -wasmtime = { path = "../wasmtime", default-features = false, features = ['cranelift'] } +wasmtime = { path = "../wasmtime", default-features = false, features = ['cranelift', 'wasm-backtrace'] } wasmtime-c-api-macros = { path = "macros" } # Optional dependency for the `wat2wasm` API diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs index f7f0e2d2b2..5290630162 100644 --- a/crates/cranelift/src/compiler.rs +++ b/crates/cranelift/src/compiler.rs @@ -188,9 +188,13 @@ impl wasmtime_environ::Compiler for Compiler { let stack_maps = mach_stack_maps_to_stack_maps(result.buffer.stack_maps()); - let unwind_info = context - .create_unwind_info(isa) - .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?; + let unwind_info = if isa.flags().unwind_info() { + context + .create_unwind_info(isa) + .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))? + } else { + None + }; let address_transform = self.get_function_address_map(&context, &input, code_buf.len() as u32, tunables); @@ -566,9 +570,13 @@ impl Compiler { .relocs() .is_empty()); - let unwind_info = context - .create_unwind_info(isa) - .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?; + let unwind_info = if isa.flags().unwind_info() { + context + .create_unwind_info(isa) + .map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))? + } else { + None + }; Ok(CompiledFunction { body: code_buf, diff --git a/crates/runtime/Cargo.toml b/crates/runtime/Cargo.toml index 1ef8bb5634..2f31f9b63e 100644 --- a/crates/runtime/Cargo.toml +++ b/crates/runtime/Cargo.toml @@ -22,7 +22,7 @@ indexmap = "1.0.2" thiserror = "1.0.4" more-asserts = "0.2.1" cfg-if = "1.0" -backtrace = "0.3.61" +backtrace = { version = "0.3.61", optional = true } rand = "0.8.3" anyhow = "1.0.38" memfd = { version = "0.4.1", optional = true } @@ -46,8 +46,8 @@ cc = "1.0" maintenance = { status = "actively-developed" } [features] -default = [] memory-init-cow = ['memfd'] +wasm-backtrace = ["backtrace"] async = ["wasmtime-fiber"] diff --git a/crates/runtime/src/externref.rs b/crates/runtime/src/externref.rs index 94c177c47a..29a011b6ef 100644 --- a/crates/runtime/src/externref.rs +++ b/crates/runtime/src/externref.rs @@ -704,6 +704,7 @@ impl VMExternRefActivationsTable { } } + #[cfg_attr(not(feature = "wasm-backtrace"), allow(dead_code))] fn insert_precise_stack_root( precise_stack_roots: &mut HashSet, root: NonNull, @@ -866,6 +867,7 @@ impl std::ops::DerefMut for DebugOnly { /// /// Additionally, you must have registered the stack maps for every Wasm module /// that has frames on the stack with the given `stack_maps_registry`. +#[cfg_attr(not(feature = "wasm-backtrace"), allow(unused_mut, unused_variables))] pub unsafe fn gc( module_info_lookup: &dyn ModuleInfoLookup, externref_activations_table: &mut VMExternRefActivationsTable, @@ -893,6 +895,7 @@ pub unsafe fn gc( None => { if cfg!(debug_assertions) { // Assert that there aren't any Wasm frames on the stack. + #[cfg(feature = "wasm-backtrace")] backtrace::trace(|frame| { assert!(module_info_lookup.lookup(frame.ip() as usize).is_none()); true @@ -917,7 +920,7 @@ pub unsafe fn gc( // newly-discovered precise set. // The SP of the previous (younger) frame we processed. - let mut last_sp = None; + let mut last_sp: Option = None; // Whether we have found our stack canary or not yet. let mut found_canary = false; @@ -934,6 +937,7 @@ pub unsafe fn gc( }); } + #[cfg(feature = "wasm-backtrace")] backtrace::trace(|frame| { let pc = frame.ip() as usize; let sp = frame.sp() as usize; diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs index 5e64f65480..d7d7d0ec92 100644 --- a/crates/runtime/src/lib.rs +++ b/crates/runtime/src/lib.rs @@ -61,7 +61,7 @@ pub use crate::mmap_vec::MmapVec; pub use crate::table::{Table, TableElement}; pub use crate::traphandlers::{ catch_traps, init_traps, raise_lib_trap, raise_user_trap, resume_panic, tls_eager_initialize, - SignalHandler, TlsRestore, Trap, + Backtrace, SignalHandler, TlsRestore, Trap, }; pub use crate::vmcontext::{ VMCallerCheckedAnyfunc, VMContext, VMFunctionBody, VMFunctionImport, VMGlobalDefinition, diff --git a/crates/runtime/src/libcalls.rs b/crates/runtime/src/libcalls.rs index 8fe7fa3351..8375d9c9e1 100644 --- a/crates/runtime/src/libcalls.rs +++ b/crates/runtime/src/libcalls.rs @@ -61,7 +61,6 @@ use crate::instance::Instance; use crate::table::{Table, TableElementType}; use crate::traphandlers::{raise_lib_trap, resume_panic, Trap}; use crate::vmcontext::{VMCallerCheckedAnyfunc, VMContext}; -use backtrace::Backtrace; use std::mem; use std::ptr::{self, NonNull}; use wasmtime_environ::{ @@ -588,10 +587,7 @@ unsafe fn validate_atomic_addr( addr: usize, ) -> Result<(), Trap> { if addr > instance.get_memory(memory).current_length { - return Err(Trap::Wasm { - trap_code: TrapCode::HeapOutOfBounds, - backtrace: Backtrace::new_unresolved(), - }); + return Err(Trap::wasm(TrapCode::HeapOutOfBounds)); } Ok(()) } diff --git a/crates/runtime/src/traphandlers.rs b/crates/runtime/src/traphandlers.rs index 240a03b512..c3b323832b 100644 --- a/crates/runtime/src/traphandlers.rs +++ b/crates/runtime/src/traphandlers.rs @@ -3,7 +3,6 @@ use crate::VMContext; use anyhow::Error; -use backtrace::Backtrace; use std::any::Any; use std::cell::{Cell, UnsafeCell}; use std::mem::MaybeUninit; @@ -143,10 +142,9 @@ impl Trap { /// /// Internally saves a backtrace when constructed. pub fn wasm(trap_code: TrapCode) -> Self { - let backtrace = Backtrace::new_unresolved(); Trap::Wasm { trap_code, - backtrace, + backtrace: Backtrace::new(), } } @@ -154,8 +152,38 @@ impl Trap { /// /// Internally saves a backtrace when constructed. pub fn oom() -> Self { - let backtrace = Backtrace::new_unresolved(); - Trap::OOM { backtrace } + Trap::OOM { + backtrace: Backtrace::new(), + } + } +} + +/// A crate-local backtrace type which conditionally, at compile time, actually +/// contains a backtrace from the `backtrace` crate or nothing. +#[derive(Debug)] +pub struct Backtrace { + #[cfg(feature = "wasm-backtrace")] + trace: backtrace::Backtrace, +} + +impl Backtrace { + /// Captures a new backtrace + /// + /// Note that this function does nothing if the `wasm-backtrace` feature is + /// disabled. + pub fn new() -> Backtrace { + Backtrace { + #[cfg(feature = "wasm-backtrace")] + trace: backtrace::Backtrace::new_unresolved(), + } + } + + /// Returns the backtrace frames associated with this backtrace. Note that + /// this is conditionally defined and not present when `wasm-backtrace` is + /// not present. + #[cfg(feature = "wasm-backtrace")] + pub fn frames(&self) -> &[backtrace::BacktraceFrame] { + self.trace.frames() } } @@ -299,7 +327,7 @@ impl CallThreadState { } fn capture_backtrace(&self, pc: *const u8) { - let backtrace = Backtrace::new_unresolved(); + let backtrace = Backtrace::new(); unsafe { (*self.unwind.get()) .as_mut_ptr() diff --git a/crates/wasmtime/Cargo.toml b/crates/wasmtime/Cargo.toml index e0981952ac..87d2fbb315 100644 --- a/crates/wasmtime/Cargo.toml +++ b/crates/wasmtime/Cargo.toml @@ -25,7 +25,7 @@ anyhow = "1.0.19" region = "2.2.0" libc = "0.2" cfg-if = "1.0" -backtrace = "0.3.61" +backtrace = { version = "0.3.61", optional = true } log = "0.4.8" wat = { version = "1.0.36", optional = true } serde = { version = "1.0.94", features = ["derive"] } @@ -61,6 +61,7 @@ default = [ 'pooling-allocator', 'memory-init-cow', 'vtune', + 'wasm-backtrace', ] # An on-by-default feature enabling runtime compilation of WebAssembly modules @@ -108,3 +109,9 @@ posix-signals-on-macos = ["wasmtime-runtime/posix-signals-on-macos"] # Enabling this feature has no effect on unsupported platforms or when the # `uffd` feature is enabled. memory-init-cow = ["wasmtime-runtime/memory-init-cow"] + +# Enables runtime support necessary to capture backtraces of WebAssembly code +# that is running. +# +# This is enabled by default. +wasm-backtrace = ["wasmtime-runtime/wasm-backtrace", "backtrace"] diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 6de73cf919..ed01cca812 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -141,7 +141,9 @@ impl Config { ret.cranelift_debug_verifier(false); ret.cranelift_opt_level(OptLevel::Speed); } + #[cfg(feature = "wasm-backtrace")] ret.wasm_reference_types(true); + ret.features.reference_types = cfg!(feature = "wasm-backtrace"); ret.wasm_multi_value(true); ret.wasm_bulk_memory(true); ret.wasm_simd(true); @@ -502,10 +504,14 @@ impl Config { /// Note that enabling the reference types feature will also enable the bulk /// memory feature. /// - /// This is `true` by default on x86-64, and `false` by default on other - /// architectures. + /// This feature is `true` by default. If the `wasm-backtrace` feature is + /// disabled at compile time, however, then this is `false` by default and + /// it cannot be turned on since GC currently requires backtraces to work. + /// Note that the `wasm-backtrace` feature is on by default, however. /// /// [proposal]: https://github.com/webassembly/reference-types + #[cfg(feature = "wasm-backtrace")] + #[cfg_attr(nightlydoc, doc(cfg(feature = "wasm-backtrace")))] pub fn wasm_reference_types(&mut self, enable: bool) -> &mut Self { self.features.reference_types = enable; @@ -1272,9 +1278,20 @@ impl Config { #[cfg(compiler)] fn compiler_builder(strategy: Strategy) -> Result> { - match strategy { - Strategy::Auto | Strategy::Cranelift => Ok(wasmtime_cranelift::builder()), - } + let mut builder = match strategy { + Strategy::Auto | Strategy::Cranelift => wasmtime_cranelift::builder(), + }; + builder + .set( + "unwind_info", + if cfg!(feature = "wasm-backtrace") { + "true" + } else { + "false" + }, + ) + .unwrap(); + Ok(builder) } fn round_up_to_pages(val: u64) -> u64 { diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs index c74c067065..1c6d858384 100644 --- a/crates/wasmtime/src/engine.rs +++ b/crates/wasmtime/src/engine.rs @@ -304,7 +304,7 @@ impl Engine { // can affect the way the generated code performs or behaves at // runtime. "avoid_div_traps" => *value == FlagValue::Bool(true), - "unwind_info" => *value == FlagValue::Bool(true), + "unwind_info" => *value == FlagValue::Bool(cfg!(feature = "wasm-backtrace")), "libcall_call_conv" => *value == FlagValue::Enum("isa_default".into()), // Features wasmtime doesn't use should all be disabled, since diff --git a/crates/wasmtime/src/lib.rs b/crates/wasmtime/src/lib.rs index 26bb8a140e..df110a2938 100644 --- a/crates/wasmtime/src/lib.rs +++ b/crates/wasmtime/src/lib.rs @@ -290,6 +290,14 @@ //! run-time via [`Config::memory_init_cow`] (which is also enabled by //! default). //! +//! * `wasm-backtrace` - Enabled by default, this feature builds in support to +//! generate backtraces at runtime for WebAssembly modules. This means that +//! unwinding information is compiled into wasm modules and necessary runtime +//! dependencies are enabled as well. If this is turned off then some methods +//! to look at trap frames will not be available. Additionally at this time +//! disabling this feature means that the reference types feature is always +//! disabled as well. +//! //! ## Examples //! //! In addition to the examples below be sure to check out the [online embedding diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs index 4fa0a19813..1f81f788a4 100644 --- a/crates/wasmtime/src/trap.rs +++ b/crates/wasmtime/src/trap.rs @@ -1,10 +1,10 @@ use crate::module::GlobalModuleRegistry; use crate::FrameInfo; -use backtrace::Backtrace; use std::fmt; use std::sync::Arc; use wasmtime_environ::TrapCode as EnvTrapCode; use wasmtime_jit::{demangle_function_name, demangle_function_name_or_index}; +use wasmtime_runtime::Backtrace; /// A struct representing an aborted instruction execution, with a message /// indicating the cause. @@ -129,8 +129,10 @@ impl fmt::Display for TrapCode { struct TrapInner { reason: TrapReason, + #[cfg(feature = "wasm-backtrace")] wasm_trace: Vec, native_trace: Backtrace, + #[cfg(feature = "wasm-backtrace")] hint_wasm_backtrace_details_env: bool, } @@ -148,18 +150,14 @@ impl Trap { #[cold] // traps are exceptional, this helps move handling off the main path pub fn new>(message: I) -> Self { let reason = TrapReason::Message(message.into()); - Trap::new_with_trace(None, reason, Backtrace::new_unresolved()) + Trap::new_with_trace(None, reason, Backtrace::new()) } /// Creates a new `Trap` representing an explicit program exit with a classic `i32` /// exit status value. #[cold] // see Trap::new pub fn i32_exit(status: i32) -> Self { - Trap::new_with_trace( - None, - TrapReason::I32Exit(status), - Backtrace::new_unresolved(), - ) + Trap::new_with_trace(None, TrapReason::I32Exit(status), Backtrace::new()) } #[cold] // see Trap::new @@ -212,10 +210,12 @@ impl Trap { /// * `native_trace` - this is a captured backtrace from when the trap /// occurred, and this will iterate over the frames to find frames that /// lie in wasm jit code. + #[cfg_attr(not(feature = "wasm-backtrace"), allow(unused_mut, unused_variables))] fn new_with_trace(trap_pc: Option, reason: TrapReason, native_trace: Backtrace) -> Self { - let mut wasm_trace = Vec::new(); + let mut wasm_trace = Vec::::new(); let mut hint_wasm_backtrace_details_env = false; + #[cfg(feature = "wasm-backtrace")] GlobalModuleRegistry::with(|registry| { for frame in native_trace.frames() { let pc = frame.ip() as usize; @@ -253,8 +253,10 @@ impl Trap { Trap { inner: Arc::new(TrapInner { reason, - wasm_trace, native_trace, + #[cfg(feature = "wasm-backtrace")] + wasm_trace, + #[cfg(feature = "wasm-backtrace")] hint_wasm_backtrace_details_env, }), } @@ -281,6 +283,8 @@ impl Trap { /// Returns a list of function frames in WebAssembly code that led to this /// trap happening. + #[cfg(feature = "wasm-backtrace")] + #[cfg_attr(nightlydoc, doc(cfg(feature = "wasm-backtrace")))] pub fn trace(&self) -> &[FrameInfo] { &self.inner.wasm_trace } @@ -297,65 +301,77 @@ impl Trap { impl fmt::Debug for Trap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Trap") - .field("reason", &self.inner.reason) - .field("wasm_trace", &self.inner.wasm_trace) - .field("native_trace", &self.inner.native_trace) - .finish() + let mut f = f.debug_struct("Trap"); + f.field("reason", &self.inner.reason); + #[cfg(feature = "wasm-backtrace")] + { + f.field("wasm_trace", &self.inner.wasm_trace) + .field("native_trace", &self.inner.native_trace); + } + f.finish() } } impl fmt::Display for Trap { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.inner.reason)?; - let trace = self.trace(); - if trace.is_empty() { - return Ok(()); - } - writeln!(f, "\nwasm backtrace:")?; - for (i, frame) in self.trace().iter().enumerate() { - let name = frame.module_name().unwrap_or(""); - write!(f, " {:>3}: ", i)?; - if let Some(offset) = frame.module_offset() { - write!(f, "{:#6x} - ", offset)?; + #[cfg(feature = "wasm-backtrace")] + { + let trace = self.trace(); + if trace.is_empty() { + return Ok(()); } + writeln!(f, "\nwasm backtrace:")?; - let write_raw_func_name = |f: &mut fmt::Formatter<'_>| { - demangle_function_name_or_index(f, frame.func_name(), frame.func_index() as usize) - }; - if frame.symbols().is_empty() { - write!(f, "{}!", name)?; - write_raw_func_name(f)?; - writeln!(f, "")?; - } else { - for (i, symbol) in frame.symbols().iter().enumerate() { - if i > 0 { - write!(f, " - ")?; - } else { - // ... - } - match symbol.name() { - Some(name) => demangle_function_name(f, name)?, - None if i == 0 => write_raw_func_name(f)?, - None => write!(f, "")?, - } + for (i, frame) in self.trace().iter().enumerate() { + let name = frame.module_name().unwrap_or(""); + write!(f, " {:>3}: ", i)?; + + if let Some(offset) = frame.module_offset() { + write!(f, "{:#6x} - ", offset)?; + } + + let write_raw_func_name = |f: &mut fmt::Formatter<'_>| { + demangle_function_name_or_index( + f, + frame.func_name(), + frame.func_index() as usize, + ) + }; + if frame.symbols().is_empty() { + write!(f, "{}!", name)?; + write_raw_func_name(f)?; writeln!(f, "")?; - if let Some(file) = symbol.file() { - write!(f, " at {}", file)?; - if let Some(line) = symbol.line() { - write!(f, ":{}", line)?; - if let Some(col) = symbol.column() { - write!(f, ":{}", col)?; + } else { + for (i, symbol) in frame.symbols().iter().enumerate() { + if i > 0 { + write!(f, " - ")?; + } else { + // ... + } + match symbol.name() { + Some(name) => demangle_function_name(f, name)?, + None if i == 0 => write_raw_func_name(f)?, + None => write!(f, "")?, + } + writeln!(f, "")?; + if let Some(file) = symbol.file() { + write!(f, " at {}", file)?; + if let Some(line) = symbol.line() { + write!(f, ":{}", line)?; + if let Some(col) = symbol.column() { + write!(f, ":{}", col)?; + } } } + writeln!(f, "")?; } - writeln!(f, "")?; } } - } - if self.inner.hint_wasm_backtrace_details_env { - writeln!(f, "note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable to may show more debugging information")?; + if self.inner.hint_wasm_backtrace_details_env { + writeln!(f, "note: using the `WASMTIME_BACKTRACE_DETAILS=1` environment variable to may show more debugging information")?; + } } Ok(()) } @@ -388,7 +404,7 @@ impl From> for Trap { trap.clone() } else { let reason = TrapReason::Error(e.into()); - Trap::new_with_trace(None, reason, Backtrace::new_unresolved()) + Trap::new_with_trace(None, reason, Backtrace::new()) } } } diff --git a/src/lib.rs b/src/lib.rs index 1de3ec4fa2..4da060dd70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -384,7 +384,9 @@ impl CommonOptions { config.wasm_bulk_memory(enable); } if let Some(enable) = reference_types { + #[cfg(feature = "wasm-backtrace")] config.wasm_reference_types(enable); + drop(enable); // suppress unused warnings } if let Some(enable) = multi_value { config.wasm_multi_value(enable);