Add trampoline compilation support for lowered imports (#4206)

* Add trampoline compilation support for lowered imports

This commit adds support to the component model implementation for
compiling trampolines suitable for calling host imports. Currently this
is purely just the compilation side of things, modifying the
wasmtime-cranelift crate and additionally filling out a new
`VMComponentOffsets` type (similar to `VMOffsets`). The actual creation
of a `VMComponentContext` is still not performed and will be a
subsequent PR.

Internally though some tests are actually possible with this where we at
least assert that compilation of a component and creation of everything
in-memory doesn't panic or trip any assertions, so some tests are added
here for that as well.

* Fix some test errors
This commit is contained in:
Alex Crichton
2022-06-03 10:01:42 -05:00
committed by GitHub
parent b49c5c878e
commit 3ed6fae7b3
17 changed files with 748 additions and 58 deletions

View File

@@ -0,0 +1,58 @@
use crate::component::{Component, ComponentTypes, LowerImport, LoweredIndex};
use crate::PrimaryMap;
use anyhow::Result;
use object::write::Object;
use serde::{Deserialize, Serialize};
use std::any::Any;
/// Description of where a trampoline is located in the text section of a
/// compiled image.
#[derive(Serialize, Deserialize)]
pub struct TrampolineInfo {
/// 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,
}
/// Compilation support necessary for components.
pub trait ComponentCompiler: Send + Sync {
/// Creates a trampoline for a `canon.lower`'d host function.
///
/// This function will create a suitable trampoline which can be called from
/// WebAssembly code and which will then call into host code. The signature
/// of this generated trampoline should have the appropriate wasm ABI for
/// the `lowering.canonical_abi` type signature (e.g. System-V).
///
/// The generated trampoline will interpret its first argument as a
/// `*mut VMComponentContext` and use the `VMComponentOffsets` for
/// `component` to read necessary data (as specified by `lowering.options`)
/// and call the host function pointer. Notably the host function pointer
/// has the signature `VMLoweringCallee` where many of the arguments are
/// loaded from known offsets (for this particular generated trampoline)
/// from the `VMComponentContext`.
///
/// Returns a compiler-specific `Box<dyn Any>` which can be passed later to
/// `emit_obj` to crate an elf object.
fn compile_lowered_trampoline(
&self,
component: &Component,
lowering: &LowerImport,
types: &ComponentTypes,
) -> Result<Box<dyn Any + Send>>;
/// Emits the `trampolines` specified into the in-progress ELF object
/// specified by `obj`.
///
/// Returns a map of trampoline information for where to find them all in
/// the text section.
///
/// Note that this will also prepare unwinding information for all the
/// trampolines as necessary.
fn emit_obj(
&self,
trampolines: PrimaryMap<LoweredIndex, Box<dyn Any + Send>>,
obj: &mut Object<'static>,
) -> Result<PrimaryMap<LoweredIndex, TrampolineInfo>>;
}

View File

@@ -0,0 +1,240 @@
// Currently the `VMComponentContext` allocation by field looks like this:
//
// struct VMComponentContext {
// magic: u32,
// may_enter: u8,
// may_leave: u8,
// store: *mut dyn Store,
// lowering_anyfuncs: [VMCallerCheckedAnyfunc; component.num_lowerings],
// lowerings: [VMLowering; component.num_lowerings],
// memories: [*mut VMMemoryDefinition; component.num_memories],
// reallocs: [*mut VMCallerCheckedAnyfunc; component.num_reallocs],
// }
use crate::component::{Component, LoweredIndex, RuntimeMemoryIndex, RuntimeReallocIndex};
use crate::PtrSize;
/// Equivalent of `VMCONTEXT_MAGIC` except for components.
///
/// This is stored at the start of all `VMComponentContext` structures adn
/// double-checked on `VMComponentContext::from_opaque`.
pub const VMCOMPONENT_MAGIC: u32 = u32::from_le_bytes(*b"comp");
/// Runtime offsets within a `VMComponentContext` for a specific component.
#[derive(Debug, Clone, Copy)]
pub struct VMComponentOffsets<P> {
/// The host pointer size
pub ptr: P,
/// The number of lowered functions this component will be creating.
pub num_lowerings: u32,
/// The number of memories which are recorded in this component for options.
pub num_runtime_memories: u32,
/// The number of reallocs which are recorded in this component for options.
pub num_runtime_reallocs: u32,
// precalculated offsets of various member fields
magic: u32,
may_enter: u32,
may_leave: u32,
store: u32,
lowering_anyfuncs: u32,
lowerings: u32,
memories: u32,
reallocs: u32,
size: u32,
}
#[inline]
fn align(offset: u32, align: u32) -> u32 {
assert!(align.is_power_of_two());
(offset + (align - 1)) & !(align - 1)
}
impl<P: PtrSize> VMComponentOffsets<P> {
/// Creates a new set of offsets for the `component` specified configured
/// additionally for the `ptr` size specified.
pub fn new(ptr: P, component: &Component) -> Self {
let mut ret = Self {
ptr,
num_lowerings: component.num_lowerings.try_into().unwrap(),
num_runtime_memories: component.num_runtime_memories.try_into().unwrap(),
num_runtime_reallocs: component.num_runtime_reallocs.try_into().unwrap(),
magic: 0,
may_enter: 0,
may_leave: 0,
store: 0,
lowering_anyfuncs: 0,
lowerings: 0,
memories: 0,
reallocs: 0,
size: 0,
};
// Convenience functions for checked addition and multiplication.
// As side effect this reduces binary size by using only a single
// `#[track_caller]` location for each function instead of one for
// each individual invocation.
#[inline]
fn cmul(count: u32, size: u8) -> u32 {
count.checked_mul(u32::from(size)).unwrap()
}
let mut next_field_offset = 0;
macro_rules! fields {
(size($field:ident) = $size:expr, $($rest:tt)*) => {
ret.$field = next_field_offset;
next_field_offset = next_field_offset.checked_add(u32::from($size)).unwrap();
fields!($($rest)*);
};
(align($align:expr), $($rest:tt)*) => {
next_field_offset = align(next_field_offset, $align);
fields!($($rest)*);
};
() => {};
}
fields! {
size(magic) = 4u32,
size(may_enter) = 1u32,
size(may_leave) = 1u32,
align(u32::from(ret.ptr.size())),
size(store) = cmul(2, ret.ptr.size()),
size(lowering_anyfuncs) = cmul(ret.num_lowerings, 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()),
}
ret.size = next_field_offset;
// This is required by the implementation of
// `VMComponentContext::from_opaque`. If this value changes then this
// location needs to be updated.
assert_eq!(ret.magic, 0);
return ret;
}
/// The size, in bytes, of the host pointer.
#[inline]
pub fn pointer_size(&self) -> u8 {
self.ptr.size()
}
/// The offset of the `magic` field.
#[inline]
pub fn magic(&self) -> u32 {
self.magic
}
/// The offset of the `may_leave` field.
#[inline]
pub fn may_leave(&self) -> u32 {
self.may_leave
}
/// The offset of the `may_enter` field.
#[inline]
pub fn may_enter(&self) -> u32 {
self.may_enter
}
/// The offset of the `store` field.
#[inline]
pub fn store(&self) -> u32 {
self.store
}
/// The offset of the `lowering_anyfuncs` field.
#[inline]
pub fn lowering_anyfuncs(&self) -> u32 {
self.lowering_anyfuncs
}
/// The offset of `VMCallerCheckedAnyfunc` for the `index` specified.
#[inline]
pub fn lowering_anyfunc(&self, index: LoweredIndex) -> u32 {
assert!(index.as_u32() < self.num_lowerings);
self.lowering_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 {
self.lowerings
}
/// The offset of the `VMLowering` for the `index` specified.
#[inline]
pub fn lowering(&self, index: LoweredIndex) -> u32 {
assert!(index.as_u32() < self.num_lowerings);
self.lowerings() + index.as_u32() * u32::from(2 * self.ptr.size())
}
/// The offset of the `callee` for the `index` specified.
#[inline]
pub fn lowering_callee(&self, index: LoweredIndex) -> u32 {
self.lowering(index) + self.lowering_callee_offset()
}
/// The offset of the `data` for the `index` specified.
#[inline]
pub fn lowering_data(&self, index: LoweredIndex) -> u32 {
self.lowering(index) + self.lowering_data_offset()
}
/// The size of the `VMLowering` type
#[inline]
pub fn lowering_size(&self) -> u8 {
2 * self.ptr.size()
}
/// The offset of the `callee` field within the `VMLowering` type.
#[inline]
pub fn lowering_callee_offset(&self) -> u32 {
0
}
/// The offset of the `data` field within the `VMLowering` type.
#[inline]
pub fn lowering_data_offset(&self) -> u32 {
u32::from(self.ptr.size())
}
/// The offset of the base of the `runtime_memories` field
#[inline]
pub fn runtime_memories(&self) -> u32 {
self.memories
}
/// The offset of the `*mut VMMemoryDefinition` for the runtime index
/// provided.
#[inline]
pub fn runtime_memory(&self, index: RuntimeMemoryIndex) -> u32 {
assert!(index.as_u32() < self.num_runtime_memories);
self.runtime_memories() + index.as_u32() * u32::from(self.ptr.size())
}
/// The offset of the base of the `runtime_reallocs` field
#[inline]
pub fn runtime_reallocs(&self) -> u32 {
self.reallocs
}
/// The offset of the `*mut VMCallerCheckedAnyfunc` for the runtime index
/// provided.
#[inline]
pub fn runtime_realloc(&self, index: RuntimeReallocIndex) -> u32 {
assert!(index.as_u32() < self.num_runtime_reallocs);
self.runtime_reallocs() + index.as_u32() * u32::from(self.ptr.size())
}
/// Return the size of the `VMComponentContext` allocation.
#[inline]
pub fn size_of_vmctx(&self) -> u32 {
self.size
}
}