Skip to content

Commit

Permalink
Implement component-to-component calls with resources (#6769)
Browse files Browse the repository at this point in the history
* Implement component-to-component calls with resources

This fills out support in FACT in Wasmtime to support
component-to-component calls that use resources. This ended up being
relatively simple as it's "just" a matter of moving resources between
tables which at this time bottoms out in calls to the host. These new
trampolines are are relatively easy to add after #6751 which helps keep
this change contained.

Closes #6696

* Review comments
  • Loading branch information
alexcrichton authored Jul 25, 2023
1 parent 7dfae4c commit 71d0418
Show file tree
Hide file tree
Showing 14 changed files with 505 additions and 29 deletions.
47 changes: 47 additions & 0 deletions crates/cranelift/src/compiler/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,18 @@ impl<'a> TrampolineCompiler<'a> {
Trampoline::ResourceNew(ty) => self.translate_resource_new(*ty),
Trampoline::ResourceRep(ty) => self.translate_resource_rep(*ty),
Trampoline::ResourceDrop(ty) => self.translate_resource_drop(*ty),
Trampoline::ResourceTransferOwn => {
self.translate_resource_libcall(host::resource_transfer_own)
}
Trampoline::ResourceTransferBorrow => {
self.translate_resource_libcall(host::resource_transfer_borrow)
}
Trampoline::ResourceEnterCall => {
self.translate_resource_libcall(host::resource_enter_call)
}
Trampoline::ResourceExitCall => {
self.translate_resource_libcall(host::resource_exit_call)
}
}
}

Expand Down Expand Up @@ -496,6 +508,41 @@ impl<'a> TrampolineCompiler<'a> {
self.builder.seal_block(return_block);
}

/// Invokes a host libcall and returns the result.
///
/// Only intended for simple trampolines and effectively acts as a bridge
/// from the wasm abi to host.
fn translate_resource_libcall(
&mut self,
get_libcall: fn(&dyn TargetIsa, &mut ir::Function) -> (ir::SigRef, u32),
) {
match self.abi {
Abi::Wasm => {}

// These trampolines can only actually be called by Wasm, so
// let's assert that here.
Abi::Native | Abi::Array => {
self.builder
.ins()
.trap(ir::TrapCode::User(crate::DEBUG_ASSERT_TRAP_CODE));
return;
}
}

let args = self.builder.func.dfg.block_params(self.block0).to_vec();
let vmctx = args[0];
let mut host_args = vec![vmctx];
host_args.extend(args[2..].iter().copied());
let (host_sig, offset) = get_libcall(self.isa, &mut self.builder.func);
let host_fn = self.load_libcall(vmctx, offset);
let call = self
.builder
.ins()
.call_indirect(host_sig, host_fn, &host_args);
let results = self.builder.func.dfg.inst_results(call).to_vec();
self.builder.ins().return_(&results);
}

