Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/environ/src/fact/signature.rs
1692 views
1
//! Size, align, and flattening information about component model types.
2
3
use crate::component::{
4
ComponentTypesBuilder, InterfaceType, MAX_FLAT_ASYNC_PARAMS, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
5
};
6
use crate::fact::{AdapterOptions, Options};
7
use crate::{WasmValType, prelude::*};
8
use wasm_encoder::ValType;
9
10
use super::LinearMemoryOptions;
11
12
/// Metadata about a core wasm signature which is created for a component model
13
/// signature.
14
#[derive(Debug)]
15
pub struct Signature {
16
/// Core wasm parameters.
17
pub params: Vec<ValType>,
18
/// Core wasm results.
19
pub results: Vec<ValType>,
20
}
21
22
impl ComponentTypesBuilder {
23
/// Calculates the core wasm function signature for the component function
24
/// type specified within `Context`.
25
///
26
/// This is used to generate the core wasm signatures for functions that are
27
/// imported (matching whatever was `canon lift`'d) and functions that are
28
/// exported (matching the generated function from `canon lower`).
29
pub(super) fn signature(&self, options: &AdapterOptions) -> Signature {
30
let f = &self.module_types_builder()[options.options.core_type]
31
.composite_type
32
.inner
33
.unwrap_func();
34
Signature {
35
params: f.params().iter().map(|ty| self.val_type(ty)).collect(),
36
results: f.returns().iter().map(|ty| self.val_type(ty)).collect(),
37
}
38
}
39
40
fn val_type(&self, ty: &WasmValType) -> ValType {
41
match ty {
42
WasmValType::I32 => ValType::I32,
43
WasmValType::I64 => ValType::I64,
44
WasmValType::F32 => ValType::F32,
45
WasmValType::F64 => ValType::F64,
46
WasmValType::V128 => ValType::V128,
47
WasmValType::Ref(_) => todo!("CM+GC"),
48
}
49
}
50
51
/// Generates the signature for a function to be exported by the adapter
52
/// module and called by the host to lift the parameters from the caller and
53
/// lower them to the callee.
54
///
55
/// This allows the host to delay copying the parameters until the callee
56
/// signals readiness by clearing its backpressure flag.
57
///
58
/// Note that this function uses multi-value return to return up to
59
/// `MAX_FLAT_PARAMS` _results_ via the stack, allowing the host to pass
60
/// them directly to the callee with no additional effort.
61
pub(super) fn async_start_signature(
62
&self,
63
lower: &AdapterOptions,
64
lift: &AdapterOptions,
65
) -> Signature {
66
let lower_ty = &self[lower.ty];
67
let lower_ptr_ty = lower.options.data_model.unwrap_memory().ptr();
68
let max_flat_params = if lower.options.async_ {
69
MAX_FLAT_ASYNC_PARAMS
70
} else {
71
MAX_FLAT_PARAMS
72
};
73
let params = match self.flatten_types(
74
&lower.options,
75
max_flat_params,
76
self[lower_ty.params].types.iter().copied(),
77
) {
78
Some(list) => list,
79
None => vec![lower_ptr_ty],
80
};
81
82
let lift_ty = &self[lift.ty];
83
let lift_ptr_ty = lift.options.data_model.unwrap_memory().ptr();
84
let results = match self.flatten_types(
85
&lift.options,
86
// Both sync- and async-lifted functions accept up to this many core
87
// parameters via the stack. The host will call the `async-start`
88
// function (possibly after a backpressure delay), which will
89
// _return_ that many values (using a multi-value return, if
90
// necessary); the host will then pass them directly to the callee.
91
MAX_FLAT_PARAMS,
92
self[lift_ty.params].types.iter().copied(),
93
) {
94
Some(list) => list,
95
None => {
96
vec![lift_ptr_ty]
97
}
98
};
99
100
Signature { params, results }
101
}
102
103
pub(super) fn flatten_lowering_types(
104
&self,
105
options: &Options,
106
tys: impl IntoIterator<Item = InterfaceType>,
107
) -> Option<Vec<ValType>> {
108
// Async functions "use the stack" for zero return values, meaning
109
// nothing is actually passed, but otherwise if anything is returned
110
// it's always through memory.
111
let max = if options.async_ { 0 } else { MAX_FLAT_RESULTS };
112
self.flatten_types(options, max, tys)
113
}
114
115
pub(super) fn flatten_lifting_types(
116
&self,
117
options: &Options,
118
tys: impl IntoIterator<Item = InterfaceType>,
119
) -> Option<Vec<ValType>> {
120
self.flatten_types(
121
options,
122
if options.async_ {
123
// Async functions return results by calling `task.return`,
124
// which accepts up to `MAX_FLAT_PARAMS` parameters via the
125
// stack.
126
MAX_FLAT_PARAMS
127
} else {
128
// Sync functions return results directly (at least until we add
129
// a `always-task-return` canonical option) and so are limited
130
// to returning up to `MAX_FLAT_RESULTS` results via the stack.
131
MAX_FLAT_RESULTS
132
},
133
tys,
134
)
135
}
136
137
/// Generates the signature for a function to be exported by the adapter
138
/// module and called by the host to lift the results from the callee and
139
/// lower them to the caller.
140
///
141
/// Given that async-lifted exports return their results via the
142
/// `task.return` intrinsic, the host will need to copy the results from
143
/// callee to caller when that intrinsic is called rather than when the
144
/// callee task fully completes (which may happen much later).
145
pub(super) fn async_return_signature(
146
&self,
147
lower: &AdapterOptions,
148
lift: &AdapterOptions,
149
) -> Signature {
150
let lift_ty = &self[lift.ty];
151
let lift_ptr_ty = lift.options.data_model.unwrap_memory().ptr();
152
let mut params = match self
153
.flatten_lifting_types(&lift.options, self[lift_ty.results].types.iter().copied())
154
{
155
Some(list) => list,
156
None => {
157
vec![lift_ptr_ty]
158
}
159
};
160
161
let lower_ty = &self[lower.ty];
162
let lower_result_tys = &self[lower_ty.results];
163
let results = if lower.options.async_ {
164
// Add return pointer
165
if !lower_result_tys.types.is_empty() {
166
params.push(lift_ptr_ty);
167
}
168
Vec::new()
169
} else {
170
match self.flatten_types(
171
&lower.options,
172
MAX_FLAT_RESULTS,
173
lower_result_tys.types.iter().copied(),
174
) {
175
Some(list) => list,
176
None => {
177
// Add return pointer
178
params.push(lift_ptr_ty);
179
Vec::new()
180
}
181
}
182
};
183
184
Signature { params, results }
185
}
186
187
/// Pushes the flat version of a list of component types into a final result
188
/// list.
189
pub(super) fn flatten_types(
190
&self,
191
opts: &Options,
192
max: usize,
193
tys: impl IntoIterator<Item = InterfaceType>,
194
) -> Option<Vec<ValType>> {
195
let mut dst = Vec::new();
196
for ty in tys {
197
for ty in opts.flat_types(&ty, self)? {
198
if dst.len() == max {
199
return None;
200
}
201
dst.push((*ty).into());
202
}
203
}
204
Some(dst)
205
}
206
207
pub(super) fn align(&self, opts: &LinearMemoryOptions, ty: &InterfaceType) -> u32 {
208
self.size_align(opts, ty).1
209
}
210
211
/// Returns a (size, align) pair corresponding to the byte-size and
212
/// byte-alignment of the type specified.
213
//
214
// TODO: this is probably inefficient to entire recalculate at all phases,
215
// seems like it would be best to intern this in some sort of map somewhere.
216
pub(super) fn size_align(&self, opts: &LinearMemoryOptions, ty: &InterfaceType) -> (u32, u32) {
217
let abi = self.canonical_abi(ty);
218
if opts.memory64 {
219
(abi.size64, abi.align64)
220
} else {
221
(abi.size32, abi.align32)
222
}
223
}
224
225
/// Tests whether the type signature for `options` contains a borrowed
226
/// resource anywhere.
227
pub(super) fn contains_borrow_resource(&self, options: &AdapterOptions) -> bool {
228
let ty = &self[options.ty];
229
230
// Only parameters need to be checked since results should never have
231
// borrowed resources.
232
debug_assert!(
233
!self[ty.results]
234
.types
235
.iter()
236
.any(|t| self.ty_contains_borrow_resource(t))
237
);
238
self[ty.params]
239
.types
240
.iter()
241
.any(|t| self.ty_contains_borrow_resource(t))
242
}
243
}
244
245