Path: blob/main/crates/environ/src/fact/signature.rs
1692 views
//! Size, align, and flattening information about component model types.12use crate::component::{3ComponentTypesBuilder, InterfaceType, MAX_FLAT_ASYNC_PARAMS, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,4};5use crate::fact::{AdapterOptions, Options};6use crate::{WasmValType, prelude::*};7use wasm_encoder::ValType;89use super::LinearMemoryOptions;1011/// Metadata about a core wasm signature which is created for a component model12/// signature.13#[derive(Debug)]14pub struct Signature {15/// Core wasm parameters.16pub params: Vec<ValType>,17/// Core wasm results.18pub results: Vec<ValType>,19}2021impl ComponentTypesBuilder {22/// Calculates the core wasm function signature for the component function23/// type specified within `Context`.24///25/// This is used to generate the core wasm signatures for functions that are26/// imported (matching whatever was `canon lift`'d) and functions that are27/// exported (matching the generated function from `canon lower`).28pub(super) fn signature(&self, options: &AdapterOptions) -> Signature {29let f = &self.module_types_builder()[options.options.core_type]30.composite_type31.inner32.unwrap_func();33Signature {34params: f.params().iter().map(|ty| self.val_type(ty)).collect(),35results: f.returns().iter().map(|ty| self.val_type(ty)).collect(),36}37}3839fn val_type(&self, ty: &WasmValType) -> ValType {40match ty {41WasmValType::I32 => ValType::I32,42WasmValType::I64 => ValType::I64,43WasmValType::F32 => ValType::F32,44WasmValType::F64 => ValType::F64,45WasmValType::V128 => ValType::V128,46WasmValType::Ref(_) => todo!("CM+GC"),47}48}4950/// Generates the signature for a function to be exported by the adapter51/// module and called by the host to lift the parameters from the caller and52/// lower them to the callee.53///54/// This allows the host to delay copying the parameters until the callee55/// signals readiness by clearing its backpressure flag.56///57/// Note that this function uses multi-value return to return up to58/// `MAX_FLAT_PARAMS` _results_ via the stack, allowing the host to pass59/// them directly to the callee with no additional effort.60pub(super) fn async_start_signature(61&self,62lower: &AdapterOptions,63lift: &AdapterOptions,64) -> Signature {65let lower_ty = &self[lower.ty];66let lower_ptr_ty = lower.options.data_model.unwrap_memory().ptr();67let max_flat_params = if lower.options.async_ {68MAX_FLAT_ASYNC_PARAMS69} else {70MAX_FLAT_PARAMS71};72let params = match self.flatten_types(73&lower.options,74max_flat_params,75self[lower_ty.params].types.iter().copied(),76) {77Some(list) => list,78None => vec![lower_ptr_ty],79};8081let lift_ty = &self[lift.ty];82let lift_ptr_ty = lift.options.data_model.unwrap_memory().ptr();83let results = match self.flatten_types(84&lift.options,85// Both sync- and async-lifted functions accept up to this many core86// parameters via the stack. The host will call the `async-start`87// function (possibly after a backpressure delay), which will88// _return_ that many values (using a multi-value return, if89// necessary); the host will then pass them directly to the callee.90MAX_FLAT_PARAMS,91self[lift_ty.params].types.iter().copied(),92) {93Some(list) => list,94None => {95vec![lift_ptr_ty]96}97};9899Signature { params, results }100}101102pub(super) fn flatten_lowering_types(103&self,104options: &Options,105tys: impl IntoIterator<Item = InterfaceType>,106) -> Option<Vec<ValType>> {107// Async functions "use the stack" for zero return values, meaning108// nothing is actually passed, but otherwise if anything is returned109// it's always through memory.110let max = if options.async_ { 0 } else { MAX_FLAT_RESULTS };111self.flatten_types(options, max, tys)112}113114pub(super) fn flatten_lifting_types(115&self,116options: &Options,117tys: impl IntoIterator<Item = InterfaceType>,118) -> Option<Vec<ValType>> {119self.flatten_types(120options,121if options.async_ {122// Async functions return results by calling `task.return`,123// which accepts up to `MAX_FLAT_PARAMS` parameters via the124// stack.125MAX_FLAT_PARAMS126} else {127// Sync functions return results directly (at least until we add128// a `always-task-return` canonical option) and so are limited129// to returning up to `MAX_FLAT_RESULTS` results via the stack.130MAX_FLAT_RESULTS131},132tys,133)134}135136/// Generates the signature for a function to be exported by the adapter137/// module and called by the host to lift the results from the callee and138/// lower them to the caller.139///140/// Given that async-lifted exports return their results via the141/// `task.return` intrinsic, the host will need to copy the results from142/// callee to caller when that intrinsic is called rather than when the143/// callee task fully completes (which may happen much later).144pub(super) fn async_return_signature(145&self,146lower: &AdapterOptions,147lift: &AdapterOptions,148) -> Signature {149let lift_ty = &self[lift.ty];150let lift_ptr_ty = lift.options.data_model.unwrap_memory().ptr();151let mut params = match self152.flatten_lifting_types(&lift.options, self[lift_ty.results].types.iter().copied())153{154Some(list) => list,155None => {156vec![lift_ptr_ty]157}158};159160let lower_ty = &self[lower.ty];161let lower_result_tys = &self[lower_ty.results];162let results = if lower.options.async_ {163// Add return pointer164if !lower_result_tys.types.is_empty() {165params.push(lift_ptr_ty);166}167Vec::new()168} else {169match self.flatten_types(170&lower.options,171MAX_FLAT_RESULTS,172lower_result_tys.types.iter().copied(),173) {174Some(list) => list,175None => {176// Add return pointer177params.push(lift_ptr_ty);178Vec::new()179}180}181};182183Signature { params, results }184}185186/// Pushes the flat version of a list of component types into a final result187/// list.188pub(super) fn flatten_types(189&self,190opts: &Options,191max: usize,192tys: impl IntoIterator<Item = InterfaceType>,193) -> Option<Vec<ValType>> {194let mut dst = Vec::new();195for ty in tys {196for ty in opts.flat_types(&ty, self)? {197if dst.len() == max {198return None;199}200dst.push((*ty).into());201}202}203Some(dst)204}205206pub(super) fn align(&self, opts: &LinearMemoryOptions, ty: &InterfaceType) -> u32 {207self.size_align(opts, ty).1208}209210/// Returns a (size, align) pair corresponding to the byte-size and211/// byte-alignment of the type specified.212//213// TODO: this is probably inefficient to entire recalculate at all phases,214// seems like it would be best to intern this in some sort of map somewhere.215pub(super) fn size_align(&self, opts: &LinearMemoryOptions, ty: &InterfaceType) -> (u32, u32) {216let abi = self.canonical_abi(ty);217if opts.memory64 {218(abi.size64, abi.align64)219} else {220(abi.size32, abi.align32)221}222}223224/// Tests whether the type signature for `options` contains a borrowed225/// resource anywhere.226pub(super) fn contains_borrow_resource(&self, options: &AdapterOptions) -> bool {227let ty = &self[options.ty];228229// Only parameters need to be checked since results should never have230// borrowed resources.231debug_assert!(232!self[ty.results]233.types234.iter()235.any(|t| self.ty_contains_borrow_resource(t))236);237self[ty.params]238.types239.iter()240.any(|t| self.ty_contains_borrow_resource(t))241}242}243244245