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()
|
.iter()
|
||||||
.zip(&compiled_trampolines)
|
.zip(&compiled_trampolines)
|
||||||
{
|
{
|
||||||
|
assert!(func.traps.is_empty());
|
||||||
trampolines.push(builder.trampoline(*i, &func));
|
trampolines.push(builder.trampoline(*i, &func));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -683,19 +684,21 @@ impl Compiler {
|
|||||||
context
|
context
|
||||||
.compile_and_emit(isa, &mut code_buf)
|
.compile_and_emit(isa, &mut code_buf)
|
||||||
.map_err(|error| CompileError::Codegen(pretty_error(&context.func, error)))?;
|
.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
|
// Processing relocations isn't the hardest thing in the world here but
|
||||||
// no trampoline should currently generate a relocation, so assert that
|
// no trampoline should currently generate a relocation, so assert that
|
||||||
// they're all empty and if this ever trips in the future then handling
|
// 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
|
// will need to be added here to ensure they make their way into the
|
||||||
// `CompiledFunction` below.
|
// `CompiledFunction` below.
|
||||||
assert!(context
|
assert!(result.buffer.relocs().is_empty());
|
||||||
.mach_compile_result
|
|
||||||
.as_ref()
|
let traps = result
|
||||||
.unwrap()
|
|
||||||
.buffer
|
.buffer
|
||||||
.relocs()
|
.traps()
|
||||||
.is_empty());
|
.into_iter()
|
||||||
|
.map(mach_trap_to_trap)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let unwind_info = if isa.flags().unwind_info() {
|
let unwind_info = if isa.flags().unwind_info() {
|
||||||
context
|
context
|
||||||
@@ -713,7 +716,7 @@ impl Compiler {
|
|||||||
value_labels_ranges: Default::default(),
|
value_labels_ranges: Default::default(),
|
||||||
info: Default::default(),
|
info: Default::default(),
|
||||||
address_map: 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 {
|
fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation {
|
||||||
let &MachTrap { offset, code } = trap;
|
let &MachTrap { offset, code } = trap;
|
||||||
TrapInformation {
|
TrapInformation {
|
||||||
@@ -905,6 +910,7 @@ fn mach_trap_to_trap(trap: &MachTrap) -> TrapInformation {
|
|||||||
ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
|
ir::TrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
|
||||||
ir::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
|
ir::TrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
|
||||||
ir::TrapCode::Interrupt => TrapCode::Interrupt,
|
ir::TrapCode::Interrupt => TrapCode::Interrupt,
|
||||||
|
ir::TrapCode::User(ALWAYS_TRAP_CODE) => TrapCode::AlwaysTrapAdapter,
|
||||||
|
|
||||||
// these should never be emitted by wasmtime-cranelift
|
// these should never be emitted by wasmtime-cranelift
|
||||||
ir::TrapCode::User(_) => unreachable!(),
|
ir::TrapCode::User(_) => unreachable!(),
|
||||||
|
|||||||
@@ -9,10 +9,10 @@ use cranelift_frontend::FunctionBuilder;
|
|||||||
use object::write::Object;
|
use object::write::Object;
|
||||||
use std::any::Any;
|
use std::any::Any;
|
||||||
use wasmtime_environ::component::{
|
use wasmtime_environ::component::{
|
||||||
CanonicalOptions, Component, ComponentCompiler, ComponentTypes, LowerImport, LoweredIndex,
|
AlwaysTrapInfo, CanonicalOptions, Component, ComponentCompiler, ComponentTypes, LowerImport,
|
||||||
LoweringInfo, VMComponentOffsets,
|
LoweredIndex, LoweringInfo, RuntimeAlwaysTrapIndex, VMComponentOffsets,
|
||||||
};
|
};
|
||||||
use wasmtime_environ::{PrimaryMap, SignatureIndex, Trampoline};
|
use wasmtime_environ::{PrimaryMap, SignatureIndex, Trampoline, TrapCode, WasmFuncType};
|
||||||
|
|
||||||
impl ComponentCompiler for Compiler {
|
impl ComponentCompiler for Compiler {
|
||||||
fn compile_lowered_trampoline(
|
fn compile_lowered_trampoline(
|
||||||
@@ -152,25 +152,51 @@ impl ComponentCompiler for Compiler {
|
|||||||
Ok(Box::new(func))
|
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(
|
fn emit_obj(
|
||||||
&self,
|
&self,
|
||||||
lowerings: PrimaryMap<LoweredIndex, Box<dyn Any + Send>>,
|
lowerings: PrimaryMap<LoweredIndex, Box<dyn Any + Send>>,
|
||||||
|
always_trap: PrimaryMap<RuntimeAlwaysTrapIndex, Box<dyn Any + Send>>,
|
||||||
trampolines: Vec<(SignatureIndex, Box<dyn Any + Send>)>,
|
trampolines: Vec<(SignatureIndex, Box<dyn Any + Send>)>,
|
||||||
obj: &mut Object<'static>,
|
obj: &mut Object<'static>,
|
||||||
) -> Result<(PrimaryMap<LoweredIndex, LoweringInfo>, Vec<Trampoline>)> {
|
) -> Result<(
|
||||||
let lowerings: PrimaryMap<LoweredIndex, CompiledFunction> = lowerings
|
PrimaryMap<LoweredIndex, LoweringInfo>,
|
||||||
.into_iter()
|
PrimaryMap<RuntimeAlwaysTrapIndex, AlwaysTrapInfo>,
|
||||||
.map(|(_, f)| *f.downcast().unwrap())
|
Vec<Trampoline>,
|
||||||
.collect();
|
)> {
|
||||||
let trampolines: Vec<(SignatureIndex, CompiledFunction)> = trampolines
|
|
||||||
.into_iter()
|
|
||||||
.map(|(i, f)| (i, *f.downcast().unwrap()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let module = Default::default();
|
let module = Default::default();
|
||||||
let mut text = ModuleTextBuilder::new(obj, &module, &*self.isa);
|
let mut text = ModuleTextBuilder::new(obj, &module, &*self.isa);
|
||||||
let mut ret = PrimaryMap::new();
|
let mut ret = PrimaryMap::new();
|
||||||
for (idx, lowering) in lowerings.iter() {
|
for (idx, lowering) in lowerings.iter() {
|
||||||
|
let lowering = lowering.downcast_ref::<CompiledFunction>().unwrap();
|
||||||
|
assert!(lowering.traps.is_empty());
|
||||||
let (_symbol, range) = text.append_func(
|
let (_symbol, range) = text.append_func(
|
||||||
false,
|
false,
|
||||||
format!("_wasm_component_lowering_trampoline{}", idx.as_u32()).into_bytes(),
|
format!("_wasm_component_lowering_trampoline{}", idx.as_u32()).into_bytes(),
|
||||||
@@ -183,13 +209,35 @@ impl ComponentCompiler for Compiler {
|
|||||||
});
|
});
|
||||||
assert_eq!(i, idx);
|
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
|
let ret_trampolines = trampolines
|
||||||
.iter()
|
.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();
|
.collect();
|
||||||
|
|
||||||
text.finish()?;
|
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 {
|
pub fn trampoline(&mut self, sig: SignatureIndex, func: &'a CompiledFunction) -> Trampoline {
|
||||||
let name = obj::trampoline_symbol_name(sig);
|
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 {
|
Trampoline {
|
||||||
signature: sig,
|
signature: sig,
|
||||||
start: range.start,
|
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
|
/// Forces "veneers" to be used for inter-function calls in the text
|
||||||
/// section which means that in-bounds optimized addresses are never used.
|
/// section which means that in-bounds optimized addresses are never used.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
use crate::component::{Component, ComponentTypes, LowerImport, LoweredIndex};
|
use crate::component::{
|
||||||
use crate::{PrimaryMap, SignatureIndex, Trampoline};
|
Component, ComponentTypes, LowerImport, LoweredIndex, RuntimeAlwaysTrapIndex,
|
||||||
|
};
|
||||||
|
use crate::{PrimaryMap, SignatureIndex, Trampoline, WasmFuncType};
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use object::write::Object;
|
use object::write::Object;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -16,6 +18,19 @@ pub struct LoweringInfo {
|
|||||||
pub length: u32,
|
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.
|
/// Compilation support necessary for components.
|
||||||
pub trait ComponentCompiler: Send + Sync {
|
pub trait ComponentCompiler: Send + Sync {
|
||||||
/// Creates a trampoline for a `canon.lower`'d host function.
|
/// Creates a trampoline for a `canon.lower`'d host function.
|
||||||
@@ -42,6 +57,13 @@ pub trait ComponentCompiler: Send + Sync {
|
|||||||
types: &ComponentTypes,
|
types: &ComponentTypes,
|
||||||
) -> Result<Box<dyn Any + Send>>;
|
) -> 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
|
/// Emits the `lowerings` and `trampolines` specified into the in-progress
|
||||||
/// ELF object specified by `obj`.
|
/// ELF object specified by `obj`.
|
||||||
///
|
///
|
||||||
@@ -53,7 +75,12 @@ pub trait ComponentCompiler: Send + Sync {
|
|||||||
fn emit_obj(
|
fn emit_obj(
|
||||||
&self,
|
&self,
|
||||||
lowerings: PrimaryMap<LoweredIndex, Box<dyn Any + Send>>,
|
lowerings: PrimaryMap<LoweredIndex, Box<dyn Any + Send>>,
|
||||||
|
always_trap: PrimaryMap<RuntimeAlwaysTrapIndex, Box<dyn Any + Send>>,
|
||||||
tramplines: Vec<(SignatureIndex, Box<dyn Any + Send>)>,
|
tramplines: Vec<(SignatureIndex, Box<dyn Any + Send>)>,
|
||||||
obj: &mut Object<'static>,
|
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
|
/// The number of modules that are required to be saved within an instance
|
||||||
/// at runtime, or effectively the number of exported modules.
|
/// at runtime, or effectively the number of exported modules.
|
||||||
pub num_runtime_modules: u32,
|
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
|
/// 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.
|
/// pointer the trampoline calls, and the canonical ABI options.
|
||||||
LowerImport(LowerImport),
|
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
|
/// A core wasm linear memory is going to be saved into the
|
||||||
/// `VMComponentContext`.
|
/// `VMComponentContext`.
|
||||||
///
|
///
|
||||||
@@ -272,6 +282,17 @@ pub struct LowerImport {
|
|||||||
pub options: CanonicalOptions,
|
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
|
/// Definition of a core wasm item and where it can come from within a
|
||||||
/// component.
|
/// component.
|
||||||
///
|
///
|
||||||
@@ -288,6 +309,10 @@ pub enum CoreDef {
|
|||||||
/// that this `LoweredIndex` corresponds to the nth
|
/// that this `LoweredIndex` corresponds to the nth
|
||||||
/// `GlobalInitializer::LowerImport` instruction.
|
/// `GlobalInitializer::LowerImport` instruction.
|
||||||
Lowered(LoweredIndex),
|
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
|
impl<T> From<CoreExport<T>> for CoreDef
|
||||||
|
|||||||
@@ -46,7 +46,7 @@
|
|||||||
//! final `Component`.
|
//! final `Component`.
|
||||||
|
|
||||||
use crate::component::translate::*;
|
use crate::component::translate::*;
|
||||||
use crate::{ModuleTranslation, PrimaryMap};
|
use crate::{ModuleTranslation, PrimaryMap, SignatureIndex};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
|
||||||
pub(super) fn run(
|
pub(super) fn run(
|
||||||
@@ -64,6 +64,7 @@ pub(super) fn run(
|
|||||||
runtime_realloc_interner: Default::default(),
|
runtime_realloc_interner: Default::default(),
|
||||||
runtime_post_return_interner: Default::default(),
|
runtime_post_return_interner: Default::default(),
|
||||||
runtime_memory_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
|
// 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_realloc_interner: HashMap<CoreDef, RuntimeReallocIndex>,
|
||||||
runtime_post_return_interner: HashMap<CoreDef, RuntimePostReturnIndex>,
|
runtime_post_return_interner: HashMap<CoreDef, RuntimePostReturnIndex>,
|
||||||
runtime_memory_interner: HashMap<CoreExport<MemoryIndex>, RuntimeMemoryIndex>,
|
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
|
/// 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.
|
// NB: at this time only lowered imported functions are supported.
|
||||||
Lower(func, options) => {
|
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
|
// Use the type information from `wasmparser` to lookup the core
|
||||||
// wasm function signature of the lowered function. This avoids
|
// wasm function signature of the lowered function. This avoids
|
||||||
// us having to reimplement the
|
// us having to reimplement the
|
||||||
@@ -461,20 +457,22 @@ impl<'a> Inliner<'a> {
|
|||||||
.types
|
.types
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.function_at(func_index.as_u32())
|
.function_at(frame.funcs.next_key().as_u32())
|
||||||
.expect("should be in-bounds");
|
.expect("should be in-bounds");
|
||||||
let canonical_abi = self
|
let canonical_abi = self
|
||||||
.types
|
.types
|
||||||
.module_types_builder()
|
.module_types_builder()
|
||||||
.wasm_func_type(lowered_function_type.clone().try_into()?);
|
.wasm_func_type(lowered_function_type.clone().try_into()?);
|
||||||
|
|
||||||
let options = self.canonical_options(frame, options);
|
let options_lower = self.canonical_options(frame, options);
|
||||||
match &frame.component_funcs[*func] {
|
let func = match &frame.component_funcs[*func] {
|
||||||
// If this component function was originally a host import
|
// If this component function was originally a host import
|
||||||
// then this is a lowered host function which needs a
|
// then this is a lowered host function which needs a
|
||||||
// trampoline to enter WebAssembly. That's recorded here
|
// trampoline to enter WebAssembly. That's recorded here
|
||||||
// with all relevant information.
|
// with all relevant information.
|
||||||
ComponentFuncDef::Import(path) => {
|
ComponentFuncDef::Import(path) => {
|
||||||
|
let index = LoweredIndex::from_u32(self.result.num_lowerings);
|
||||||
|
self.result.num_lowerings += 1;
|
||||||
let import = self.runtime_import(path);
|
let import = self.runtime_import(path);
|
||||||
self.result
|
self.result
|
||||||
.initializers
|
.initializers
|
||||||
@@ -482,35 +480,91 @@ impl<'a> Inliner<'a> {
|
|||||||
canonical_abi,
|
canonical_abi,
|
||||||
import,
|
import,
|
||||||
index,
|
index,
|
||||||
options,
|
options: options_lower,
|
||||||
}));
|
}));
|
||||||
|
CoreDef::Lowered(index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Lowering a lift function could mean one of two
|
// This case handles when a lifted function is later
|
||||||
// things:
|
// lowered, and both the lowering and the lifting are
|
||||||
|
// happening within the same component instance.
|
||||||
//
|
//
|
||||||
// * This could mean that a "fused adapter" was just
|
// In this situation if the `canon.lower`'d function is
|
||||||
// identified. If the lifted function here comes from a
|
// called then it immediately sets `may_enter` to `false`.
|
||||||
// different component than we're lowering into then we
|
// When calling the callee, however, that's `canon.lift`
|
||||||
// have identified the fusion location of two components
|
// which immediately traps if `may_enter` is `false`. That
|
||||||
// talking to each other. Metadata needs to be recorded
|
// means that this pairing of functions creates a function
|
||||||
// here about the fusion to get something generated by
|
// that always traps.
|
||||||
// Cranelift later on.
|
|
||||||
//
|
//
|
||||||
// * Otherwise if the lifted function is in the same
|
// When closely reading the spec though the precise trap
|
||||||
// component that we're lowering into then that means
|
// that comes out can be somewhat variable. Technically the
|
||||||
// something "funky" is happening. This needs to be
|
// function yielded here is one that should validate the
|
||||||
// carefully implemented with respect to the
|
// arguments by lifting them, and then trap. This means that
|
||||||
// may_{enter,leave} flags as specified with the canonical
|
// the trap could be different depending on whether all
|
||||||
// ABI. The careful consideration for how to do this has
|
// arguments are valid for now. This was discussed in
|
||||||
// not yet happened.
|
// 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
|
// The `CoreDef::AlwaysTrap` variant here is used to
|
||||||
// new variant of `GlobalInitializer` in one form or another.
|
// indicate that this function is valid but if something
|
||||||
ComponentFuncDef::Lifted { .. } => {
|
// 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")
|
unimplemented!("lowering a lifted function")
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
frame.funcs.push(func);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lifting a core wasm function is relatively easy for now in that
|
// Lifting a core wasm function is relatively easy for now in that
|
||||||
@@ -656,7 +710,7 @@ impl<'a> Inliner<'a> {
|
|||||||
frame.tables.push(
|
frame.tables.push(
|
||||||
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
||||||
CoreDef::Export(e) => e,
|
CoreDef::Export(e) => e,
|
||||||
CoreDef::Lowered(_) => unreachable!(),
|
CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -665,7 +719,7 @@ impl<'a> Inliner<'a> {
|
|||||||
frame.globals.push(
|
frame.globals.push(
|
||||||
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
||||||
CoreDef::Export(e) => e,
|
CoreDef::Export(e) => e,
|
||||||
CoreDef::Lowered(_) => unreachable!(),
|
CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -674,7 +728,7 @@ impl<'a> Inliner<'a> {
|
|||||||
frame.memories.push(
|
frame.memories.push(
|
||||||
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
match self.core_def_of_module_instance_export(frame, *instance, *name) {
|
||||||
CoreDef::Export(e) => e,
|
CoreDef::Export(e) => e,
|
||||||
CoreDef::Lowered(_) => unreachable!(),
|
CoreDef::Lowered(_) | CoreDef::AlwaysTrap(_) => unreachable!(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,9 @@ indices! {
|
|||||||
/// component model.
|
/// component model.
|
||||||
pub struct LoweredIndex(u32);
|
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
|
/// Index representing a linear memory extracted from a wasm instance
|
||||||
/// which is stored in a `VMComponentContext`. This is used to deduplicate
|
/// 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
|
/// references to the same linear memory where it's only stored once in a
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
// store: *mut dyn Store,
|
// store: *mut dyn Store,
|
||||||
// flags: [VMComponentFlags; component.num_runtime_component_instances],
|
// flags: [VMComponentFlags; component.num_runtime_component_instances],
|
||||||
// lowering_anyfuncs: [VMCallerCheckedAnyfunc; component.num_lowerings],
|
// lowering_anyfuncs: [VMCallerCheckedAnyfunc; component.num_lowerings],
|
||||||
|
// always_trap_anyfuncs: [VMCallerCheckedAnyfunc; component.num_always_trap],
|
||||||
// lowerings: [VMLowering; component.num_lowerings],
|
// lowerings: [VMLowering; component.num_lowerings],
|
||||||
// memories: [*mut VMMemoryDefinition; component.num_memories],
|
// memories: [*mut VMMemoryDefinition; component.num_memories],
|
||||||
// reallocs: [*mut VMCallerCheckedAnyfunc; component.num_reallocs],
|
// reallocs: [*mut VMCallerCheckedAnyfunc; component.num_reallocs],
|
||||||
@@ -12,8 +13,8 @@
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
use crate::component::{
|
use crate::component::{
|
||||||
Component, LoweredIndex, RuntimeComponentInstanceIndex, RuntimeMemoryIndex,
|
Component, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex,
|
||||||
RuntimePostReturnIndex, RuntimeReallocIndex,
|
RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex,
|
||||||
};
|
};
|
||||||
use crate::PtrSize;
|
use crate::PtrSize;
|
||||||
|
|
||||||
@@ -52,12 +53,16 @@ pub struct VMComponentOffsets<P> {
|
|||||||
/// Number of component instances internally in the component (always at
|
/// Number of component instances internally in the component (always at
|
||||||
/// least 1).
|
/// least 1).
|
||||||
pub num_runtime_component_instances: u32,
|
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
|
// precalculated offsets of various member fields
|
||||||
magic: u32,
|
magic: u32,
|
||||||
store: u32,
|
store: u32,
|
||||||
flags: u32,
|
flags: u32,
|
||||||
lowering_anyfuncs: u32,
|
lowering_anyfuncs: u32,
|
||||||
|
always_trap_anyfuncs: u32,
|
||||||
lowerings: u32,
|
lowerings: u32,
|
||||||
memories: u32,
|
memories: u32,
|
||||||
reallocs: u32,
|
reallocs: u32,
|
||||||
@@ -85,10 +90,12 @@ impl<P: PtrSize> VMComponentOffsets<P> {
|
|||||||
.num_runtime_component_instances
|
.num_runtime_component_instances
|
||||||
.try_into()
|
.try_into()
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
|
num_always_trap: component.num_always_trap,
|
||||||
magic: 0,
|
magic: 0,
|
||||||
store: 0,
|
store: 0,
|
||||||
flags: 0,
|
flags: 0,
|
||||||
lowering_anyfuncs: 0,
|
lowering_anyfuncs: 0,
|
||||||
|
always_trap_anyfuncs: 0,
|
||||||
lowerings: 0,
|
lowerings: 0,
|
||||||
memories: 0,
|
memories: 0,
|
||||||
reallocs: 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()),
|
size(flags) = cmul(ret.num_runtime_component_instances, ret.size_of_vmcomponent_flags()),
|
||||||
align(u32::from(ret.ptr.size())),
|
align(u32::from(ret.ptr.size())),
|
||||||
size(lowering_anyfuncs) = cmul(ret.num_lowerings, ret.ptr.size_of_vmcaller_checked_anyfunc()),
|
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(lowerings) = cmul(ret.num_lowerings, ret.ptr.size() * 2),
|
||||||
size(memories) = cmul(ret.num_runtime_memories, ret.ptr.size()),
|
size(memories) = cmul(ret.num_runtime_memories, ret.ptr.size()),
|
||||||
size(reallocs) = cmul(ret.num_runtime_reallocs, 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())
|
+ 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.
|
/// The offset of the `lowerings` field.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn lowerings(&self) -> u32 {
|
pub fn lowerings(&self) -> u32 {
|
||||||
|
|||||||
@@ -98,6 +98,10 @@ pub enum TrapCode {
|
|||||||
/// Execution has potentially run too long and may be interrupted.
|
/// Execution has potentially run too long and may be interrupted.
|
||||||
/// This trap is resumable.
|
/// This trap is resumable.
|
||||||
Interrupt,
|
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
|
// 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
|
BadConversionToInteger
|
||||||
UnreachableCodeReached
|
UnreachableCodeReached
|
||||||
Interrupt
|
Interrupt
|
||||||
|
AlwaysTrapAdapter
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ use std::mem;
|
|||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::ptr::{self, NonNull};
|
use std::ptr::{self, NonNull};
|
||||||
use wasmtime_environ::component::{
|
use wasmtime_environ::component::{
|
||||||
Component, LoweredIndex, RuntimeComponentInstanceIndex, RuntimeMemoryIndex,
|
Component, LoweredIndex, RuntimeAlwaysTrapIndex, RuntimeComponentInstanceIndex,
|
||||||
RuntimePostReturnIndex, RuntimeReallocIndex, StringEncoding, VMComponentOffsets,
|
RuntimeMemoryIndex, RuntimePostReturnIndex, RuntimeReallocIndex, StringEncoding,
|
||||||
VMCOMPONENT_FLAG_MAY_ENTER, VMCOMPONENT_FLAG_MAY_LEAVE, VMCOMPONENT_FLAG_NEEDS_POST_RETURN,
|
VMComponentOffsets, VMCOMPONENT_FLAG_MAY_ENTER, VMCOMPONENT_FLAG_MAY_LEAVE,
|
||||||
VMCOMPONENT_MAGIC,
|
VMCOMPONENT_FLAG_NEEDS_POST_RETURN, VMCOMPONENT_MAGIC,
|
||||||
};
|
};
|
||||||
use wasmtime_environ::HostPtr;
|
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.
|
/// Stores the runtime memory pointer at the index specified.
|
||||||
///
|
///
|
||||||
/// This is intended to be called during the instantiation process of a
|
/// 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) {
|
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.magic()) = VMCOMPONENT_MAGIC;
|
||||||
*self.vmctx_plus_offset(self.offsets.store()) = store;
|
*self.vmctx_plus_offset(self.offsets.store()) = store;
|
||||||
@@ -362,6 +398,11 @@ impl ComponentInstance {
|
|||||||
let offset = self.offsets.lowering_anyfunc(i);
|
let offset = self.offsets.lowering_anyfunc(i);
|
||||||
*self.vmctx_plus_offset(offset) = INVALID_PTR;
|
*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 {
|
for i in 0..self.offsets.num_runtime_memories {
|
||||||
let i = RuntimeMemoryIndex::from_u32(i);
|
let i = RuntimeMemoryIndex::from_u32(i);
|
||||||
let offset = self.offsets.runtime_memory(i);
|
let offset = self.offsets.runtime_memory(i);
|
||||||
@@ -476,6 +517,19 @@ impl OwnedComponentInstance {
|
|||||||
.set_lowering(idx, lowering, anyfunc_func_ptr, anyfunc_type_index)
|
.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 {
|
impl Deref for OwnedComponentInstance {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::signatures::SignatureCollection;
|
use crate::signatures::SignatureCollection;
|
||||||
use crate::{Engine, Module};
|
use crate::{Engine, Module};
|
||||||
use anyhow::{bail, Context, Result};
|
use anyhow::{bail, Context, Result};
|
||||||
|
use std::any::Any;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -9,9 +10,10 @@ use std::path::Path;
|
|||||||
use std::ptr::NonNull;
|
use std::ptr::NonNull;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmtime_environ::component::{
|
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_jit::CodeMemory;
|
||||||
use wasmtime_runtime::VMFunctionBody;
|
use wasmtime_runtime::VMFunctionBody;
|
||||||
|
|
||||||
@@ -41,19 +43,27 @@ struct ComponentInner {
|
|||||||
/// this field.
|
/// this field.
|
||||||
types: Arc<ComponentTypes>,
|
types: Arc<ComponentTypes>,
|
||||||
|
|
||||||
/// The in-memory ELF image of the compiled trampolines for this component.
|
/// The in-memory ELF image of the compiled functions for this component.
|
||||||
///
|
|
||||||
/// This is currently only used for wasm-to-host trampolines when
|
|
||||||
/// `canon.lower` is encountered.
|
|
||||||
trampoline_obj: CodeMemory,
|
trampoline_obj: CodeMemory,
|
||||||
|
|
||||||
/// The index ranges within `trampoline_obj`'s mmap memory for the entire
|
/// The index ranges within `trampoline_obj`'s mmap memory for the entire
|
||||||
/// text section.
|
/// text section.
|
||||||
text: Range<usize>,
|
text: Range<usize>,
|
||||||
|
|
||||||
/// Where trampolines are located within the `text` section of
|
/// Where lowered function trampolines are located within the `text`
|
||||||
/// `trampoline_obj`.
|
/// section of `trampoline_obj`.
|
||||||
trampolines: PrimaryMap<LoweredIndex, LoweringInfo>,
|
///
|
||||||
|
/// 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 {
|
impl Component {
|
||||||
@@ -117,39 +127,10 @@ impl Component {
|
|||||||
.context("failed to parse WebAssembly module")?;
|
.context("failed to parse WebAssembly module")?;
|
||||||
let types = Arc::new(types.finish());
|
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
|
let provided_trampolines = modules
|
||||||
.iter()
|
.iter()
|
||||||
.flat_map(|(_, m)| m.exported_signatures.iter().copied())
|
.flat_map(|(_, m)| m.exported_signatures.iter().copied())
|
||||||
.collect::<HashSet<_>>();
|
.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(
|
let (static_modules, trampolines) = engine.join_maybe_parallel(
|
||||||
// In one (possibly) parallel task all the modules found within this
|
// 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
|
// In another (possibly) parallel task we compile lowering
|
||||||
// trampolines necessary found in the component.
|
// trampolines necessary found in the component.
|
||||||
|| -> Result<_> {
|
|| Component::compile_component(engine, &component, &types, &provided_trampolines),
|
||||||
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)?))
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
let static_modules = static_modules?;
|
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 mut trampoline_obj = CodeMemory::new(trampoline_obj);
|
||||||
let code = trampoline_obj.publish()?;
|
let code = trampoline_obj.publish()?;
|
||||||
let text = wasmtime_jit::subslice_range(code.text, code.mmap);
|
let text = wasmtime_jit::subslice_range(code.text, code.mmap);
|
||||||
@@ -231,8 +181,8 @@ impl Component {
|
|||||||
vmtrampolines.insert(idx, trampoline);
|
vmtrampolines.insert(idx, trampoline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (signature, trampoline) in trampolines_to_compile.iter().zip(core_trampolines) {
|
for trampoline in trampolines {
|
||||||
vmtrampolines.insert(**signature, unsafe {
|
vmtrampolines.insert(trampoline.signature, unsafe {
|
||||||
let ptr =
|
let ptr =
|
||||||
code.text[trampoline.start as usize..][..trampoline.length as usize].as_ptr();
|
code.text[trampoline.start as usize..][..trampoline.length as usize].as_ptr();
|
||||||
std::mem::transmute::<*const u8, wasmtime_runtime::VMTrampoline>(ptr)
|
std::mem::transmute::<*const u8, wasmtime_runtime::VMTrampoline>(ptr)
|
||||||
@@ -248,6 +198,15 @@ impl Component {
|
|||||||
vmtrampolines.into_iter(),
|
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 {
|
Ok(Component {
|
||||||
inner: Arc::new(ComponentInner {
|
inner: Arc::new(ComponentInner {
|
||||||
component,
|
component,
|
||||||
@@ -256,11 +215,136 @@ impl Component {
|
|||||||
signatures,
|
signatures,
|
||||||
trampoline_obj,
|
trampoline_obj,
|
||||||
text,
|
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 {
|
pub(crate) fn env_component(&self) -> &wasmtime_environ::component::Component {
|
||||||
&self.inner.component
|
&self.inner.component
|
||||||
}
|
}
|
||||||
@@ -278,13 +362,52 @@ impl Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn text(&self) -> &[u8] {
|
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> {
|
pub(crate) fn lowering_ptr(&self, index: LoweredIndex) -> NonNull<VMFunctionBody> {
|
||||||
let info = &self.inner.trampolines[index];
|
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 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()
|
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::marker;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use wasmtime_environ::component::{
|
use wasmtime_environ::component::{
|
||||||
ComponentTypes, CoreDef, CoreExport, Export, ExportItem, ExtractMemory, ExtractPostReturn,
|
AlwaysTrap, ComponentTypes, CoreDef, CoreExport, Export, ExportItem, ExtractMemory,
|
||||||
ExtractRealloc, GlobalInitializer, InstantiateModule, LowerImport, RuntimeImportIndex,
|
ExtractPostReturn, ExtractRealloc, GlobalInitializer, InstantiateModule, LowerImport,
|
||||||
RuntimeInstanceIndex, RuntimeModuleIndex,
|
RuntimeImportIndex, RuntimeInstanceIndex, RuntimeModuleIndex,
|
||||||
};
|
};
|
||||||
use wasmtime_environ::{EntityIndex, PrimaryMap};
|
use wasmtime_environ::{EntityIndex, PrimaryMap};
|
||||||
use wasmtime_runtime::component::{ComponentInstance, OwnedComponentInstance};
|
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 {
|
pub fn lookup_def(&self, store: &mut StoreOpaque, def: &CoreDef) -> wasmtime_runtime::Export {
|
||||||
match def {
|
match def {
|
||||||
|
CoreDef::Export(e) => self.lookup_export(store, e),
|
||||||
CoreDef::Lowered(idx) => {
|
CoreDef::Lowered(idx) => {
|
||||||
wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction {
|
wasmtime_runtime::Export::Function(wasmtime_runtime::ExportFunction {
|
||||||
anyfunc: self.state.lowering_anyfunc(*idx),
|
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::LowerImport(import) => self.lower_import(import),
|
||||||
|
|
||||||
|
GlobalInitializer::AlwaysTrap(trap) => self.always_trap(trap),
|
||||||
|
|
||||||
GlobalInitializer::ExtractMemory(mem) => self.extract_memory(store.0, mem),
|
GlobalInitializer::ExtractMemory(mem) => self.extract_memory(store.0, mem),
|
||||||
|
|
||||||
GlobalInitializer::ExtractRealloc(realloc) => {
|
GlobalInitializer::ExtractRealloc(realloc) => {
|
||||||
@@ -307,7 +314,7 @@ impl<'a> Instantiator<'a> {
|
|||||||
self.data.state.set_lowering(
|
self.data.state.set_lowering(
|
||||||
import.index,
|
import.index,
|
||||||
func.lowering(),
|
func.lowering(),
|
||||||
self.component.trampoline_ptr(import.index),
|
self.component.lowering_ptr(import.index),
|
||||||
self.component
|
self.component
|
||||||
.signatures()
|
.signatures()
|
||||||
.shared_signature(import.canonical_abi)
|
.shared_signature(import.canonical_abi)
|
||||||
@@ -324,6 +331,17 @@ impl<'a> Instantiator<'a> {
|
|||||||
self.data.funcs.push(func.clone());
|
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) {
|
fn extract_memory(&mut self, store: &mut StoreOpaque, memory: &ExtractMemory) {
|
||||||
let mem = match self.data.lookup_export(store, &memory.export) {
|
let mem = match self.data.lookup_export(store, &memory.export) {
|
||||||
wasmtime_runtime::Export::Memory(m) => m,
|
wasmtime_runtime::Export::Memory(m) => m,
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ mod registry;
|
|||||||
mod serialization;
|
mod serialization;
|
||||||
|
|
||||||
pub use registry::{is_wasm_trap_pc, ModuleRegistry};
|
pub use registry::{is_wasm_trap_pc, ModuleRegistry};
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
pub use registry::{register_component, unregister_component};
|
||||||
pub use serialization::SerializedModule;
|
pub use serialization::SerializedModule;
|
||||||
|
|
||||||
/// A compiled WebAssembly module, ready to be instantiated.
|
/// 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
|
// into the global registry of modules so we can resolve traps
|
||||||
// appropriately. Note that the corresponding `unregister` happens below
|
// appropriately. Note that the corresponding `unregister` happens below
|
||||||
// in `Drop for ModuleInner`.
|
// in `Drop for ModuleInner`.
|
||||||
registry::register(&module);
|
registry::register_module(&module);
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
inner: Arc::new(ModuleInner {
|
inner: Arc::new(ModuleInner {
|
||||||
@@ -987,7 +989,7 @@ impl wasmtime_runtime::ModuleInfo for ModuleInner {
|
|||||||
|
|
||||||
impl Drop for ModuleInner {
|
impl Drop for ModuleInner {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
registry::unregister(&self.module);
|
registry::unregister_module(&self.module);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,11 @@ use std::{
|
|||||||
sync::{Arc, RwLock},
|
sync::{Arc, RwLock},
|
||||||
};
|
};
|
||||||
use wasmtime_environ::TrapCode;
|
use wasmtime_environ::TrapCode;
|
||||||
|
#[cfg(feature = "component-model")]
|
||||||
|
use wasmtime_environ::{
|
||||||
|
component::{AlwaysTrapInfo, RuntimeAlwaysTrapIndex},
|
||||||
|
PrimaryMap,
|
||||||
|
};
|
||||||
use wasmtime_jit::CompiledModule;
|
use wasmtime_jit::CompiledModule;
|
||||||
use wasmtime_runtime::{ModuleInfo, VMCallerCheckedAnyfunc, VMTrampoline};
|
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
|
/// 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
|
/// 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
|
/// 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
|
/// theoretically be omitted as they're not needed by the store they're
|
||||||
/// currently small enough to not worry much about.
|
/// currently small enough to not worry much about.
|
||||||
@@ -147,8 +152,13 @@ impl ModuleRegistry {
|
|||||||
|
|
||||||
/// Fetches trap information about a program counter in a backtrace.
|
/// Fetches trap information about a program counter in a backtrace.
|
||||||
pub fn lookup_trap_code(&self, pc: usize) -> Option<TrapCode> {
|
pub fn lookup_trap_code(&self, pc: usize) -> Option<TrapCode> {
|
||||||
let (module, offset) = self.module(pc)?;
|
match self.module_or_component(pc)? {
|
||||||
wasmtime_environ::lookup_trap_code(module.compiled_module().trap_data(), offset)
|
(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.
|
/// 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
|
/// boolean indicates whether the engine used to compile this module is
|
||||||
/// using environment variables to control debuginfo parsing.
|
/// using environment variables to control debuginfo parsing.
|
||||||
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> {
|
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> {
|
||||||
let (module, offset) = self.module(pc)?;
|
match self.module_or_component(pc)? {
|
||||||
let info = FrameInfo::new(module, offset)?;
|
(ModuleOrComponent::Module(module), offset) => {
|
||||||
Some((info, module))
|
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();
|
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,
|
/// Returns whether the `pc`, according to globally registered information,
|
||||||
/// is a wasm trap or not.
|
/// is a wasm trap or not.
|
||||||
pub fn is_wasm_trap_pc(pc: usize) -> bool {
|
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 all_modules = GLOBAL_MODULES.read().unwrap();
|
||||||
|
|
||||||
let (end, (start, module)) = match all_modules.range(pc..).next() {
|
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)
|
(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.
|
/// 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
|
/// 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
|
/// will lookup in the `GLOBAL_MODULES` list to determine which a particular pc
|
||||||
/// is a trap or not.
|
/// is a trap or not.
|
||||||
pub fn register(module: &Arc<CompiledModule>) {
|
pub fn register_module(module: &Arc<CompiledModule>) {
|
||||||
let code = module.code();
|
let code = module.code();
|
||||||
if code.is_empty() {
|
if code.is_empty() {
|
||||||
return;
|
return;
|
||||||
@@ -222,14 +260,14 @@ pub fn register(module: &Arc<CompiledModule>) {
|
|||||||
let prev = GLOBAL_MODULES
|
let prev = GLOBAL_MODULES
|
||||||
.write()
|
.write()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.insert(end, (start, module.clone()));
|
.insert(end, (start, TrapInfo::Module(module.clone())));
|
||||||
assert!(prev.is_none());
|
assert!(prev.is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Unregisters a module from the global map.
|
/// Unregisters a module from the global map.
|
||||||
///
|
///
|
||||||
/// Must hae been previously registered with `register`.
|
/// Must have been previously registered with `register`.
|
||||||
pub fn unregister(module: &Arc<CompiledModule>) {
|
pub fn unregister_module(module: &Arc<CompiledModule>) {
|
||||||
let code = module.code();
|
let code = module.code();
|
||||||
if code.is_empty() {
|
if code.is_empty() {
|
||||||
return;
|
return;
|
||||||
@@ -239,6 +277,39 @@ pub fn unregister(module: &Arc<CompiledModule>) {
|
|||||||
assert!(module.is_some());
|
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]
|
#[test]
|
||||||
fn test_frame_info() -> Result<(), anyhow::Error> {
|
fn test_frame_info() -> Result<(), anyhow::Error> {
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|||||||
@@ -87,6 +87,13 @@ pub enum TrapCode {
|
|||||||
|
|
||||||
/// Execution has potentially run too long and may be interrupted.
|
/// Execution has potentially run too long and may be interrupted.
|
||||||
Interrupt,
|
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 {
|
impl TrapCode {
|
||||||
@@ -104,6 +111,7 @@ impl TrapCode {
|
|||||||
EnvTrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
|
EnvTrapCode::BadConversionToInteger => TrapCode::BadConversionToInteger,
|
||||||
EnvTrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
|
EnvTrapCode::UnreachableCodeReached => TrapCode::UnreachableCodeReached,
|
||||||
EnvTrapCode::Interrupt => TrapCode::Interrupt,
|
EnvTrapCode::Interrupt => TrapCode::Interrupt,
|
||||||
|
EnvTrapCode::AlwaysTrapAdapter => TrapCode::AlwaysTrapAdapter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,6 +131,7 @@ impl fmt::Display for TrapCode {
|
|||||||
BadConversionToInteger => "invalid conversion to integer",
|
BadConversionToInteger => "invalid conversion to integer",
|
||||||
UnreachableCodeReached => "wasm `unreachable` instruction executed",
|
UnreachableCodeReached => "wasm `unreachable` instruction executed",
|
||||||
Interrupt => "interrupt",
|
Interrupt => "interrupt",
|
||||||
|
AlwaysTrapAdapter => "degenerate component adapter called",
|
||||||
};
|
};
|
||||||
write!(f, "{}", desc)
|
write!(f, "{}", desc)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -83,3 +83,31 @@
|
|||||||
)
|
)
|
||||||
(export "f" (func $f2))
|
(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