/// Loads a host function pointer for a libcall stored at the `offset`
/// provided in the libcalls array.
///
Expand Down
5 changes: 5 additions & 0 deletions crates/environ/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ macro_rules! foreach_builtin_component_function {
// is encoded as a 64-bit integer where the low bit is Some/None
// and bits 1-33 are the payload.
resource_drop(vmctx: vmctx, resource: u32, idx: u32) -> u64;

resource_transfer_own(vmctx: vmctx, src_idx: u32, src_table: u32, dst_table: u32) -> u32;
resource_transfer_borrow(vmctx: vmctx, src_idx: u32, src_table: u32, dst_table: u32) -> u32;
resource_enter_call(vmctx: vmctx);
resource_exit_call(vmctx: vmctx);
}
};
}
11 changes: 10 additions & 1 deletion crates/environ/src/component/dfg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ pub struct ComponentDfg {

/// All trampolines and their type signature which will need to get
/// compiled by Cranelift.
pub trampolines: PrimaryMap<TrampolineIndex, (SignatureIndex, Trampoline)>,
pub trampolines: Intern<TrampolineIndex, (SignatureIndex, Trampoline)>,

/// Know reallocation functions which are used by `lowerings` (e.g. will be
/// used by the host)
Expand Down Expand Up @@ -238,6 +238,7 @@ impl<T> CoreExport<T> {
}

/// Same as `info::Trampoline`
#[derive(Clone, PartialEq, Eq, Hash)]
#[allow(missing_docs)]
pub enum Trampoline {
LowerImport {
Expand All @@ -256,6 +257,10 @@ pub enum Trampoline {
ResourceNew(TypeResourceTableIndex),
ResourceRep(TypeResourceTableIndex),
ResourceDrop(TypeResourceTableIndex),
ResourceTransferOwn,
ResourceTransferBorrow,
ResourceEnterCall,
ResourceExitCall,
}

/// Same as `info::CanonicalOptions`
Expand Down Expand Up @@ -581,6 +586,10 @@ impl LinearizeDfg<'_> {
Trampoline::ResourceNew(ty) => info::Trampoline::ResourceNew(*ty),
Trampoline::ResourceDrop(ty) => info::Trampoline::ResourceDrop(*ty),
Trampoline::ResourceRep(ty) => info::Trampoline::ResourceRep(*ty),
Trampoline::ResourceTransferOwn => info::Trampoline::ResourceTransferOwn,
Trampoline::ResourceTransferBorrow => info::Trampoline::ResourceTransferBorrow,
Trampoline::ResourceEnterCall => info::Trampoline::ResourceEnterCall,
Trampoline::ResourceExitCall => info::Trampoline::ResourceExitCall,
};
let i1 = self.trampolines.push(*signature);
let i2 = self.trampoline_defs.push(trampoline);
Expand Down
22 changes: 22 additions & 0 deletions crates/environ/src/component/info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,24 @@ pub enum Trampoline {

/// Same as `ResourceNew`, but for the `resource.drop` intrinsic.
ResourceDrop(TypeResourceTableIndex),

/// An intrinsic used by FACT-generated modules which will transfer an owned
/// resource from one table to another. Used in component-to-component
/// adapter trampolines.
ResourceTransferOwn,

/// Same as `ResourceTransferOwn` but for borrows.
ResourceTransferBorrow,

/// An intrinsic used by FACT-generated modules which indicates that a call
/// is being entered and resource-related metadata needs to be configured.
///
/// Note that this is currently only invoked when borrowed resources are
/// detected, otherwise this is "optimized out".
ResourceEnterCall,

/// Same as `ResourceEnterCall` except for when exiting a call.
ResourceExitCall,
}

impl Trampoline {
Expand All @@ -556,6 +574,10 @@ impl Trampoline {
ResourceNew(i) => format!("component-resource-new[{}]", i.as_u32()),
ResourceRep(i) => format!("component-resource-rep[{}]", i.as_u32()),
ResourceDrop(i) => format!("component-resource-drop[{}]", i.as_u32()),
ResourceTransferOwn => format!("component-resource-transfer-own"),
ResourceTransferBorrow => format!("component-resource-transfer-borrow"),
ResourceEnterCall => format!("component-resource-enter-call"),
ResourceExitCall => format!("component-resource-exit-call"),
}
}
}
16 changes: 12 additions & 4 deletions crates/environ/src/component/translate/adapt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,11 @@ fn fact_import_to_core_def(
import: &fact::Import,
ty: EntityType,
) -> dfg::CoreDef {
let mut simple_intrinsic = |trampoline: dfg::Trampoline| {
let signature = ty.unwrap_func();
let index = dfg.trampolines.push((signature, trampoline));
dfg::CoreDef::Trampoline(index)
};
match import {
fact::Import::CoreDef(def) => def.clone(),
fact::Import::Transcode {
Expand All @@ -278,10 +283,7 @@ fn fact_import_to_core_def(

let from = dfg.memories.push(unwrap_memory(from));
let to = dfg.memories.push(unwrap_memory(to));
let signature = match ty {
EntityType::Function(signature) => signature,
_ => unreachable!(),
};
let signature = ty.unwrap_func();
let index = dfg.trampolines.push((
signature,
dfg::Trampoline::Transcoder {
Expand All @@ -294,6 +296,12 @@ fn fact_import_to_core_def(
));
dfg::CoreDef::Trampoline(index)
}
fact::Import::ResourceTransferOwn => simple_intrinsic(dfg::Trampoline::ResourceTransferOwn),
fact::Import::ResourceTransferBorrow => {
simple_intrinsic(dfg::Trampoline::ResourceTransferBorrow)
}
fact::Import::ResourceEnterCall => simple_intrinsic(dfg::Trampoline::ResourceEnterCall),
fact::Import::ResourceExitCall => simple_intrinsic(dfg::Trampoline::ResourceExitCall),
}
}

Expand Down
22 changes: 20 additions & 2 deletions crates/environ/src/component/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,12 @@ impl ComponentTypesBuilder {
self.type_information(ty).flat.as_flat_types()
}

/// Returns whether the type specified contains any borrowed resources
/// within it.
pub fn ty_contains_borrow_resource(&self, ty: &InterfaceType) -> bool {
self.type_information(ty).has_borrow
}

fn type_information(&self, ty: &InterfaceType) -> &TypeInformation {
match ty {
InterfaceType::U8
Expand All @@ -877,11 +883,18 @@ impl ComponentTypesBuilder {
| InterfaceType::U32
| InterfaceType::S32
| InterfaceType::Char
| InterfaceType::Own(_)
| InterfaceType::Borrow(_) => {
| InterfaceType::Own(_) => {
static INFO: TypeInformation = TypeInformation::primitive(FlatType::I32);
&INFO
}
InterfaceType::Borrow(_) => {
static INFO: TypeInformation = {
let mut info = TypeInformation::primitive(FlatType::I32);
info.has_borrow = true;
info
};
&INFO
}
InterfaceType::U64 | InterfaceType::S64 => {
static INFO: TypeInformation = TypeInformation::primitive(FlatType::I64);
&INFO
Expand Down Expand Up @@ -1711,13 +1724,15 @@ struct TypeInformationCache {
struct TypeInformation {
depth: u32,
flat: FlatTypesStorage,
has_borrow: bool,
}

impl TypeInformation {
const fn new() -> TypeInformation {
TypeInformation {
depth: 0,
flat: FlatTypesStorage::new(),
has_borrow: false,
}
}

Expand Down Expand Up @@ -1747,6 +1762,7 @@ impl TypeInformation {
self.depth = 1;
for info in types {
self.depth = self.depth.max(1 + info.depth);
self.has_borrow = self.has_borrow || info.has_borrow;
match info.flat.as_flat_types() {
Some(types) => {
for (t32, t64) in types.memory32.iter().zip(types.memory64) {
Expand Down Expand Up @@ -1789,6 +1805,7 @@ impl TypeInformation {
None => continue,
};
self.depth = self.depth.max(1 + info.depth);
self.has_borrow = self.has_borrow || info.has_borrow;

// If this variant is already unrepresentable in a flat
// representation then this can be skipped.
Expand Down Expand Up @@ -1898,5 +1915,6 @@ impl TypeInformation {
*self = TypeInformation::string();
let info = types.type_information(&ty.element);
self.depth += info.depth;
self.has_borrow = info.has_borrow;
}
}
85 changes: 85 additions & 0 deletions crates/environ/src/fact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,12 @@ pub struct Module<'a> {
/// Intern'd transcoders and what index they were assigned.
imported_transcoders: HashMap<Transcoder, FuncIndex>,

/// Cached versions of imported trampolines for working with resources.
imported_resource_transfer_own: Option<FuncIndex>,
imported_resource_transfer_borrow: Option<FuncIndex>,
imported_resource_enter_call: Option<FuncIndex>,
imported_resource_exit_call: Option<FuncIndex>,

// Current status of index spaces from the imports generated so far.
imported_funcs: PrimaryMap<FuncIndex, Option<CoreDef>>,
imported_memories: PrimaryMap<MemoryIndex, CoreDef>,
Expand Down Expand Up @@ -178,6 +184,10 @@ impl<'a> Module<'a> {
funcs: PrimaryMap::new(),
helper_funcs: HashMap::new(),
helper_worklist: Vec::new(),
imported_resource_transfer_own: None,
imported_resource_transfer_borrow: None,
imported_resource_enter_call: None,
imported_resource_exit_call: None,
}
}

Expand Down Expand Up @@ -359,6 +369,72 @@ impl<'a> Module<'a> {
})
}

fn import_simple(
&mut self,
module: &str,
name: &str,
params: &[ValType],
results: &[ValType],
import: Import,
get: impl Fn(&mut Self) -> &mut Option<FuncIndex>,
) -> FuncIndex {
if let Some(idx) = get(self) {
return *idx;
}
let ty = self.core_types.function(params, results);
let ty = EntityType::Function(ty);
self.core_imports.import(module, name, ty);

self.imports.push(import);
let idx = self.imported_funcs.push(None);
*get(self) = Some(idx);
idx
}

fn import_resource_transfer_own(&mut self) -> FuncIndex {
self.import_simple(
"resource",
"transfer-own",
&[ValType::I32, ValType::I32, ValType::I32],
&[ValType::I32],
Import::ResourceTransferOwn,
|me| &mut me.imported_resource_transfer_own,
)
}

fn import_resource_transfer_borrow(&mut self) -> FuncIndex {
self.import_simple(
"resource",
"transfer-borrow",
&[ValType::I32, ValType::I32, ValType::I32],
&[ValType::I32],
Import::ResourceTransferBorrow,
|me| &mut me.imported_resource_transfer_borrow,
)
}

fn import_resource_enter_call(&mut self) -> FuncIndex {
self.import_simple(
"resource",
"enter-call",
&[],
&[],
Import::ResourceEnterCall,
|me| &mut me.imported_resource_enter_call,
)
}

fn import_resource_exit_call(&mut self) -> FuncIndex {
self.import_simple(
"resource",
"exit-call",
&[],
&[],
Import::ResourceExitCall,
|me| &mut me.imported_resource_exit_call,
)
}

fn translate_helper(&mut self, helper: Helper) -> FunctionId {
*self.helper_funcs.entry(helper).or_insert_with(|| {
// Generate a fresh `Function` with a unique id for what we're about to
Expand Down Expand Up @@ -470,6 +546,15 @@ pub enum Import {
/// Whether or not `to` is a 64-bit memory
to64: bool,
},
/// Transfers an owned resource from one table to another.
ResourceTransferOwn,
/// Transfers a borrowed resource from one table to another.
ResourceTransferBorrow,
/// Sets up entry metadata for a borrow resources when a call starts.
ResourceEnterCall,
/// Tears down a previous entry and handles checking borrow-related
/// metadata.
ResourceExitCall,
}

impl Options {
Expand Down
17 changes: 17 additions & 0 deletions crates/environ/src/fact/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,4 +115,21 @@ impl ComponentTypesBuilder {
(abi.size32, abi.align32)
}
}

/// Tests whether the type signature for `options` contains a borrowed
/// resource anywhere.
pub(super) fn contains_borrow_resource(&self, options: &AdapterOptions) -> bool {
let ty = &self[options.ty];

// Only parameters need to be checked since results should never have
// borrowed resources.
debug_assert!(!self[ty.results]
.types
.iter()
.any(|t| self.ty_contains_borrow_resource(t)));
self[ty.params]
.types
.iter()
.any(|t| self.ty_contains_borrow_resource(t))
}
}
Loading

0 comments on commit 71d0418

Please sign in to comment.