Skip to content

Commit 015b271

Browse files
committed
Add support for async ABI, futures, streams, and errors
This adds support for encoding and parsing components which use the [Async ABI](https://github.com/WebAssembly/component-model/blob/main/design/mvp/Async.md) and associated canonical options and functions, along with the [`stream`, `future`, and `error`](WebAssembly/component-model#405) types. Note that the `error` type was recently (about 30 minutes ago) renamed to `error-context` in Luke's spec PR. I haven't updated this implementation to reflect that yet, but will do so in a follow-up commit. That should allow us to avoid conflicts with existing WIT files that use `error` as a type and/or interface name. This does not include any new tests; I'll also add those in a follow-up commit. See bytecodealliance/rfcs#38 for more context. Signed-off-by: Joel Dice <[email protected]>
1 parent 196a6be commit 015b271

Some content is hidden

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

43 files changed

+3718
-298
lines changed

crates/wasm-compose/src/composer.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,6 @@ impl<'a> CompositionGraphBuilder<'a> {
497497
}
498498
}
499499

500-
self.graph.unify_imported_resources();
501-
502500
Ok((self.instances[root_instance], self.graph))
503501
}
504502
}

crates/wasm-compose/src/encoding.rs

Lines changed: 58 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,17 @@ impl<'a> TypeEncoder<'a> {
596596
return ret;
597597
}
598598

599+
if let Some((instance, name)) = state.cur.instance_exports.get(&key) {
600+
let ret = state.cur.encodable.type_count();
601+
state.cur.encodable.alias(Alias::InstanceExport {
602+
instance: *instance,
603+
name,
604+
kind: ComponentExportKind::Type,
605+
});
606+
log::trace!("id defined in current instance");
607+
return ret;
608+
}
609+
599610
match id.peel_alias(&self.0.types) {
600611
Some(next) => id = next,
601612
// If there's no more aliases then fall through to the
@@ -608,15 +619,17 @@ impl<'a> TypeEncoder<'a> {
608619
return match id {
609620
AnyTypeId::Core(ComponentCoreTypeId::Sub(_)) => unreachable!(),
610621
AnyTypeId::Core(ComponentCoreTypeId::Module(id)) => self.module_type(state, id),
611-
AnyTypeId::Component(id) => match id {
612-
ComponentAnyTypeId::Resource(_) => {
613-
unreachable!("should have been handled in `TypeEncoder::component_entity_type`")
622+
AnyTypeId::Component(id) => {
623+
match id {
624+
ComponentAnyTypeId::Resource(r) => {
625+
unreachable!("should have been handled in `TypeEncoder::component_entity_type`: {r:?}")
626+
}
627+
ComponentAnyTypeId::Defined(id) => self.defined_type(state, id),
628+
ComponentAnyTypeId::Func(id) => self.component_func_type(state, id),
629+
ComponentAnyTypeId::Instance(id) => self.component_instance_type(state, id),
630+
ComponentAnyTypeId::Component(id) => self.component_type(state, id),
614631
}
615-
ComponentAnyTypeId::Defined(id) => self.defined_type(state, id),
616-
ComponentAnyTypeId::Func(id) => self.component_func_type(state, id),
617-
ComponentAnyTypeId::Instance(id) => self.component_instance_type(state, id),
618-
ComponentAnyTypeId::Component(id) => self.component_type(state, id),
619-
},
632+
}
620633
};
621634
}
622635

@@ -667,6 +680,9 @@ impl<'a> TypeEncoder<'a> {
667680
state.cur.encodable.ty().defined_type().borrow(ty);
668681
index
669682
}
683+
ComponentDefinedType::Future(ty) => self.future(state, *ty),
684+
ComponentDefinedType::Stream(ty) => self.stream(state, *ty),
685+
ComponentDefinedType::Error => self.error(state),
670686
}
671687
}
672688

