Implement canon lower of a canon lift function in the same component (#4347)
* Implement `canon lower` of a `canon lift` function in the same component This commit implements the "degenerate" logic for implementing a function within a component that is lifted and then immediately lowered again. In this situation the lowered function will immediately generate a trap and doesn't need to implement anything else. The implementation in this commit is somewhat heavyweight but I think is probably justified moreso in future additions to the component model rather than what exactly is here right now. It's not expected that this "always trap" functionality will really be used all that often since it would generally mean a buggy component, but the functionality plumbed through here is hopefully going to be useful for implementing component-to-component adapter trampolines. Specifically this commit implements a strategy where the `canon.lower`'d function is generated by Cranelift and simply has a single trap instruction when called, doing nothing else. The main complexity comes from juggling around all the data associated with these functions, primarily plumbing through the traps into the `ModuleRegistry` to ensure that the global `is_wasm_trap_pc` function returns `true` and at runtime when we lookup information about the trap it's all readily available (e.g. translating the trapping pc to a `TrapCode`). * Fix non-component build * Fix some offset calculations * Only create one "always trap" per signature Use an internal map to deduplicate during compilation.
This commit is contained in:
@@ -342,6 +342,7 @@ impl wasmtime_environ::Compiler for Compiler {
|
||||
.iter()
|
||||
.zip(&compiled_trampolines)
|
||||
{
|
||||
assert!(func.traps.is_empty());
|
||||
trampolines.push(builder.trampoline(*i, &func));
|
||||
}
|
||||
|
||||
@@ -683,19 +684,21 @@ impl Compiler {
|
||||
context
|
||||
.compile_and_emit(isa, &mut code_buf)
|
||||
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
|
||||
let result = context.mach_compile_result.as_ref().unwrap();
|
||||
|
||||
// Processing relocations isn't the hardest thing in the world here but
|
||||
// no trampoline should currently generate a relocation, so assert that
|
||||
// they're all empty and if this ever trips in the future then handling
|
||||
// will need to be added here to ensure they make their way into the
|
||||
// `CompiledFunction` below.
|
||||
assert!(context
|
||||
.mach_compile_result
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
assert!(result.buffer.relocs().is_empty());
|
||||
|
||||
let traps = result
|
||||
.buffer
|
||||
.relocs()
|
||||
.is_empty());
|
||||
.traps()
|
||||
.into_iter()
|
||||
.map(mach_trap_to_trap)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let unwind_info = if isa.flags().unwind_info() {
|
||||
context
|
||||
@@ -713,7 +716,7 @@ impl Compiler {
|
||||
value_labels_ranges: Default::default(),
|
||||
info: Default::default(),
|
||||
address_map: Default::default(),
|
||||
traps: Vec::new(),
|
||||
traps,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -889,6 +892,8 @@ fn mach_reloc_to_reloc(reloc: &MachReloc) -> Relocation {
|
||||
}
|
||||
}
|
||||
|
||||
const ALWAYS_TRAP_CODE: u16 = 100;
|
||||
|
||||
fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation {
|
||||
let &MachTrap { offset, code } = trap;
|
||||
TrapInformation {
|
||||
@@ -905,6 +910,7 @@ fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation {
|
||||
ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
|
||||
ir::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
|
||||
ir::TrapCode::Interrupt => TrapCode::Interrupt,
|
||||
ir::TrapCode::User(ALWAYS_TRAP_CODE) => TrapCode::AlwaysTrapAdapter,
|
||||
|
||||
// these should never be emitted by wasmtime-cranelift
|
||||
ir::TrapCode::User(_) => unreachable!(),
|
||||
|
||||
@@ -9,10 +9,10 @@ use cranelift_frontend::FunctionBuilder;
|
||||
use object::write::Object;
|
||||
use std::any::Any;
|
||||
use wasmtime_environ::component::{
|
||||
CanonicalOptions, Component, ComponentCompiler, ComponentTypes, LowerImport, LoweredIndex,
|
||||
LoweringInfo, VMComponentOffsets,
|
||||
AlwaysTrapInfo, CanonicalOptions, Component, ComponentCompiler, ComponentTypes, LowerImport,
|
||||
LoweredIndex, LoweringInfo, RuntimeAlwaysTrapIndex, VMComponentOffsets,
|
||||
};
|
||||
use wasmtime_environ::{PrimaryMap, SignatureIndex, Trampoline};
|
||||
use wasmtime_environ::{PrimaryMap, SignatureIndex, Trampoline, TrapCode, WasmFuncType};
|
||||
|
||||
impl ComponentCompiler for Compiler {
|
||||
fn compile_lowered_trampoline(
|
||||
@@ -152,25 +152,51 @@ impl ComponentCompiler for Compiler {
|
||||
Ok(Box::new(func))
|
||||
}
|
||||
|
||||
fn compile_always_trap(&self, ty: &WasmFuncType) -> Result<Box<dyn Any + Send>> {
|
||||
let isa = &*self.isa;
|
||||
let CompilerContext {
|
||||
mut func_translator,
|
||||
codegen_context: mut context,
|
||||
} = self.take_context();
|
||||
context.func = ir::Function::with_name_signature(
|
||||
ir::ExternalName::user(0, 0),
|
||||
crate::indirect_signature(isa, ty),
|
||||
);
|
||||
let mut builder = FunctionBuilder::new(&mut context.func, func_translator.context());
|
||||
let block0 = builder.create_block();
|
||||
builder.append_block_params_for_function_params(block0);
|
||||
builder.switch_to_block(block0);
|
||||
builder.seal_block(block0);
|
||||
builder
|
||||
.ins()
|
||||
.trap(ir::TrapCode::User(super::ALWAYS_TRAP_CODE));
|
||||
builder.finalize();
|
||||
|
||||
let func: CompiledFunction = self.finish_trampoline(&mut context, isa)?;
|
||||
self.save_context(CompilerContext {
|
||||
func_translator,
|
||||
codegen_context: context,
|
||||
});
|
||||
Ok(Box::new(func))
|
||||
}
|
||||
|
||||
fn emit_obj(
|
||||
&self,
|
||||
lowerings: PrimaryMap<LoweredIndex, Box<dyn Any + Send>>,
|
||||
always_trap: PrimaryMap<RuntimeAlwaysTrapIndex, Box<dyn Any + Send>>,
|
||||
trampolines: Vec<(SignatureIndex, Box<dyn Any + Send>)>,
|
||||
obj: &mut Object<'static>,
|
||||
) -> Result<(PrimaryMap<LoweredIndex, LoweringInfo>, Vec<Trampoline>)> {
|
||||
let lowerings: PrimaryMap<LoweredIndex, CompiledFunction> = lowerings
|
||||
.into_iter()
|
||||
.map(|(_, f)| *f.downcast().unwrap())
|
||||
.collect();
|
||||
let trampolines: Vec<(SignatureIndex, CompiledFunction)> = trampolines
|
||||
.into_iter()
|
||||
.map(|(i, f)| (i, *f.downcast().unwrap()))
|
||||
.collect();
|
||||
|
||||
) -> Result<(
|
||||
PrimaryMap<LoweredIndex, LoweringInfo>,
|
||||
PrimaryMap<RuntimeAlwaysTrapIndex, AlwaysTrapInfo>,
|
||||
Vec<Trampoline>,
|
||||
)> {
|
||||
let module = Default::default();
|
||||
let mut text = ModuleTextBuilder::new(obj, &module, &*self.isa);
|
||||
let mut ret = PrimaryMap::new();
|
||||
for (idx, lowering) in lowerings.iter() {
|
||||
let lowering = lowering.downcast_ref::<CompiledFunction>().unwrap();
|
||||
assert!(lowering.traps.is_empty());
|
||||
let (_symbol, range) = text.append_func(
|
||||
false,
|
||||
format!("_wasm_component_lowering_trampoline{}", idx.as_u32()).into_bytes(),
|
||||
@@ -183,13 +209,35 @@ impl ComponentCompiler for Compiler {
|
||||
});
|
||||
assert_eq!(i, idx);
|
||||
}
|
||||
let ret_always_trap = always_trap
|
||||
.iter()
|
||||
.map(|(i, func)| {
|
||||
let func = func.downcast_ref::<CompiledFunction>().unwrap();
|
||||
assert_eq!(func.traps.len(), 1);
|
||||
assert_eq!(func.traps[0].trap_code, TrapCode::AlwaysTrapAdapter);
|
||||
let name = format!("_wasmtime_always_trap{}", i.as_u32());
|
||||
let range = text.named_func(&name, func);
|
||||
let start = u32::try_from(range.start).unwrap();
|
||||
let end = u32::try_from(range.end).unwrap();
|
||||
AlwaysTrapInfo {
|
||||
start: start,
|
||||
length: end - start,
|
||||
trap_offset: func.traps[0].code_offset,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
let ret_trampolines = trampolines
|
||||
.iter()
|
||||
.map(|(i, func)| text.trampoline(*i, func))
|
||||
.map(|(i, func)| {
|
||||
let func = func.downcast_ref::<CompiledFunction>().unwrap();
|
||||
assert!(func.traps.is_empty());
|
||||
text.trampoline(*i, func)
|
||||
})
|
||||
.collect();
|
||||
|
||||
text.finish()?;
|
||||
|
||||
Ok((ret, ret_trampolines))
|
||||
Ok((ret, ret_always_trap, ret_trampolines))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ impl<'a> ModuleTextBuilder<'a> {
|
||||
|
||||
pub fn trampoline(&mut self, sig: SignatureIndex, func: &'a CompiledFunction) -> Trampoline {
|
||||
let name = obj::trampoline_symbol_name(sig);
|
||||
let (_, range) = self.append_func(false, name.into_bytes(), func);
|
||||
let range = self.named_func(&name, func);
|
||||
Trampoline {
|
||||
signature: sig,
|
||||
start: range.start,
|
||||
@@ -176,6 +176,11 @@ impl<'a> ModuleTextBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn named_func(&mut self, name: &str, func: &'a CompiledFunction) -> Range<u64> {
|
||||
let (_, range) = self.append_func(false, name.as_bytes().to_vec(), func);
|
||||
range
|
||||
}
|
||||
|
||||
/// Forces "veneers" to be used for inter-function calls in the text
|
||||
/// section which means that in-bounds optimized addresses are never used.
|
||||
///
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
use crate::component::{Component, ComponentTypes, LowerImport, LoweredIndex};
|
||||
use crate::{PrimaryMap, SignatureIndex, Trampoline};
|
||||
use crate::component::{
|
||||
Component, ComponentTypes, LowerImport, LoweredIndex, RuntimeAlwaysTrapIndex,
|
||||
};
|
||||
use crate::{PrimaryMap, SignatureIndex, Trampoline, WasmFuncType};
|
||||
use anyhow::Result;
|
||||
use object::write::Object;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -16,6 +18,19 @@ pub struct LoweringInfo {
|
||||
pub length: u32,
|
||||
}
|
||||
|
||||
/// Description of an "always trap" function generated by
|
||||
/// `ComponentCompiler::compile_always_trap`.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct AlwaysTrapInfo {
|
||||
/// The byte offset from the start of the text section where this trampoline
|
||||
/// starts.
|
||||
pub start: u32,
|
||||
/// The byte length of this trampoline's function body.
|
||||
pub length: u32,
|
||||
/// The offset from `start` of where the trapping instruction is located.
|
||||
pub trap_offset: u32,
|
||||
}
|
||||
|
||||
/// Compilation support necessary for components.
|
||||
pub trait ComponentCompiler: Send + Sync {
|
||||
/// Creates a trampoline for a `canon.lower`'d host function.
|
||||
@@ -42,6 +57,13 @@ pub trait ComponentCompiler: Send + Sync {
|
||||
types: &ComponentTypes,
|
||||
) -> Result<Box<dyn Any + Send>>;
|
||||
|
||||
/// Creates a function which will always trap that has the `ty` specified.
|
||||
///
|
||||
/// This will create a small trampoline whose only purpose is to generate a
|
||||
/// trap at runtime. This is used to implement the degenerate case of a
|
||||
/// `canon lift`'d function immediately being `canon lower`'d.
|
||||
fn compile_always_trap(&self, ty: &WasmFuncType) -> Result<Box<dyn Any + Send>>;
|
||||
|
||||
/// Emits the `lowerings` and `trampolines` specified into the in-progress
|
||||
/// ELF object specified by `obj`.
|
||||
///
|
||||
@@ -53,7 +75,12 @@ pub trait ComponentCompiler: Send + Sync {
|
||||
fn emit_obj(
|
||||
&self,
|
||||
lowerings: PrimaryMap<LoweredIndex, Box<dyn Any + Send>>,
|
||||
always_trap: PrimaryMap<RuntimeAlwaysTrapIndex, Box<dyn Any + Send>>,
|
||||
tramplines: Vec<(SignatureIndex, Box<dyn Any + Send>)>,
|
||||
obj: &mut Object<'static>,
|
||||
) -> Result<(PrimaryMap<LoweredIndex, LoweringInfo>, Vec<Trampoline>)>;
|
||||
) -> Result<(
|
||||
PrimaryMap<LoweredIndex, LoweringInfo>,
|
||||
PrimaryMap<RuntimeAlwaysTrapIndex, AlwaysTrapInfo>,
|
||||
Vec<Trampoline>,
|
||||
)>;
|
||||
}
|
||||
|
||||
@@ -143,6 +143,10 @@ pub struct Component {
|
||||
/// The number of modules that are required to be saved within an instance
|
||||
/// at runtime, or effectively the number of exported modules.
|
||||
pub num_runtime_modules: u32,
|
||||
|
||||
/// The number of functions which "always trap" used to implement
|
||||
/// `canon.lower` of `canon.lift`'d functions within the same component.
|
||||
pub num_always_trap: u32,
|
||||
}
|
||||
|
||||
/// GlobalInitializer instructions to get processed when instantiating a component
|
||||
@@ -173,6 +177,12 @@ pub enum GlobalInitializer {
|
||||
/// pointer the trampoline calls, and the canonical ABI options.
|
||||
LowerImport(LowerImport),
|
||||
|
||||
/// A core wasm function was "generated" via `canon lower` of a function
|
||||
/// that was `canon lift`'d in the same component, meaning that the function
|
||||
/// always traps. This is recorded within the `VMComponentContext` as a new
|
||||
/// `VMCallerCheckedAnyfunc` that's available for use.
|
||||
AlwaysTrap(AlwaysTrap),
|
||||
|
||||
/// A core wasm linear memory is going to be saved into the
|
||||
/// `VMComponentContext`.
|
||||
///
|
||||
@@ -272,6 +282,17 @@ pub struct LowerImport {
|
||||
pub options: CanonicalOptions,
|
||||
}
|
||||
|
||||
/// Description of what to initialize when a `GlobalInitializer::AlwaysTrap` is
|
||||
/// encountered.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct AlwaysTrap {
|
||||
/// The index of the function that is being initialized in the
|
||||
/// `VMComponentContext`.
|
||||
pub index: RuntimeAlwaysTrapIndex,
|
||||
/// The core wasm signature of the function that's inserted.
|
||||
pub canonical_abi: SignatureIndex,
|
||||
}
|
||||
|
||||
/// Definition of a core wasm item and where it can come from within a
|
||||
/// component.
|
||||
///
|
||||
@@ -288,6 +309,10 @@ pub enum CoreDef {
|
||||
/// that this `LoweredIndex` corresponds to the nth
|
||||
/// `GlobalInitializer::LowerImport` instruction.
|
||||
Lowered(LoweredIndex),
|
||||
/// This is used to represent a degenerate case of where a `canon lift`'d
|
||||
/// function is immediately `canon lower`'d in the same instance. Such a
|
||||
/// function always traps at runtime.
|
||||
AlwaysTrap(RuntimeAlwaysTrapIndex),
|
||||
}
|
||||
|
||||
impl<T> From<CoreExport<T>> for CoreDef
|
||||
|
||||
@@ -46,7 +46,7 @@
|
||||
//! final `Component`.
|
||||
|
||||
use crate::component::translate::*;
|
||||
use crate::{ModuleTranslation, PrimaryMap};
|
||||
use crate::{ModuleTranslation, PrimaryMap, SignatureIndex};
|
||||
use indexmap::IndexMap;
|
||||
|
||||
pub(super) fn run(
|
||||
@@ -64,6 +64,7 @@ pub(super) fn run(
|
||||
runtime_realloc_interner: Default::default(),
|
||||
runtime_post_return_interner: Default::default(),
|
||||
runtime_memory_interner: Default::default(),
|
||||
runtime_always_trap_interner: Default::default(),
|
||||
};
|
||||
|
||||
// The initial arguments to the root component are all host imports. This
|
||||
@@ -194,6 +195,7 @@ struct Inliner<'a> {
|
||||
runtime_realloc_interner: HashMap<CoreDef, RuntimeReallocIndex>,
|
||||
runtime_post_return_interner: HashMap<CoreDef, RuntimePostReturnIndex>,
|
||||
runtime_memory_interner: HashMap<CoreExport<MemoryIndex>, RuntimeMemoryIndex>,
|
||||
runtime_always_trap_interner: HashMap<SignatureIndex, RuntimeAlwaysTrapIndex>,
|
||||
}
|
||||
|
||||
/// A "stack frame" as part of the inlining process, or the progress through
|
||||
@@ -443,12 +445,6 @@ impl<'a> Inliner<'a> {
|
||||
//
|
||||
// NB: at this time only lowered imported functions are supported.
|
||||
Lower(func, options) => {
|
||||
// Assign this lowering a unique index and determine the core
|
||||
// wasm function index we're defining.
|
||||
let index = LoweredIndex::from_u32(self.result.num_lowerings);
|
||||
self.result.num_lowerings += 1;
|
||||
let func_index = frame.funcs.push(CoreDef::Lowered(index));
|
||||
|
||||
// Use the type information from `wasmparser` to lookup the core
|
||||
// wasm function signature of the lowered function. This avoids
|
||||
// us having to reimplement the
|
||||
@@ -461,20 +457,22 @@ impl<'a> Inliner<'a> {
|
||||
.types
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.function_at(func_index.as_u32())
|
||||
.function_at(frame.funcs.next_key().as_u32())
|
||||
.expect("should be in-bounds");
|
||||
let canonical_abi = self
|
||||
.types
|
||||
.module_types_builder()
|
||||
.wasm_func_type(lowered_function_type.clone().try_into()?);
|
||||
|
||||
let options = self.canonical_options(frame, options);
|
||||
match &frame.component_funcs[*func] {
|
||||
let options_lower = self.canonical_options(frame, options);
|
||||
let func = match &frame.component_funcs[*func] {
|
||||
// If this component function was originally a host import
|
||||
// then this is a lowered host function which needs a
|
||||
// trampoline to enter WebAssembly. That's recorded here
|
||||
// with all relevant information.
|
||||
ComponentFuncDef::Import(path) => {
|
||||
let index = LoweredIndex::from_u32(self.result.num_lowerings);
|
||||
self.result.num_lowerings += 1;
|
||||
let import = self.runtime_import(path);
|
||||
self.result
|
||||
.initializers
|
||||
@@ -482,35 +480,91 @@ impl<'a> Inliner<'a> {
|
||||
canonical_abi,
|
||||
import,
|
||||
index,
|
||||
options,
|
||||
options: options_lower,
|
||||
}));
|
||||
CoreDef::Lowered(index)
|
||||
}
|
||||
|
||||
// TODO: Lowering a lift function could mean one of two
|
||||
// things:
|
||||
// This case handles when a lifted function is later
|
||||
// lowered, and both the lowering and the lifting are
|
||||
// happening within the same component instance.
|
||||
//
|
||||
// * This could mean that a "fused adapter" was just
|
||||
// identified. If the lifted function here comes from a
|
||||
// different component than we're lowering into then we
|
||||
// have identified the fusion location of two components
|
||||
// talking to each other. Metadata needs to be recorded
|
||||
// here about the fusion to get something generated by
|
||||
// Cranelift later on.
|
||||
// In this situation if the `canon.lower`'d function is
|
||||
// called then it immediately sets `may_enter` to `false`.
|
||||
// When calling the callee, however, that's `canon.lift`
|
||||
// which immediately traps if `may_enter` is `false`. That
|
||||
// means that this pairing of functions creates a function
|
||||
// that always traps.
|
||||
//
|
||||
// * Otherwise if the lifted function is in the same
|
||||
// component that we're lowering into then that means
|
||||
// something "funky" is happening. This needs to be
|
||||
// carefully implemented with respect to the
|
||||
// may_{enter,leave} flags as specified with the canonical
|
||||
// ABI. The careful consideration for how to do this has
|
||||
// not yet happened.
|
||||
// When closely reading the spec though the precise trap
|
||||
// that comes out can be somewhat variable. Technically the
|
||||
// function yielded here is one that should validate the
|
||||
// arguments by lifting them, and then trap. This means that
|
||||
// the trap could be different depending on whether all
|
||||
// arguments are valid for now. This was discussed in
|
||||
// WebAssembly/component-model#51 somewhat and the
|
||||
// conclusion was that we can probably get away with "always
|
||||
// trap" here.
|
||||
//
|
||||
// In general this is almost certainly going to require some
|
||||
// new variant of `GlobalInitializer` in one form or another.
|
||||
ComponentFuncDef::Lifted { .. } => {
|
||||
// The `CoreDef::AlwaysTrap` variant here is used to
|
||||
// indicate that this function is valid but if something
|
||||
// actually calls it then it just generates a trap
|
||||
// immediately.
|
||||
ComponentFuncDef::Lifted {
|
||||
options: options_lift,
|
||||
..
|
||||
} if options_lift.instance == options_lower.instance => {
|
||||
let index = *self
|
||||
.runtime_always_trap_interner
|
||||
.entry(canonical_abi)
|
||||
.or_insert_with(|| {
|
||||
let index =
|
||||
RuntimeAlwaysTrapIndex::from_u32(self.result.num_always_trap);
|
||||
self.result.num_always_trap += 1;
|
||||
self.result.initializers.push(GlobalInitializer::AlwaysTrap(
|
||||
AlwaysTrap {
|
||||
canonical_abi,
|
||||
index,
|
||||
},
|
||||
));
|
||||
index
|
||||
});
|
||||
CoreDef::AlwaysTrap(index)
|
||||
}
|
||||
|
||||
// Lowering a lifted function where the destination
|
||||
// component is different than the source component means
|
||||
// that a "fused adapter" was just identified.
|
||||
//
|
||||
// This is the location where, when this is actually
|
||||
// implemented, we'll record metadata about this fused
|
||||
// adapter to get compiled later during the compilation
|
||||
// process. The fused adapter here will be generated by
|
||||
// cranelift and will perfom argument validation when
|
||||
// called, copy the arguments from `options_lower` to
|
||||
// `options_lift` and then call the `func` specified for the
|
||||
// lifted options.
|
||||
//
|
||||
// When the `func` returns the canonical adapter will verify
|
||||
// the return values, copy them from `options_lift` to
|
||||
// `options_lower`, and then return.
|
||||
ComponentFuncDef::Lifted {
|
||||
ty,
|
||||
func,
|
||||
options: options_lift,
|
||||
} => {
|
||||
// These are the various compilation options for lifting
|
||||
// and lowering.
|
||||
drop(ty); // component-model function type
|
||||
drop(func); // original core wasm function that was lifted
|
||||
drop(options_lift); // options during `canon lift`
|
||||
drop(options_lower); // options during `canon lower`
|
||||
drop(canonical_abi); // type signature of created core wasm function
|
||||
|
||||
unimplemented!("lowering a lifted function")
|
||||
}
|
||||
}
|
||||
};
|
||||
frame.funcs.push(func);
|
||||
}
|
||||
|
||||
// Lifting a core wasm function is relatively easy for now in that
|
||||
@@ -656,7 +710,7 @@ impl<'a> Inliner<'a> {
|
||||
frame.tables.push(
|
||||
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
||||
CoreDef::Export(e) => e,
|
||||
CoreDef::Lowered(_) => unreachable!(),
|
||||
CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -665,7 +719,7 @@ impl<'a> Inliner<'a> {
|
||||
frame.globals.push(
|
||||
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
||||
CoreDef::Export(e) => e,
|
||||
CoreDef::Lowered(_) => unreachable!(),
|
||||
CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -674,7 +728,7 @@ impl<'a> Inliner<'a> {
|
||||
frame.memories.push(
|
||||
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
||||
CoreDef::Export(e) => e,
|
||||
CoreDef::Lowered(_) => unreachable!(),
|
||||
CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,6 +145,9 @@ indices! {
|
||||
/// component model.
|
||||
pub struct LoweredIndex(u32);
|
||||
|
||||
/// Same as `LoweredIndex` but for the `CoreDef::AlwaysTrap` variant.
|
||||
pub struct RuntimeAlwaysTrapIndex(u32);
|
||||
|
||||
/// Index representing a linear memory extracted from a wasm instance
|
||||
/// which is stored in a `VMComponentContext`. This is used to deduplicate
|
||||
/// references to the same linear memory where it's only stored once in a
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
// store: *mut dyn Store,
|
||||
// flags: [VMComponentFlags; component.num_runtime_component_instances],
|
||||
// lowering_anyfuncs: [VMCallerCheckedAnyfunc; component.num_lowerings],
|
||||
// always_trap_anyfuncs: [VMCallerCheckedAnyfunc; component.num_always_trap],
|
||||
// lowerings: [VMLowering; component.num_lowerings],
|
||||
// memories: [*mut VMMemoryDefinition; component.num_memories],
|
||||
// reallocs: [*mut VMCallerCheckedAnyfunc; component.num_reallocs],
|
||||
@@ -12,8 +13,8 @@
|
||||
// }
|
||||
|
||||
use crate::component::{
|
||||
Component, LoweredIndex, RuntimeComponentInstanceIndex, RuntimeMemoryIndex,
|
||||
RuntimePostReturnIndex, RuntimeReallocIndex,
|
||||
Component, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex,
|
||||
RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex,
|
||||
};
|
||||
use crate::PtrSize;
|
||||
|
||||
@@ -52,12 +53,16 @@ pub struct VMComponentOffsets<P> {
|
||||
/// Number of component instances internally in the component (always at
|
||||
/// least 1).
|
||||
pub num_runtime_component_instances: u32,
|
||||
/// Number of "always trap" functions which have their
|
||||
/// `VMCallerCheckedAnyfunc` stored inline in the `VMComponentContext`.
|
||||
pub num_always_trap: u32,
|
||||
|
||||
// precalculated offsets of various member fields
|
||||
magic: u32,
|
||||
store: u32,
|
||||
flags: u32,
|
||||
lowering_anyfuncs: u32,
|
||||
always_trap_anyfuncs: u32,
|
||||
lowerings: u32,
|
||||
memories: u32,
|
||||
reallocs: u32,
|
||||
@@ -85,10 +90,12 @@ impl<P: PtrSize> VMComponentOffsets<P> {
|
||||
.num_runtime_component_instances
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
num_always_trap: component.num_always_trap,
|
||||
magic: 0,
|
||||
store: 0,
|
||||
flags: 0,
|
||||
lowering_anyfuncs: 0,
|
||||
always_trap_anyfuncs: 0,
|
||||
lowerings: 0,
|
||||
memories: 0,
|
||||
reallocs: 0,
|
||||
@@ -127,6 +134,7 @@ impl<P: PtrSize> VMComponentOffsets<P> {
|
||||
size(flags) = cmul(ret.num_runtime_component_instances, ret.size_of_vmcomponent_flags()),
|
||||
align(u32::from(ret.ptr.size())),
|
||||
size(lowering_anyfuncs) = cmul(ret.num_lowerings, ret.ptr.size_of_vmcaller_checked_anyfunc()),
|
||||
size(always_trap_anyfuncs) = cmul(ret.num_always_trap, ret.ptr.size_of_vmcaller_checked_anyfunc()),
|
||||
size(lowerings) = cmul(ret.num_lowerings, ret.ptr.size() * 2),
|
||||
size(memories) = cmul(ret.num_runtime_memories, ret.ptr.size()),
|
||||
size(reallocs) = cmul(ret.num_runtime_reallocs, ret.ptr.size()),
|
||||
@@ -188,6 +196,20 @@ impl<P: PtrSize> VMComponentOffsets<P> {
|
||||
+ index.as_u32() * u32::from(self.ptr.size_of_vmcaller_checked_anyfunc())
|
||||
}
|
||||
|
||||
/// The offset of the `always_trap_anyfuncs` field.
|
||||
#[inline]
|
||||
pub fn always_trap_anyfuncs(&self) -> u32 {
|
||||
self.always_trap_anyfuncs
|
||||
}
|
||||
|
||||
/// The offset of `VMCallerCheckedAnyfunc` for the `index` specified.
|
||||
#[inline]
|
||||
pub fn always_trap_anyfunc(&self, index: RuntimeAlwaysTrapIndex) -> u32 {
|
||||
assert!(index.as_u32() < self.num_always_trap);
|
||||
self.always_trap_anyfuncs()
|
||||
+ index.as_u32() * u32::from(self.ptr.size_of_vmcaller_checked_anyfunc())
|
||||
}
|
||||
|
||||
/// The offset of the `lowerings` field.
|
||||
#[inline]
|
||||
pub fn lowerings(&self) -> u32 {
|
||||
|
||||
@@ -98,6 +98,10 @@ pub enum TrapCode {
|
||||
/// Execution has potentially run too long and may be interrupted.
|
||||
/// This trap is resumable.
|
||||
Interrupt,
|
||||
|
||||
/// Used for the component model when functions are lifted/lowered in a way
|
||||
/// that generates a function that always traps.
|
||||
AlwaysTrapAdapter,
|
||||
// if adding a variant here be sure to update the `check!` macro below
|
||||
}
|
||||
|
||||
@@ -205,6 +209,7 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option<TrapCode> {
|
||||
BadConversionToInteger
|
||||
UnreachableCodeReached
|
||||
Interrupt
|
||||
AlwaysTrapAdapter
|
||||
}
|
||||
|
||||
if cfg!(debug_assertions) {
|
||||
|
||||
@@ -17,10 +17,10 @@ use std::mem;
|
||||
use std::ops::Deref;
|
||||
use std::ptr::{self, NonNull};
|
||||
use wasmtime_environ::component::{
|
||||
Component, LoweredIndex, RuntimeComponentInstanceIndex, RuntimeMemoryIndex,
|
||||
RuntimePostReturnIndex, RuntimeReallocIndex, StringEncoding, VMComponentOffsets,
|
||||
VMCOMPONENT_FLAG_MAY_ENTER, VMCOMPONENT_FLAG_MAY_LEAVE, VMCOMPONENT_FLAG_NEEDS_POST_RETURN,
|
||||
VMCOMPONENT_MAGIC,
|
||||
Component, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex,
|
||||
RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex, StringEncoding,
|
||||
VMComponentOffsets, VMCOMPONENT_FLAG_MAY_ENTER, VMCOMPONENT_FLAG_MAY_LEAVE,
|
||||
VMCOMPONENT_FLAG_NEEDS_POST_RETURN, VMCOMPONENT_MAGIC,
|
||||
};
|
||||
use wasmtime_environ::HostPtr;
|
||||
|
||||
@@ -259,6 +259,20 @@ impl ComponentInstance {
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `lowering_anyfunc` except for the functions that always trap.
|
||||
pub fn always_trap_anyfunc(
|
||||
&self,
|
||||
idx: RuntimeAlwaysTrapIndex,
|
||||
) -> NonNull<VMCallerCheckedAnyfunc> {
|
||||
unsafe {
|
||||
let ret = self
|
||||
.vmctx_plus_offset::<VMCallerCheckedAnyfunc>(self.offsets.always_trap_anyfunc(idx));
|
||||
debug_assert!((*ret).func_ptr.as_ptr() as usize != INVALID_PTR);
|
||||
debug_assert!((*ret).vmctx as usize != INVALID_PTR);
|
||||
NonNull::new(ret).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores the runtime memory pointer at the index specified.
|
||||
///
|
||||
/// This is intended to be called during the instantiation process of a
|
||||
@@ -340,6 +354,28 @@ impl ComponentInstance {
|
||||
}
|
||||
}
|
||||
|
||||
/// Same as `set_lowering` but for the "always trap" functions.
|
||||
pub fn set_always_trap(
|
||||
&mut self,
|
||||
idx: RuntimeAlwaysTrapIndex,
|
||||
func_ptr: NonNull<VMFunctionBody>,
|
||||
type_index: VMSharedSignatureIndex,
|
||||
) {
|
||||
unsafe {
|
||||
debug_assert!(
|
||||
*self.vmctx_plus_offset::<usize>(self.offsets.always_trap_anyfunc(idx))
|
||||
== INVALID_PTR
|
||||
);
|
||||
let vmctx = self.vmctx();
|
||||
*self.vmctx_plus_offset(self.offsets.always_trap_anyfunc(idx)) =
|
||||
VMCallerCheckedAnyfunc {
|
||||
func_ptr,
|
||||
type_index,
|
||||
vmctx: VMOpaqueContext::from_vmcomponent(vmctx),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn initialize_vmctx(&mut self, store: *mut dyn Store) {
|
||||
*self.vmctx_plus_offset(self.offsets.magic()) = VMCOMPONENT_MAGIC;
|
||||
*self.vmctx_plus_offset(self.offsets.store()) = store;
|
||||
@@ -362,6 +398,11 @@ impl ComponentInstance {
|
||||
let offset = self.offsets.lowering_anyfunc(i);
|
||||
*self.vmctx_plus_offset(offset) = INVALID_PTR;
|
||||
}
|
||||
for i in 0..self.offsets.num_always_trap {
|
||||
let i = RuntimeAlwaysTrapIndex::from_u32(i);
|
||||
let offset = self.offsets.always_trap_anyfunc(i);
|
||||
*self.vmctx_plus_offset(offset) = INVALID_PTR;
|
||||
}
|
||||
for i in 0..self.offsets.num_runtime_memories {
|
||||
let i = RuntimeMemoryIndex::from_u32(i);
|
||||
let offset = self.offsets.runtime_memory(i);
|
||||
@@ -476,6 +517,19 @@ impl OwnedComponentInstance {
|
||||
.set_lowering(idx, lowering, anyfunc_func_ptr, anyfunc_type_index)
|
||||
}
|
||||
}
|
||||
|
||||
/// See `ComponentInstance::set_always_trap`
|
||||
pub fn set_always_trap(
|
||||
&mut self,
|
||||
idx: RuntimeAlwaysTrapIndex,
|
||||
func_ptr: NonNull<VMFunctionBody>,
|
||||
type_index: VMSharedSignatureIndex,
|
||||
) {
|
||||
unsafe {
|
||||
self.instance_mut()
|
||||
.set_always_trap(idx, func_ptr, type_index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for OwnedComponentInstance {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use crate::signatures::SignatureCollection;
|
||||
use crate::{Engine, Module};
|
||||
use anyhow::{bail, Context, Result};
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::fs;
|
||||
@@ -9,9 +10,10 @@ use std::path::Path;
|
||||
use std::ptr::NonNull;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::component::{
|
||||
ComponentTypes, GlobalInitializer, LoweredIndex, LoweringInfo, StaticModuleIndex, Translator,
|
||||
AlwaysTrapInfo, ComponentTypes, GlobalInitializer, LoweredIndex, LoweringInfo,
|
||||
RuntimeAlwaysTrapIndex, StaticModuleIndex, Translator,
|
||||
};
|
||||
use wasmtime_environ::PrimaryMap;
|
||||
use wasmtime_environ::{PrimaryMap, SignatureIndex, Trampoline, TrapCode};
|
||||
use wasmtime_jit::CodeMemory;
|
||||
use wasmtime_runtime::VMFunctionBody;
|
||||
|
||||
@@ -41,19 +43,27 @@ struct ComponentInner {
|
||||
/// this field.
|
||||
types: Arc<ComponentTypes>,
|
||||
|
||||
/// The in-memory ELF image of the compiled trampolines for this component.
|
||||
///
|
||||
/// This is currently only used for wasm-to-host trampolines when
|
||||
/// `canon.lower` is encountered.
|
||||
/// The in-memory ELF image of the compiled functions for this component.
|
||||
trampoline_obj: CodeMemory,
|
||||
|
||||
/// The index ranges within `trampoline_obj`'s mmap memory for the entire
|
||||
/// text section.
|
||||
text: Range<usize>,
|
||||
|
||||
/// Where trampolines are located within the `text` section of
|
||||
/// `trampoline_obj`.
|
||||
trampolines: PrimaryMap<LoweredIndex, LoweringInfo>,
|
||||
/// Where lowered function trampolines are located within the `text`
|
||||
/// section of `trampoline_obj`.
|
||||
///
|
||||
/// These trampolines are the function pointer within the
|
||||
/// `VMCallerCheckedAnyfunc` and will delegate indirectly to a host function
|
||||
/// pointer when called.
|
||||
lowerings: PrimaryMap<LoweredIndex, LoweringInfo>,
|
||||
|
||||
/// Where the "always trap" functions are located within the `text` section
|
||||
/// of `trampoline_obj`.
|
||||
///
|
||||
/// These functions are "degenerate functions" here solely to implement
|
||||
/// functions that are `canon lift`'d then immediately `canon lower`'d.
|
||||
always_trap: PrimaryMap<RuntimeAlwaysTrapIndex, AlwaysTrapInfo>,
|
||||
}
|
||||
|
||||
impl Component {
|
||||
@@ -117,39 +127,10 @@ impl Component {
|
||||
.context("failed to parse WebAssembly module")?;
|
||||
let types = Arc::new(types.finish());
|
||||
|
||||
// All lowered functions will require a trampoline to be available in
|
||||
// case they're used when entering wasm. For example a lowered function
|
||||
// could be immediately lifted in which case we'll need a trampoline to
|
||||
// call that lowered function.
|
||||
//
|
||||
// Most of the time trampolines can come from the core wasm modules
|
||||
// since lifted functions come from core wasm. For these esoteric cases
|
||||
// though we may have to compile trampolines specifically into the
|
||||
// component object as well in case core wasm doesn't provide the
|
||||
// necessary trampoline.
|
||||
let lowerings = component
|
||||
.initializers
|
||||
.iter()
|
||||
.filter_map(|init| match init {
|
||||
GlobalInitializer::LowerImport(i) => Some(i),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let required_trampolines = lowerings
|
||||
.iter()
|
||||
.map(|l| l.canonical_abi)
|
||||
.collect::<HashSet<_>>();
|
||||
let provided_trampolines = modules
|
||||
.iter()
|
||||
.flat_map(|(_, m)| m.exported_signatures.iter().copied())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut trampolines_to_compile = required_trampolines
|
||||
.difference(&provided_trampolines)
|
||||
.collect::<Vec<_>>();
|
||||
// Ensure a deterministically compiled artifact by sorting this list
|
||||
// which was otherwise created with nondeterministically ordered hash
|
||||
// tables.
|
||||
trampolines_to_compile.sort();
|
||||
|
||||
let (static_modules, trampolines) = engine.join_maybe_parallel(
|
||||
// In one (possibly) parallel task all the modules found within this
|
||||
@@ -173,41 +154,10 @@ impl Component {
|
||||
},
|
||||
// In another (possibly) parallel task we compile lowering
|
||||
// trampolines necessary found in the component.
|
||||
|| -> Result<_> {
|
||||
let compiler = engine.compiler();
|
||||
let (lowered_trampolines, core_trampolines) = engine.join_maybe_parallel(
|
||||
// Compile all the lowered trampolines here which implement
|
||||
// `canon lower` and are used to exit wasm into the host.
|
||||
|| -> Result<_> {
|
||||
Ok(engine
|
||||
.run_maybe_parallel(lowerings, |lowering| {
|
||||
compiler
|
||||
.component_compiler()
|
||||
.compile_lowered_trampoline(&component, lowering, &types)
|
||||
})?
|
||||
.into_iter()
|
||||
.collect())
|
||||
},
|
||||
// Compile all entry host-to-wasm trampolines here that
|
||||
// aren't otherwise provided by core wasm modules.
|
||||
|| -> Result<_> {
|
||||
engine.run_maybe_parallel(trampolines_to_compile.clone(), |i| {
|
||||
let ty = &types[*i];
|
||||
Ok((*i, compiler.compile_host_to_wasm_trampoline(ty)?))
|
||||
})
|
||||
},
|
||||
);
|
||||
let mut obj = engine.compiler().object()?;
|
||||
let trampolines = compiler.component_compiler().emit_obj(
|
||||
lowered_trampolines?,
|
||||
core_trampolines?,
|
||||
&mut obj,
|
||||
)?;
|
||||
Ok((trampolines, wasmtime_jit::mmap_vec_from_obj(obj)?))
|
||||
},
|
||||
|| Component::compile_component(engine, &component, &types, &provided_trampolines),
|
||||
);
|
||||
let static_modules = static_modules?;
|
||||
let ((lowering_trampolines, core_trampolines), trampoline_obj) = trampolines?;
|
||||
let (lowerings, always_trap, trampolines, trampoline_obj) = trampolines?;
|
||||
let mut trampoline_obj = CodeMemory::new(trampoline_obj);
|
||||
let code = trampoline_obj.publish()?;
|
||||
let text = wasmtime_jit::subslice_range(code.text, code.mmap);
|
||||
@@ -231,8 +181,8 @@ impl Component {
|
||||
vmtrampolines.insert(idx, trampoline);
|
||||
}
|
||||
}
|
||||
for (signature, trampoline) in trampolines_to_compile.iter().zip(core_trampolines) {
|
||||
vmtrampolines.insert(**signature, unsafe {
|
||||
for trampoline in trampolines {
|
||||
vmtrampolines.insert(trampoline.signature, unsafe {
|
||||
let ptr =
|
||||
code.text[trampoline.start as usize..][..trampoline.length as usize].as_ptr();
|
||||
std::mem::transmute::<*const u8, wasmtime_runtime::VMTrampoline>(ptr)
|
||||
@@ -248,6 +198,15 @@ impl Component {
|
||||
vmtrampolines.into_iter(),
|
||||
);
|
||||
|
||||
// Assert that this `always_trap` list is sorted which is relied on in
|
||||
// `register_component` as well as `Component::lookup_trap_code` below.
|
||||
assert!(always_trap
|
||||
.values()
|
||||
.as_slice()
|
||||
.windows(2)
|
||||
.all(|window| { window[0].start < window[1].start }));
|
||||
|
||||
crate::module::register_component(code.text, &always_trap);
|
||||
Ok(Component {
|
||||
inner: Arc::new(ComponentInner {
|
||||
component,
|
||||
@@ -256,11 +215,136 @@ impl Component {
|
||||
signatures,
|
||||
trampoline_obj,
|
||||
text,
|
||||
trampolines: lowering_trampolines,
|
||||
lowerings,
|
||||
always_trap,
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(compiler)]
|
||||
fn compile_component(
|
||||
engine: &Engine,
|
||||
component: &wasmtime_environ::component::Component,
|
||||
types: &ComponentTypes,
|
||||
provided_trampolines: &HashSet<SignatureIndex>,
|
||||
) -> Result<(
|
||||
PrimaryMap<LoweredIndex, LoweringInfo>,
|
||||
PrimaryMap<RuntimeAlwaysTrapIndex, AlwaysTrapInfo>,
|
||||
Vec<Trampoline>,
|
||||
wasmtime_runtime::MmapVec,
|
||||
)> {
|
||||
let results = engine.join_maybe_parallel(
|
||||
|| compile_lowerings(engine, component, types),
|
||||
|| -> Result<_> {
|
||||
Ok(engine.join_maybe_parallel(
|
||||
|| compile_always_trap(engine, component, types),
|
||||
|| compile_trampolines(engine, component, types, provided_trampolines),
|
||||
))
|
||||
},
|
||||
);
|
||||
let (lowerings, other) = results;
|
||||
let (always_trap, trampolines) = other?;
|
||||
let mut obj = engine.compiler().object()?;
|
||||
let (lower, traps, trampolines) = engine.compiler().component_compiler().emit_obj(
|
||||
lowerings?,
|
||||
always_trap?,
|
||||
trampolines?,
|
||||
&mut obj,
|
||||
)?;
|
||||
return Ok((
|
||||
lower,
|
||||
traps,
|
||||
trampolines,
|
||||
wasmtime_jit::mmap_vec_from_obj(obj)?,
|
||||
));
|
||||
|
||||
fn compile_lowerings(
|
||||
engine: &Engine,
|
||||
component: &wasmtime_environ::component::Component,
|
||||
types: &ComponentTypes,
|
||||
) -> Result<PrimaryMap<LoweredIndex, Box<dyn Any + Send>>> {
|
||||
let lowerings = component
|
||||
.initializers
|
||||
.iter()
|
||||
.filter_map(|init| match init {
|
||||
GlobalInitializer::LowerImport(i) => Some(i),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(engine
|
||||
.run_maybe_parallel(lowerings, |lowering| {
|
||||
engine
|
||||
.compiler()
|
||||
.component_compiler()
|
||||
.compile_lowered_trampoline(&component, lowering, &types)
|
||||
})?
|
||||
.into_iter()
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn compile_always_trap(
|
||||
engine: &Engine,
|
||||
component: &wasmtime_environ::component::Component,
|
||||
types: &ComponentTypes,
|
||||
) -> Result<PrimaryMap<RuntimeAlwaysTrapIndex, Box<dyn Any + Send>>> {
|
||||
let always_trap = component
|
||||
.initializers
|
||||
.iter()
|
||||
.filter_map(|init| match init {
|
||||
GlobalInitializer::AlwaysTrap(i) => Some(i),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(engine
|
||||
.run_maybe_parallel(always_trap, |info| {
|
||||
engine
|
||||
.compiler()
|
||||
.component_compiler()
|
||||
.compile_always_trap(&types[info.canonical_abi])
|
||||
})?
|
||||
.into_iter()
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn compile_trampolines(
|
||||
engine: &Engine,
|
||||
component: &wasmtime_environ::component::Component,
|
||||
types: &ComponentTypes,
|
||||
provided_trampolines: &HashSet<SignatureIndex>,
|
||||
) -> Result<Vec<(SignatureIndex, Box<dyn Any + Send>)>> {
|
||||
// All lowered functions will require a trampoline to be available in
|
||||
// case they're used when entering wasm. For example a lowered function
|
||||
// could be immediately lifted in which case we'll need a trampoline to
|
||||
// call that lowered function.
|
||||
//
|
||||
// Most of the time trampolines can come from the core wasm modules
|
||||
// since lifted functions come from core wasm. For these esoteric cases
|
||||
// though we may have to compile trampolines specifically into the
|
||||
// component object as well in case core wasm doesn't provide the
|
||||
// necessary trampoline.
|
||||
let required_trampolines = component
|
||||
.initializers
|
||||
.iter()
|
||||
.filter_map(|init| match init {
|
||||
GlobalInitializer::LowerImport(i) => Some(i.canonical_abi),
|
||||
GlobalInitializer::AlwaysTrap(i) => Some(i.canonical_abi),
|
||||
_ => None,
|
||||
})
|
||||
.collect::<HashSet<_>>();
|
||||
let mut trampolines_to_compile = required_trampolines
|
||||
.difference(&provided_trampolines)
|
||||
.collect::<Vec<_>>();
|
||||
// Ensure a deterministically compiled artifact by sorting this list
|
||||
// which was otherwise created with nondeterministically ordered hash
|
||||
// tables.
|
||||
trampolines_to_compile.sort();
|
||||
engine.run_maybe_parallel(trampolines_to_compile.clone(), |i| {
|
||||
let ty = &types[*i];
|
||||
Ok((*i, engine.compiler().compile_host_to_wasm_trampoline(ty)?))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn env_component(&self) -> &wasmtime_environ::component::Component {
|
||||
&self.inner.component
|
||||
}
|
||||
@@ -278,13 +362,52 @@ impl Component {
|
||||
}
|
||||
|
||||
pub(crate) fn text(&self) -> &[u8] {
|
||||
&self.inner.trampoline_obj.mmap()[self.inner.text.clone()]
|
||||
self.inner.text()
|
||||
}
|
||||
|
||||
pub(crate) fn trampoline_ptr(&self, index: LoweredIndex) -> NonNull<VMFunctionBody> {
|
||||
let info = &self.inner.trampolines[index];
|
||||
pub(crate) fn lowering_ptr(&self, index: LoweredIndex) -> NonNull<VMFunctionBody> {
|
||||
let info = &self.inner.lowerings[index];
|
||||
self.func(info.start, info.length)
|
||||
}
|
||||
|
||||
pub(crate) fn always_trap_ptr(&self, index: RuntimeAlwaysTrapIndex) -> NonNull<VMFunctionBody> {
|
||||
let info = &self.inner.always_trap[index];
|
||||
self.func(info.start, info.length)
|
||||
}
|
||||
|
||||
fn func(&self, start: u32, len: u32) -> NonNull<VMFunctionBody> {
|
||||
let text = self.text();
|
||||
let trampoline = &text[info.start as usize..][..info.length as usize];
|
||||
let trampoline = &text[start as usize..][..len as usize];
|
||||
NonNull::new(trampoline.as_ptr() as *mut VMFunctionBody).unwrap()
|
||||
}
|
||||
|
||||
/// Looks up a trap code for the instruction at `offset` where the offset
|
||||
/// specified is relative to the start of this component's text section.
|
||||
pub(crate) fn lookup_trap_code(&self, offset: usize) -> Option<TrapCode> {
|
||||
let offset = u32::try_from(offset).ok()?;
|
||||
// Currently traps only come from "always trap" adapters so that map is
|
||||
// the only map that's searched.
|
||||
match self
|
||||
.inner
|
||||
.always_trap
|
||||
.values()
|
||||
.as_slice()
|
||||
.binary_search_by_key(&offset, |info| info.start + info.trap_offset)
|
||||
{
|
||||
Ok(_) => Some(TrapCode::AlwaysTrapAdapter),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentInner {
|
||||
fn text(&self) -> &[u8] {
|
||||
&self.trampoline_obj.mmap()[self.text.clone()]
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ComponentInner {
|
||||
fn drop(&mut self) {
|
||||
crate::module::unregister_component(self.text());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,9 +7,9 @@ use anyhow::{anyhow, Context, Result};
|
||||
use std::marker;
|
||||
use std::sync::Arc;
|
||||
use wasmtime_environ::component::{
|
||||
ComponentTypes, CoreDef, CoreExport, Export, ExportItem, ExtractMemory, ExtractPostReturn,
|
||||
ExtractRealloc, GlobalInitializer, InstantiateModule, LowerImport, RuntimeImportIndex,
|
||||
RuntimeInstanceIndex, RuntimeModuleIndex,
|
||||
AlwaysTrap, ComponentTypes, CoreDef, CoreExport, Export, ExportItem, ExtractMemory,
|
||||
ExtractPostReturn, ExtractRealloc, GlobalInitializer, InstantiateModule, LowerImport,
|
||||
RuntimeImportIndex, RuntimeInstanceIndex, RuntimeModuleIndex,
|
||||
};
|
||||
use wasmtime_environ::{EntityIndex, PrimaryMap};
|
||||
use wasmtime_runtime::component::{ComponentInstance, OwnedComponentInstance};
|
||||
@@ -145,12 +145,17 @@ impl InstanceData {
|
||||
|
||||
pub fn lookup_def(&self, store: &mut StoreOpaque, def: &CoreDef) -> wasmtime_runtime::Export {
|
||||
match def {
|
||||
CoreDef::Export(e) => self.lookup_export(store, e),
|
||||
CoreDef::Lowered(idx) => {
|
||||
wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction {
|
||||
anyfunc: self.state.lowering_anyfunc(*idx),
|
||||
})
|
||||
}
|
||||
CoreDef::Export(e) => self.lookup_export(store, e),
|
||||
CoreDef::AlwaysTrap(idx) => {
|
||||
wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction {
|
||||
anyfunc: self.state.always_trap_anyfunc(*idx),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -272,6 +277,8 @@ impl<'a> Instantiator<'a> {
|
||||
|
||||
GlobalInitializer::LowerImport(import) => self.lower_import(import),
|
||||
|
||||
GlobalInitializer::AlwaysTrap(trap) => self.always_trap(trap),
|
||||
|
||||
GlobalInitializer::ExtractMemory(mem) => self.extract_memory(store.0, mem),
|
||||
|
||||
GlobalInitializer::ExtractRealloc(realloc) => {
|
||||
@@ -307,7 +314,7 @@ impl<'a> Instantiator<'a> {
|
||||
self.data.state.set_lowering(
|
||||
import.index,
|
||||
func.lowering(),
|
||||
self.component.trampoline_ptr(import.index),
|
||||
self.component.lowering_ptr(import.index),
|
||||
self.component
|
||||
.signatures()
|
||||
.shared_signature(import.canonical_abi)
|
||||
@@ -324,6 +331,17 @@ impl<'a> Instantiator<'a> {
|
||||
self.data.funcs.push(func.clone());
|
||||
}
|
||||
|
||||
fn always_trap(&mut self, trap: &AlwaysTrap) {
|
||||
self.data.state.set_always_trap(
|
||||
trap.index,
|
||||
self.component.always_trap_ptr(trap.index),
|
||||
self.component
|
||||
.signatures()
|
||||
.shared_signature(trap.canonical_abi)
|
||||
.expect("found unregistered signature"),
|
||||
);
|
||||
}
|
||||
|
||||
fn extract_memory(&mut self, store: &mut StoreOpaque, memory: &ExtractMemory) {
|
||||
let mem = match self.data.lookup_export(store, &memory.export) {
|
||||
wasmtime_runtime::Export::Memory(m) => m,
|
||||
|
||||
@@ -26,6 +26,8 @@ mod registry;
|
||||
mod serialization;
|
||||
|
||||
pub use registry::{is_wasm_trap_pc, ModuleRegistry};
|
||||
#[cfg(feature = "component-model")]
|
||||
pub use registry::{register_component, unregister_component};
|
||||
pub use serialization::SerializedModule;
|
||||
|
||||
/// A compiled WebAssembly module, ready to be instantiated.
|
||||
@@ -537,7 +539,7 @@ impl Module {
|
||||
// into the global registry of modules so we can resolve traps
|
||||
// appropriately. Note that the corresponding `unregister` happens below
|
||||
// in `Drop for ModuleInner`.
|
||||
registry::register(&module);
|
||||
registry::register_module(&module);
|
||||
|
||||
Ok(Self {
|
||||
inner: Arc::new(ModuleInner {
|
||||
@@ -987,7 +989,7 @@ impl wasmtime_runtime::ModuleInfo for ModuleInner {
|
||||
|
||||
impl Drop for ModuleInner {
|
||||
fn drop(&mut self) {
|
||||
registry::unregister(&self.module);
|
||||
registry::unregister_module(&self.module);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,11 @@ use std::{
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use wasmtime_environ::TrapCode;
|
||||
#[cfg(feature = "component-model")]
|
||||
use wasmtime_environ::{
|
||||
component::{AlwaysTrapInfo, RuntimeAlwaysTrapIndex},
|
||||
PrimaryMap,
|
||||
};
|
||||
use wasmtime_jit::CompiledModule;
|
||||
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
|
||||
|
||||
@@ -15,7 +20,7 @@ use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
|
||||
///
|
||||
/// Note that the primary reason for this registry is to ensure that everything
|
||||
/// in `Module` is kept alive for the duration of a `Store`. At this time we
|
||||
/// need "basically everything" within a `Moudle` to stay alive once it's
|
||||
/// need "basically everything" within a `Module` to stay alive once it's
|
||||
/// instantiated within a store. While there's some smaller portions that could
|
||||
/// theoretically be omitted as they're not needed by the store they're
|
||||
/// currently small enough to not worry much about.
|
||||
@@ -147,8 +152,13 @@ impl ModuleRegistry {
|
||||
|
||||
/// Fetches trap information about a program counter in a backtrace.
|
||||
pub fn lookup_trap_code(&self, pc: usize) -> Option<TrapCode> {
|
||||
let (module, offset) = self.module(pc)?;
|
||||
wasmtime_environ::lookup_trap_code(module.compiled_module().trap_data(), offset)
|
||||
match self.module_or_component(pc)? {
|
||||
(ModuleOrComponent::Module(module), offset) => {
|
||||
wasmtime_environ::lookup_trap_code(module.compiled_module().trap_data(), offset)
|
||||
}
|
||||
#[cfg(feature = "component-model")]
|
||||
(ModuleOrComponent::Component(component), offset) => component.lookup_trap_code(offset),
|
||||
}
|
||||
}
|
||||
|
||||
/// Fetches frame information about a program counter in a backtrace.
|
||||
@@ -160,9 +170,21 @@ impl ModuleRegistry {
|
||||
/// boolean indicates whether the engine used to compile this module is
|
||||
/// using environment variables to control debuginfo parsing.
|
||||
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> {
|
||||
let (module, offset) = self.module(pc)?;
|
||||
let info = FrameInfo::new(module, offset)?;
|
||||
Some((info, module))
|
||||
match self.module_or_component(pc)? {
|
||||
(ModuleOrComponent::Module(module), offset) => {
|
||||
let info = FrameInfo::new(module, offset)?;
|
||||
Some((info, module))
|
||||
}
|
||||
#[cfg(feature = "component-model")]
|
||||
(ModuleOrComponent::Component(_), _) => {
|
||||
// FIXME: should investigate whether it's worth preserving
|
||||
// frame information on a `Component` to resolve a frame here.
|
||||
// Note that this can be traced back to either a lowered
|
||||
// function via a trampoline or an "always trap" function at
|
||||
// this time which may be useful debugging information to have.
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -183,12 +205,19 @@ lazy_static::lazy_static! {
|
||||
static ref GLOBAL_MODULES: RwLock<GlobalModuleRegistry> = Default::default();
|
||||
}
|
||||
|
||||
type GlobalModuleRegistry = BTreeMap<usize, (usize, Arc<CompiledModule>)>;
|
||||
type GlobalModuleRegistry = BTreeMap<usize, (usize, TrapInfo)>;
|
||||
|
||||
#[derive(Clone)]
|
||||
enum TrapInfo {
|
||||
Module(Arc<CompiledModule>),
|
||||
#[cfg(feature = "component-model")]
|
||||
Component(Arc<Vec<u32>>),
|
||||
}
|
||||
|
||||
/// Returns whether the `pc`, according to globally registered information,
|
||||
/// is a wasm trap or not.
|
||||
pub fn is_wasm_trap_pc(pc: usize) -> bool {
|
||||
let (module, text_offset) = {
|
||||
let (trap_info, text_offset) = {
|
||||
let all_modules = GLOBAL_MODULES.read().unwrap();
|
||||
|
||||
let (end, (start, module)) = match all_modules.range(pc..).next() {
|
||||
@@ -201,7 +230,16 @@ pub fn is_wasm_trap_pc(pc: usize) -> bool {
|
||||
(module.clone(), pc - *start)
|
||||
};
|
||||
|
||||
wasmtime_environ::lookup_trap_code(module.trap_data(), text_offset).is_some()
|
||||
match trap_info {
|
||||
TrapInfo::Module(module) => {
|
||||
wasmtime_environ::lookup_trap_code(module.trap_data(), text_offset).is_some()
|
||||
}
|
||||
#[cfg(feature = "component-model")]
|
||||
TrapInfo::Component(traps) => {
|
||||
let offset = u32::try_from(text_offset).unwrap();
|
||||
traps.binary_search(&offset).is_ok()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers a new region of code.
|
||||
@@ -212,7 +250,7 @@ pub fn is_wasm_trap_pc(pc: usize) -> bool {
|
||||
/// This is required to enable traps to work correctly since the signal handler
|
||||
/// will lookup in the `GLOBAL_MODULES` list to determine which a particular pc
|
||||
/// is a trap or not.
|
||||
pub fn register(module: &Arc<CompiledModule>) {
|
||||
pub fn register_module(module: &Arc<CompiledModule>) {
|
||||
let code = module.code();
|
||||
if code.is_empty() {
|
||||
return;
|
||||
@@ -222,14 +260,14 @@ pub fn register(module: &Arc<CompiledModule>) {
|
||||
let prev = GLOBAL_MODULES
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(end, (start, module.clone()));
|
||||
.insert(end, (start, TrapInfo::Module(module.clone())));
|
||||
assert!(prev.is_none());
|
||||
}
|
||||
|
||||
/// Unregisters a module from the global map.
|
||||
///
|
||||
/// Must hae been previously registered with `register`.
|
||||
pub fn unregister(module: &Arc<CompiledModule>) {
|
||||
/// Must have been previously registered with `register`.
|
||||
pub fn unregister_module(module: &Arc<CompiledModule>) {
|
||||
let code = module.code();
|
||||
if code.is_empty() {
|
||||
return;
|
||||
@@ -239,6 +277,39 @@ pub fn unregister(module: &Arc<CompiledModule>) {
|
||||
assert!(module.is_some());
|
||||
}
|
||||
|
||||
/// Same as `register_module`, but for components
|
||||
#[cfg(feature = "component-model")]
|
||||
pub fn register_component(text: &[u8], traps: &PrimaryMap<RuntimeAlwaysTrapIndex, AlwaysTrapInfo>) {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
let start = text.as_ptr() as usize;
|
||||
let end = start + text.len();
|
||||
let info = Arc::new(
|
||||
traps
|
||||
.iter()
|
||||
.map(|(_, info)| info.start + info.trap_offset)
|
||||
.collect::<Vec<_>>(),
|
||||
);
|
||||
let prev = GLOBAL_MODULES
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(end, (start, TrapInfo::Component(info)));
|
||||
assert!(prev.is_none());
|
||||
}
|
||||
|
||||
/// Same as `unregister_module`, but for components
|
||||
#[cfg(feature = "component-model")]
|
||||
pub fn unregister_component(text: &[u8]) {
|
||||
if text.is_empty() {
|
||||
return;
|
||||
}
|
||||
let start = text.as_ptr() as usize;
|
||||
let end = start + text.len();
|
||||
let info = GLOBAL_MODULES.write().unwrap().remove(&end);
|
||||
assert!(info.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_frame_info() -> Result<(), anyhow::Error> {
|
||||
use crate::*;
|
||||
|
||||
@@ -87,6 +87,13 @@ pub enum TrapCode {
|
||||
|
||||
/// Execution has potentially run too long and may be interrupted.
|
||||
Interrupt,
|
||||
|
||||
/// When the `component-model` feature is enabled this trap represents a
|
||||
/// function that was `canon lift`'d, then `canon lower`'d, then called.
|
||||
/// This combination of creation of a function in the component model
|
||||
/// generates a function that always traps and, when called, produces this
|
||||
/// flavor of trap.
|
||||
AlwaysTrapAdapter,
|
||||
}
|
||||
|
||||
impl TrapCode {
|
||||
@@ -104,6 +111,7 @@ impl TrapCode {
|
||||
EnvTrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
|
||||
EnvTrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
|
||||
EnvTrapCode::Interrupt => TrapCode::Interrupt,
|
||||
EnvTrapCode::AlwaysTrapAdapter => TrapCode::AlwaysTrapAdapter,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -123,6 +131,7 @@ impl fmt::Display for TrapCode {
|
||||
BadConversionToInteger => "invalid conversion to integer",
|
||||
UnreachableCodeReached => "wasm `unreachable` instruction executed",
|
||||
Interrupt => "interrupt",
|
||||
AlwaysTrapAdapter => "degenerate component adapter called",
|
||||
};
|
||||
write!(f, "{}", desc)
|
||||
}
|
||||
|
||||
@@ -83,3 +83,31 @@
|
||||
)
|
||||
(export "f" (func $f2))
|
||||
)
|
||||
|
||||
;; valid, but odd
|
||||
(component
|
||||
(core module $m (func (export "")))
|
||||
(core instance $m (instantiate $m))
|
||||
|
||||
(func $f1 (canon lift (core func $m "")))
|
||||
(core func $f2 (canon lower (func $f1)))
|
||||
)
|
||||
(assert_trap
|
||||
(component
|
||||
(core module $m (func (export "")))
|
||||
(core instance $m (instantiate $m))
|
||||
|
||||
(func $f1 (canon lift (core func $m "")))
|
||||
(core func $f2 (canon lower (func $f1)))
|
||||
|
||||
(core module $m2
|
||||
(import "" "" (func $f))
|
||||
(func $start
|
||||
call $f)
|
||||
(start $start)
|
||||
)
|
||||
(core instance (instantiate $m2
|
||||
(with "" (instance (export "" (func $f2))))
|
||||
))
|
||||
)
|
||||
"degenerate component adapter called")
|
||||
|
||||
Reference in New Issue
Block a user