Skip to content

Commit

Permalink
Add support for async ABI, futures, streams, and errors
Browse files Browse the repository at this point in the history
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 <joel.dice@fermyon.com>
  • Loading branch information
dicej committed Nov 5, 2024
1 parent 196a6be commit 015b271
Show file tree
Hide file tree
Showing 43 changed files with 3,718 additions and 298 deletions.
2 changes: 0 additions & 2 deletions crates/wasm-compose/src/composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -497,8 +497,6 @@ impl<'a> CompositionGraphBuilder<'a> {
}
}

self.graph.unify_imported_resources();

Ok((self.instances[root_instance], self.graph))
}
}
Expand Down
69 changes: 58 additions & 11 deletions crates/wasm-compose/src/encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,17 @@ impl<'a> TypeEncoder<'a> {
return ret;
}

if let Some((instance, name)) = state.cur.instance_exports.get(&key) {
let ret = state.cur.encodable.type_count();
state.cur.encodable.alias(Alias::InstanceExport {
instance: *instance,
name,
kind: ComponentExportKind::Type,
});
log::trace!("id defined in current instance");
return ret;
}

match id.peel_alias(&self.0.types) {
Some(next) => id = next,
// If there's no more aliases then fall through to the
Expand All @@ -608,15 +619,17 @@ impl<'a> TypeEncoder<'a> {
return match id {
AnyTypeId::Core(ComponentCoreTypeId::Sub(_)) => unreachable!(),
AnyTypeId::Core(ComponentCoreTypeId::Module(id)) => self.module_type(state, id),
AnyTypeId::Component(id) => match id {
ComponentAnyTypeId::Resource(_) => {
unreachable!("should have been handled in `TypeEncoder::component_entity_type`")
AnyTypeId::Component(id) => {
match id {
ComponentAnyTypeId::Resource(r) => {
unreachable!("should have been handled in `TypeEncoder::component_entity_type`: {r:?}")
}
ComponentAnyTypeId::Defined(id) => self.defined_type(state, id),
ComponentAnyTypeId::Func(id) => self.component_func_type(state, id),
ComponentAnyTypeId::Instance(id) => self.component_instance_type(state, id),
ComponentAnyTypeId::Component(id) => self.component_type(state, id),
}
ComponentAnyTypeId::Defined(id) => self.defined_type(state, id),
ComponentAnyTypeId::Func(id) => self.component_func_type(state, id),
ComponentAnyTypeId::Instance(id) => self.component_instance_type(state, id),
ComponentAnyTypeId::Component(id) => self.component_type(state, id),
},
}
};
}

Expand Down Expand Up @@ -667,6 +680,9 @@ impl<'a> TypeEncoder<'a> {
state.cur.encodable.ty().defined_type().borrow(ty);
index
}
ComponentDefinedType::Future(ty) => self.future(state, *ty),
ComponentDefinedType::Stream(ty) => self.stream(state, *ty),
ComponentDefinedType::Error => self.error(state),
}
}

Expand Down Expand Up @@ -788,6 +804,28 @@ impl<'a> TypeEncoder<'a> {
}
export
}

fn future(&self, state: &mut TypeState<'a>, ty: Option<ct::ComponentValType>) -> u32 {
let ty = ty.map(|ty| self.component_val_type(state, ty));

let index = state.cur.encodable.type_count();
state.cur.encodable.ty().defined_type().future(ty);
index
}

fn stream(&self, state: &mut TypeState<'a>, ty: ct::ComponentValType) -> u32 {
let ty = self.component_val_type(state, ty);

let index = state.cur.encodable.type_count();
state.cur.encodable.ty().defined_type().stream(ty);
index
}

fn error(&self, state: &mut TypeState<'a>) -> u32 {
let index = state.cur.encodable.type_count();
state.cur.encodable.ty().defined_type().error();
index
}
}