@@ -788,6 +804,28 @@ impl<'a> TypeEncoder<'a> {
788804
}
789805
export
790806
}
807+
808+
fn future(&self, state: &mut TypeState<'a>, ty: Option<ct::ComponentValType>) -> u32 {
809+
let ty = ty.map(|ty| self.component_val_type(state, ty));
810+
811+
let index = state.cur.encodable.type_count();
812+
state.cur.encodable.ty().defined_type().future(ty);
813+
index
814+
}
815+
816+
fn stream(&self, state: &mut TypeState<'a>, ty: ct::ComponentValType) -> u32 {
817+
let ty = self.component_val_type(state, ty);
818+
819+
let index = state.cur.encodable.type_count();
820+
state.cur.encodable.ty().defined_type().stream(ty);
821+
index
822+
}
823+
824+
fn error(&self, state: &mut TypeState<'a>) -> u32 {
825+
let index = state.cur.encodable.type_count();
826+
state.cur.encodable.ty().defined_type().error();
827+
index
828+
}
791829
}
792830

793831
/// Represents an instance index in a composition graph.
@@ -1215,8 +1253,11 @@ impl DependencyRegistrar<'_, '_> {
12151253
match &self.types[ty] {
12161254
ComponentDefinedType::Primitive(_)
12171255
| ComponentDefinedType::Enum(_)
1218-
| ComponentDefinedType::Flags(_) => {}
1219-
ComponentDefinedType::List(t) | ComponentDefinedType::Option(t) => self.val_type(*t),
1256+
| ComponentDefinedType::Flags(_)
1257+
| ComponentDefinedType::Error => {}
1258+
ComponentDefinedType::List(t)
1259+
| ComponentDefinedType::Option(t)
1260+
| ComponentDefinedType::Stream(t) => self.val_type(*t),
12201261
ComponentDefinedType::Own(r) | ComponentDefinedType::Borrow(r) => {
12211262
self.ty(ComponentAnyTypeId::Resource(*r))
12221263
}
@@ -1245,6 +1286,11 @@ impl DependencyRegistrar<'_, '_> {
12451286
self.val_type(*err);
12461287
}
12471288
}
1289+
ComponentDefinedType::Future(ty) => {
1290+
if let Some(ty) = ty {
1291+
self.val_type(*ty);
1292+
}
1293+
}
12481294
}
12491295
}
12501296
}
@@ -1402,7 +1448,7 @@ impl<'a> CompositionGraphEncoder<'a> {
14021448
state.push(Encodable::Instance(InstanceType::new()));
14031449
for (name, types) in exports {
14041450
let (component, ty) = types[0];
1405-
log::trace!("export {name}");
1451+
log::trace!("export {name}: {ty:?}");
14061452
let export = TypeEncoder::new(component).export(name, ty, state);
14071453
let t = match &mut state.cur.encodable {
14081454
Encodable::Instance(c) => c,
@@ -1418,6 +1464,7 @@ impl<'a> CompositionGraphEncoder<'a> {
14181464
}
14191465
}
14201466
}
1467+
14211468
let instance_type = match state.pop() {
14221469
Encodable::Instance(c) => c,
14231470
_ => unreachable!(),

crates/wasm-compose/src/graph.rs

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ use wasmparser::{
1818
names::ComponentName,
1919
types::{Types, TypesRef},
2020
Chunk, ComponentExternalKind, ComponentTypeRef, Encoding, Parser, Payload, ValidPayload,
21-
Validator,
21+
Validator, WasmFeatures,
2222
};
2323

2424
pub(crate) fn type_desc(item: ComponentEntityType) -> &'static str {
@@ -99,7 +99,11 @@ impl<'a> Component<'a> {
9999
fn parse(name: String, path: Option<PathBuf>, bytes: Cow<'a, [u8]>) -> Result<Self> {
100100
let mut parser = Parser::new(0);
101101
let mut parsers = Vec::new();
102-
let mut validator = Validator::new();
102+
let mut validator = Validator::new_with_features(
103+
WasmFeatures::WASM2
104+
| WasmFeatures::COMPONENT_MODEL
105+
| WasmFeatures::COMPONENT_MODEL_ASYNC,
106+
);
103107
let mut imports = IndexMap::new();
104108
let mut exports = IndexMap::new();
105109

@@ -439,7 +443,7 @@ pub(crate) struct Instance {
439443
}
440444

441445
/// The options for encoding a composition graph.
442-
#[derive(Debug, Clone, Copy, Eq, PartialEq, Default)]
446+
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default)]
443447
pub struct EncodeOptions {
444448
/// Whether or not to define instantiated components.
445449
///
@@ -448,7 +452,7 @@ pub struct EncodeOptions {
448452

449453
/// The instance in the graph to export.
450454
///
451-
/// If `Some`, the instance's exports will be aliased and
455+
/// If non-empty, the instance's exports will be aliased and
452456
/// exported from the resulting component.
453457
pub export: Option<InstanceId>,
454458

@@ -508,9 +512,6 @@ impl ResourceMapping {
508512
if value.1 == export_resource {
509513
self.map.insert(export_resource, value);
510514
self.map.insert(import_resource, value);
511-
} else {
512-
// Can't set two different exports equal to each other -- give up.
513-
return None;
514515
}
515516
} else {
516517
// Couldn't find an export with a name that matches this
@@ -559,14 +560,19 @@ impl<'a> CompositionGraph<'a> {
559560
/// connected to exports, group them by name, and update the resource
560561
/// mapping to make all resources within each group equivalent.
561562
///
562-
/// This should be the last step prior to encoding, after all
563-
/// inter-component connections have been made. It ensures that each set of
564-
/// identical imports composed component can be merged into a single import
565-
/// in the output component.
563+
/// This ensures that each set of identical imports in the composed
564+
/// components can be merged into a single import in the output component.
565+
//
566+
// TODO: How do we balance the need to call this early (so we can match up
567+
// imports with exports which mutually import the same resources) with the
568+
// need to delay decisions about where resources are coming from (so that we
569+
// can match up imported resources with exported resources)? Right now I
570+
// think we're erring on the side if the former at the expense of the
571+
// latter.
566572
pub(crate) fn unify_imported_resources(&self) {
567573
let mut resource_mapping = self.resource_mapping.borrow_mut();
568574

569-
let mut resource_imports = HashMap::<_, Vec<_>>::new();
575+
let mut resource_imports = IndexMap::<_, IndexSet<_>>::new();
570576
for (component_id, component) in &self.components {
571577
let component = &component.component;
572578
for import_name in component.imports.keys() {
@@ -584,20 +590,22 @@ impl<'a> CompositionGraph<'a> {
584590
..
585591
} = ty
586592
{
587-
if !resource_mapping.map.contains_key(&resource_id.resource()) {
588-
resource_imports
589-
.entry(vec![import_name.to_string(), export_name.to_string()])
590-
.or_default()
591-
.push((*component_id, resource_id.resource()))
593+
let set = resource_imports
594+
.entry(vec![import_name.to_string(), export_name.to_string()])
595+
.or_default();
596+
597+
if let Some(pair) = resource_mapping.map.get(&resource_id.resource()) {
598+
set.insert(*pair);
592599
}
600+
set.insert((*component_id, resource_id.resource()));
593601
}
594602
}
595603
}
596604
}
597605
}
598606

599607
for resources in resource_imports.values() {
600-
match &resources[..] {
608+
match &resources.iter().copied().collect::<Vec<_>>()[..] {
601609
[] => unreachable!(),
602610
[_] => {}
603611
[first, rest @ ..] => {
@@ -653,10 +661,8 @@ impl<'a> CompositionGraph<'a> {
653661
.remap_component_entity(&mut import_type, remapping);
654662
remapping.reset_type_cache();
655663

656-
if context
657-
.component_entity_type(&export_type, &import_type, 0)
658-
.is_ok()
659-
{
664+
let v = context.component_entity_type(&export_type, &import_type, 0);
665+
if v.is_ok() {
660666
*self.resource_mapping.borrow_mut() = resource_mapping;
661667
true
662668
} else {
@@ -706,6 +712,10 @@ impl<'a> CompositionGraph<'a> {
706712

707713
assert!(self.components.insert(id, entry).is_none());
708714

715+
if self.components.len() > 1 {
716+
self.unify_imported_resources();
717+
}
718+
709719
Ok(id)
710720
}
711721

0 commit comments

Comments
 (0)