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.
|
||||
///
|
||||
|
||||
Reference in New Issue
Block a user