/// Represents an instance index in a composition graph.
Expand Down Expand Up @@ -1215,8 +1253,11 @@ impl DependencyRegistrar<'_, '_> {
match &self.types[ty] {
ComponentDefinedType::Primitive(_)
| ComponentDefinedType::Enum(_)
| ComponentDefinedType::Flags(_) => {}
ComponentDefinedType::List(t) | ComponentDefinedType::Option(t) => self.val_type(*t),
| ComponentDefinedType::Flags(_)
| ComponentDefinedType::Error => {}
ComponentDefinedType::List(t)
| ComponentDefinedType::Option(t)
| ComponentDefinedType::Stream(t) => self.val_type(*t),
ComponentDefinedType::Own(r) | ComponentDefinedType::Borrow(r) => {
self.ty(ComponentAnyTypeId::Resource(*r))
}
Expand Down Expand Up @@ -1245,6 +1286,11 @@ impl DependencyRegistrar<'_, '_> {
self.val_type(*err);
}
}
ComponentDefinedType::Future(ty) => {
if let Some(ty) = ty {
self.val_type(*ty);
}
}
}
}
}
Expand Down Expand Up @@ -1402,7 +1448,7 @@ impl<'a> CompositionGraphEncoder<'a> {
state.push(Encodable::Instance(InstanceType::new()));
for (name, types) in exports {
let (component, ty) = types[0];
log::trace!("export {name}");
log::trace!("export {name}: {ty:?}");
let export = TypeEncoder::new(component).export(name, ty, state);
let t = match &mut state.cur.encodable {
Encodable::Instance(c) => c,
Expand All @@ -1418,6 +1464,7 @@ impl<'a> CompositionGraphEncoder<'a> {
}
}
}

let instance_type = match state.pop() {
Encodable::Instance(c) => c,
_ => unreachable!(),
Expand Down
54 changes: 32 additions & 22 deletions crates/wasm-compose/src/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ use wasmparser::{
names::ComponentName,
types::{Types, TypesRef},
Chunk, ComponentExternalKind, ComponentTypeRef, Encoding, Parser, Payload, ValidPayload,
Validator,
Validator, WasmFeatures,
};

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

Expand Down Expand Up @@ -439,7 +443,7 @@ pub(crate) struct Instance {
}

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

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

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

let mut resource_imports = HashMap::<_, Vec<_>>::new();
let mut resource_imports = IndexMap::<_, IndexSet<_>>::new();
for (component_id, component) in &self.components {
let component = &component.component;
for import_name in component.imports.keys() {
Expand All @@ -584,20 +590,22 @@ impl<'a> CompositionGraph<'a> {
..
} = ty
{
if !resource_mapping.map.contains_key(&resource_id.resource()) {
resource_imports
.entry(vec![import_name.to_string(), export_name.to_string()])
.or_default()
.push((*component_id, resource_id.resource()))
let set = resource_imports
.entry(vec![import_name.to_string(), export_name.to_string()])
.or_default();

if let Some(pair) = resource_mapping.map.get(&resource_id.resource()) {
set.insert(*pair);
}
set.insert((*component_id, resource_id.resource()));
}
}
}
}
}

for resources in resource_imports.values() {
match &resources[..] {
match &resources.iter().copied().collect::<Vec<_>>()[..] {
[] => unreachable!(),
[_] => {}
[first, rest @ ..] => {
Expand Down Expand Up @@ -653,10 +661,8 @@ impl<'a> CompositionGraph<'a> {
.remap_component_entity(&mut import_type, remapping);
remapping.reset_type_cache();

if context
.component_entity_type(&export_type, &import_type, 0)
.is_ok()
{
let v = context.component_entity_type(&export_type, &import_type, 0);
if v.is_ok() {
*self.resource_mapping.borrow_mut() = resource_mapping;
true
} else {
Expand Down Expand Up @@ -706,6 +712,10 @@ impl<'a> CompositionGraph<'a> {

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

if self.components.len() > 1 {
self.unify_imported_resources();
}

Ok(id)
}

Expand Down
Loading

0 comments on commit 015b271

Please sign in to comment.