Skip to content

Commit b5d95aa

Browse files
authored
Bake async into component model function types (#2376)
* Bake `async` into component model function types This commit is a breaking change to the component-model-async implementation in this repository. This will effectively invalidate all WASIp3 binaries created prior to this commit. Given that this feature is off-by-default everywhere (right? right?) it's expected that while this will have churn it's the appropriate time to make such a change as this. Concretely the changes here are: * Function types in the component model now reflect whether they're `async`-or-not. This is no longer WIT-level sugar. * Kebab names no longer support `[async]`, `[async method]`, or `[async static]`. * The text format now supports `(func async? (param ...))`. The binary format previously used 0x40 as the prefix byte for "this is a component function type" and now 0x43 is used as "this is an async function type". The `async_` boolean is plumbed throughout component function type locations and is required to be handled by callers. For now there is no subtyping relationship between async and non-async functions, they must be exactly the same when linking. The rationale for this change is going to be expanded on more fully in an upcoming PR to the component-model repository itself. The rough tl;dr; is that to fully support JS in/out of components the component model is going to need to require traps in some dynamic situations such as when a synchronous function-type function tries to block. This is required to respect JS's "cannot block the main thread" semantics, for example. This will be more fully explain in the component-model PR and this PR is not intended to serve as justification alone for this change. Instead this PR is intended to change/update Wasmtime as quickly as possible to understand the new binary format so implementation work can proceed as most of the work here is on the runtime side of things, not validation. One minor caveat for this change is that the `wit-dylib` crate has some tests which exercise async functionality. Those are now all disabled or set as "expect this to fail" because Wasmtime, the runner for these tests, no longer understands the binaries it's ingesting. * Delete outdated snapshot * Propagate async-ness in wasm-compose too * Add `async` to the function type hash key
1 parent e2bb0a2 commit b5d95aa

File tree

47 files changed

+280
-411
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+280
-411
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/wasm-compose/src/encoding.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,7 +482,7 @@ impl<'a> TypeEncoder<'a> {
482482
let index = state.cur.encodable.type_count();
483483
let mut f = state.cur.encodable.ty().function();
484484

485-
f.params(params).result(result);
485+
f.async_(ty.async_).params(params).result(result);
486486

487487
index
488488
}

crates/wasm-encoder/src/component/types.rs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,21 +353,44 @@ impl Encode for InstanceType {
353353
/// Used to encode component function types.
354354
#[derive(Debug)]
355355
pub struct ComponentFuncTypeEncoder<'a> {
356+
async_encoded: bool,
356357
params_encoded: bool,
357358
results_encoded: bool,
358359
sink: &'a mut Vec<u8>,
359360
}
360361

361362
impl<'a> ComponentFuncTypeEncoder<'a> {
362363
fn new(sink: &'a mut Vec<u8>) -> Self {
363-
sink.push(0x40);
364364
Self {
365+
async_encoded: false,
365366
params_encoded: false,
366367
results_encoded: false,
367368
sink,
368369
}
369370
}
370371

372+
/// Indicates whether this is an `async` function or not.
373+
///
374+
/// If this function is not invoked then the function type will not be
375+
/// `async`.
376+
///
377+
/// # Panics
378+
///
379+
/// This method will panic if parameters or results have already been
380+
/// encoded.
381+
pub fn async_(&mut self, is_async: bool) -> &mut Self {
382+
assert!(!self.params_encoded);
383+
assert!(!self.results_encoded);
384+
assert!(!self.async_encoded);
385+
self.async_encoded = true;
386+
if is_async {
387+
self.sink.push(0x43);
388+
} else {
389+
self.sink.push(0x40);
390+
}
391+
self
392+
}
393+
371394
/// Defines named parameters.
372395
///
373396
/// Parameters must be defined before defining results.
@@ -383,6 +406,9 @@ impl<'a> ComponentFuncTypeEncoder<'a> {
383406
T: Into<ComponentValType>,
384407
{
385408
assert!(!self.params_encoded);
409+
if !self.async_encoded {
410+
self.async_(false);
411+
}
386412
self.params_encoded = true;
387413
let params = params.into_iter();
388414
params.len().encode(self.sink);
@@ -402,6 +428,7 @@ impl<'a> ComponentFuncTypeEncoder<'a> {
402428
/// This method will panic if the function is called twice, called before
403429
/// the `params` method, or called in addition to the `results` method.
404430
pub fn result(&mut self, ty: Option<ComponentValType>) -> &mut Self {
431+
assert!(self.async_encoded);
405432
assert!(self.params_encoded);
406433
assert!(!self.results_encoded);
407434
self.results_encoded = true;

crates/wasm-encoder/src/reencode/component.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,7 @@ pub mod component_utils {
731731
mut func: crate::ComponentFuncTypeEncoder<'_>,
732732
ty: wasmparser::ComponentFuncType<'_>,
733733
) -> Result<(), Error<T::Error>> {
734+
func.async_(ty.async_);
734735
func.params(
735736
Vec::from(ty.params)
736737
.into_iter()

crates/wasmparser/src/readers/component/types.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,12 +282,16 @@ impl<'a> FromReader<'a> for ComponentType<'a> {
282282
b => return reader.invalid_leading_byte(b, "resource destructor"),
283283
},
284284
},
285-
0x40 => {
285+
byte @ (0x40 | 0x43) => {
286286
let params = reader
287287
.read_iter(MAX_WASM_FUNCTION_PARAMS, "component function parameters")?
288288
.collect::<Result<_>>()?;
289289
let result = read_resultlist(reader)?;
290-
ComponentType::Func(ComponentFuncType { params, result })
290+
ComponentType::Func(ComponentFuncType {
291+
async_: byte == 0x43,
292+
params,
293+
result,
294+
})
291295
}
292296
0x41 => ComponentType::Component(
293297
reader
@@ -387,6 +391,8 @@ impl<'a> FromReader<'a> for InstanceTypeDeclaration<'a> {
387391
/// Represents a type of a function in a WebAssembly component.
388392
#[derive(Debug, Clone, Eq, PartialEq)]
389393
pub struct ComponentFuncType<'a> {
394+
/// Whether or not this is an async function.
395+
pub async_: bool,
390396
/// The function parameters.
391397
pub params: Box<[(&'a str, ComponentValType)]>,
392398
/// The function result.

crates/wasmparser/src/validator/component.rs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1447,6 +1447,7 @@ impl ComponentState {
14471447
}
14481448

14491449
let func_ty = ComponentFuncType {
1450+
async_: false,
14501451
info: TypeInfo::new(),
14511452
params: result
14521453
.iter()
@@ -3051,6 +3052,13 @@ impl ComponentState {
30513052
) -> Result<ComponentFuncType> {
30523053
let mut info = TypeInfo::new();
30533054

3055+
if ty.async_ && !self.features.cm_async() {
3056+
bail!(
3057+
offset,
3058+
"async component functions require the component model async feature"
3059+
);
3060+
}
3061+
30543062
let mut set = Set::default();
30553063
set.reserve(core::cmp::max(
30563064
ty.params.len(),
@@ -3092,6 +3100,7 @@ impl ComponentState {
30923100
.transpose()?;
30933101

30943102
Ok(ComponentFuncType {
3103+
async_: ty.async_,
30953104
info,
30963105
params,
30973106
result,
@@ -4534,11 +4543,8 @@ impl ComponentNameContext {
45344543
if let ExternKind::Export = kind {
45354544
match kebab.kind() {
45364545
ComponentNameKind::Label(_)
4537-
| ComponentNameKind::AsyncLabel(_)
45384546
| ComponentNameKind::Method(_)
4539-
| ComponentNameKind::AsyncMethod(_)
45404547
| ComponentNameKind::Static(_)
4541-
| ComponentNameKind::AsyncStatic(_)
45424548
| ComponentNameKind::Constructor(_)
45434549
| ComponentNameKind::Interface(_) => {}
45444550

@@ -4552,7 +4558,7 @@ impl ComponentNameContext {
45524558

45534559
// Validate that the kebab name, if it has structure such as
45544560
// `[method]a.b`, is indeed valid with respect to known resources.
4555-
self.validate(&kebab, ty, types, offset, features)
4561+
self.validate(&kebab, ty, types, offset)
45564562
.with_context(|| format!("{} name `{kebab}` is not valid", kind.desc()))?;
45574563

45584564
// Top-level kebab-names must all be unique, even between both imports
@@ -4593,7 +4599,6 @@ impl ComponentNameContext {
45934599
ty: &ComponentEntityType,
45944600
types: &TypeAlloc,
45954601
offset: usize,
4596-
features: &WasmFeatures,
45974602
) -> Result<()> {
45984603
let func = || {
45994604
let id = match ty {
@@ -4602,24 +4607,10 @@ impl ComponentNameContext {
46024607
};
46034608
Ok(&types[id])
46044609
};
4605-
match name.kind() {
4606-
ComponentNameKind::AsyncLabel(_)
4607-
| ComponentNameKind::AsyncMethod(_)
4608-
| ComponentNameKind::AsyncStatic(_) => {
4609-
if !features.cm_async() {
4610-
bail!(
4611-
offset,
4612-
"async kebab-names require the component model async feature"
4613-
);
4614-
}
4615-
}
4616-
_ => {}
4617-
}
46184610

46194611
match name.kind() {
46204612
// No validation necessary for these styles of names
46214613
ComponentNameKind::Label(_)
4622-
| ComponentNameKind::AsyncLabel(_)
46234614
| ComponentNameKind::Interface(_)
46244615
| ComponentNameKind::Url(_)
46254616
| ComponentNameKind::Dependency(_)
@@ -4630,6 +4621,9 @@ impl ComponentNameContext {
46304621
// within this context to match `rname`.
46314622
ComponentNameKind::Constructor(rname) => {
46324623
let ty = func()?;
4624+
if ty.async_ {
4625+
bail!(offset, "constructor function cannot be async");
4626+
}
46334627
let ty = match ty.result {
46344628
Some(result) => result,
46354629
None => bail!(offset, "function should return one value"),
@@ -4661,7 +4655,7 @@ impl ComponentNameContext {
46614655
// Methods must take `(param "self" (borrow $resource))` as the
46624656
// first argument where `$resources` matches the name `resource` as
46634657
// named in this context.
4664-
ComponentNameKind::Method(name) | ComponentNameKind::AsyncMethod(name) => {
4658+
ComponentNameKind::Method(name) => {
46654659
let ty = func()?;
46664660
if ty.params.len() == 0 {
46674661
bail!(offset, "function should have at least one argument");
@@ -4693,7 +4687,7 @@ impl ComponentNameContext {
46934687
// Static methods don't have much validation beyond that they must
46944688
// be a function and the resource name referred to must already be
46954689
// in this context.
4696-
ComponentNameKind::Static(name) | ComponentNameKind::AsyncStatic(name) => {
4690+
ComponentNameKind::Static(name) => {
46974691
func()?;
46984692
if !self.all_resource_names.contains(name.resource().as_str()) {
46994693
bail!(offset, "static resource name is not known in this context");

crates/wasmparser/src/validator/component_types.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1086,6 +1086,8 @@ impl TypeData for ComponentInstanceType {
10861086
pub struct ComponentFuncType {
10871087
/// Metadata about this function type.
10881088
pub(crate) info: TypeInfo,
1089+
/// Whether or not this is an async function.
1090+
pub async_: bool,
10891091
/// The function parameters.
10901092
pub params: Box<[(KebabString, ComponentValType)]>,
10911093
/// The function's result.
@@ -3237,6 +3239,15 @@ impl<'a> SubtypeCx<'a> {
32373239
let a = &self.a[a];
32383240
let b = &self.b[b];
32393241

3242+
if a.async_ != b.async_ {
3243+
let a_desc = if a.async_ { "async" } else { "sync" };
3244+
let b_desc = if b.async_ { "async" } else { "sync" };
3245+
bail!(
3246+
offset,
3247+
"expected {a_desc} function, found {b_desc} function",
3248+
);
3249+
}
3250+
32403251
// Note that this intentionally diverges from the upstream
32413252
// specification in terms of subtyping. This is a full
32423253
// type-equality check which ensures that the structure of `a`

0 commit comments

Comments
 (0)