Skip to content

Commit b6f38ae

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]> add wasmparser::WasmFeatures support to wasm-compose Signed-off-by: Joel Dice <[email protected]> fix no-std build in readers.rs Signed-off-by: Joel Dice <[email protected]> rename `error` to `error-context` per latest spec Signed-off-by: Joel Dice <[email protected]> rename `error` to `error-context` per latest spec (part 2) Also, parse string encoding and realloc from encoded `error-context.new` and `error-context.debug-string` names. Signed-off-by: Joel Dice <[email protected]> add `wast` support for parsing async canon opts And add tests/local/component-model-async/lift-async.wast for round-trip testing of async lifts (more to come). Signed-off-by: Joel Dice <[email protected]> more wast async support and more tests This also fixes a bug in `wasmprinter` keeping track of core functions. Signed-off-by: Joel Dice <[email protected]> more wast async support; add async tests; fix bugs Signed-off-by: Joel Dice <[email protected]> more component-model-async tests and fixes Signed-off-by: Joel Dice <[email protected]> add `wit-parser` tests for streams, futures, and error-contexts Signed-off-by: Joel Dice <[email protected]> add first `wit-component` async test This required adding a new `wit_parser::decoding::decode_reader_with_features` function for passing `WasmFeatures` to `wasmparser::Validator::new_with_features`. Signed-off-by: Joel Dice <[email protected]> add more async tests Signed-off-by: Joel Dice <[email protected]> add `async-builtins` test for `wit-component` Signed-off-by: Joel Dice <[email protected]> add `async-streams-and-futures` test to `wit-component` Signed-off-by: Joel Dice <[email protected]>
1 parent aab1ac8 commit b6f38ae

File tree

116 files changed

+8946
-351
lines changed

Some content is hidden

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

116 files changed

+8946
-351
lines changed

crates/wasm-compose/src/composer.rs

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ use std::{collections::VecDeque, ffi::OsStr, path::Path};
1414
use wasmparser::{
1515
component_types::{ComponentEntityType, ComponentInstanceTypeId},
1616
types::TypesRef,
17-
ComponentExternalKind, ComponentTypeRef,
17+
ComponentExternalKind, ComponentTypeRef, WasmFeatures,
1818
};
1919

2020
/// The root component name used in configuration.
@@ -72,9 +72,13 @@ struct CompositionGraphBuilder<'a> {
7272
}
7373

7474
impl<'a> CompositionGraphBuilder<'a> {
75-
fn new(root_path: &Path, config: &'a Config) -> Result<Self> {
75+
fn new(root_path: &Path, config: &'a Config, features: WasmFeatures) -> Result<Self> {
7676
let mut graph = CompositionGraph::new();
77-
graph.add_component(Component::from_file(ROOT_COMPONENT_NAME, root_path)?)?;
77+
graph.add_component(Component::from_file(
78+
ROOT_COMPONENT_NAME,
79+
root_path,
80+
features,
81+
)?)?;
7882

7983
let definitions = config
8084
.definitions
@@ -87,7 +91,7 @@ impl<'a> CompositionGraphBuilder<'a> {
8791
)
8892
})?;
8993

90-
let component = Component::from_file(name, config.dir.join(path))?;
94+
let component = Component::from_file(name, config.dir.join(path), features)?;
9195

9296
Ok((graph.add_component(component)?, None))
9397
})
@@ -105,19 +109,19 @@ impl<'a> CompositionGraphBuilder<'a> {
105109
///
106110
/// If a component with the given name already exists, its id is returned.
107111
/// Returns `Ok(None)` if a matching component cannot be found.
108-
fn add_component(&mut self, name: &str) -> Result<Option<ComponentId>> {
112+
fn add_component(&mut self, name: &str, features: WasmFeatures) -> Result<Option<ComponentId>> {
109113
if let Some((id, _)) = self.graph.get_component_by_name(name) {
110114
return Ok(Some(id));
111115
}
112116

113-
match self.find_component(name)? {
117+
match self.find_component(name, features)? {
114118
Some(component) => Ok(Some(self.graph.add_component(component)?)),
115119
None => Ok(None),
116120
}
117121
}
118122

119123
/// Finds the component with the given name on disk.
120-
fn find_component(&self, name: &str) -> Result<Option<Component<'a>>> {
124+
fn find_component(&self, name: &str, features: WasmFeatures) -> Result<Option<Component<'a>>> {
121125
// Check the config for an explicit path (must be a valid component)
122126
if let Some(dep) = self.config.dependencies.get(name) {
123127
log::debug!(
@@ -127,13 +131,14 @@ impl<'a> CompositionGraphBuilder<'a> {
127131
return Ok(Some(Component::from_file(
128132
name,
129133
self.config.dir.join(&dep.path),
134+
features,
130135
)?));
131136
}
132137

133138
// Otherwise, search the paths for a valid component with the same name
134139
log::info!("searching for a component with name `{name}`");
135140
for dir in std::iter::once(&self.config.dir).chain(self.config.search_paths.iter()) {
136-
if let Some(component) = Self::parse_component(dir, name)? {
141+
if let Some(component) = Self::parse_component(dir, name, features)? {
137142
return Ok(Some(component));
138143
}
139144
}
@@ -144,7 +149,11 @@ impl<'a> CompositionGraphBuilder<'a> {
144149
/// Parses a component from the given directory, if it exists.
145150
///
146151
/// Returns `Ok(None)` if the component does not exist.
147-
fn parse_component(dir: &Path, name: &str) -> Result<Option<Component<'a>>> {
152+
fn parse_component(
153+
dir: &Path,
154+
name: &str,
155+
features: WasmFeatures,
156+
) -> Result<Option<Component<'a>>> {
148157
let mut path = dir.join(name);
149158

150159
for ext in ["wasm", "wat"] {
@@ -154,7 +163,7 @@ impl<'a> CompositionGraphBuilder<'a> {
154163
continue;
155164
}
156165

157-
return Ok(Some(Component::from_file(name, &path)?));
166+
return Ok(Some(Component::from_file(name, &path, features)?));
158167
}
159168

160169
Ok(None)
@@ -165,12 +174,17 @@ impl<'a> CompositionGraphBuilder<'a> {
165174
/// Returns an index into `instances` for the instance being instantiated.
166175
///
167176
/// Returns `Ok(None)` if a component to instantiate cannot be found.
168-
fn instantiate(&mut self, name: &str, component_name: &str) -> Result<Option<(usize, bool)>> {
177+
fn instantiate(
178+
&mut self,
179+
name: &str,
180+
component_name: &str,
181+
features: WasmFeatures,
182+
) -> Result<Option<(usize, bool)>> {
169183
if let Some(index) = self.instances.get_index_of(name) {
170184
return Ok(Some((index, true)));
171185
}
172186

173-
match self.add_component(component_name)? {
187+
match self.add_component(component_name, features)? {
174188
Some(component_id) => {
175189
let (index, prev) = self
176190
.instances
@@ -301,13 +315,18 @@ impl<'a> CompositionGraphBuilder<'a> {
301315
/// Processes a dependency in the graph.
302316
///
303317
/// Returns `Ok(Some(index))` if the dependency resulted in a new dependency instance being created.
304-
fn process_dependency(&mut self, dependency: Dependency) -> Result<Option<usize>> {
318+
fn process_dependency(
319+
&mut self,
320+
dependency: Dependency,
321+
features: WasmFeatures,
322+
) -> Result<Option<usize>> {
305323
match dependency.kind {
306324
DependencyKind::Instance { instance, export } => self.process_instance_dependency(
307325
dependency.dependent,
308326
dependency.import,
309327
&instance,
310328
export.as_deref(),
329+
features,
311330
),
312331
DependencyKind::Definition { index, export } => {
313332
// The dependency is on a definition component, so we simply connect the dependent to the definition's export
@@ -348,6 +367,7 @@ impl<'a> CompositionGraphBuilder<'a> {
348367
import: InstanceImportRef,
349368
instance: &str,
350369
export: Option<&str>,
370+
features: WasmFeatures,
351371
) -> Result<Option<usize>> {
352372
let name = self.config.dependency_name(instance);
353373

@@ -356,7 +376,7 @@ impl<'a> CompositionGraphBuilder<'a> {
356376
dependent_name = self.instances.get_index(dependent_index).unwrap().0,
357377
);
358378

359-
match self.instantiate(instance, name)? {
379+
match self.instantiate(instance, name, features)? {
360380
Some((instance, existing)) => {
361381
let (dependent, import_name, import_type) = self.resolve_import_ref(import);
362382

@@ -478,12 +498,12 @@ impl<'a> CompositionGraphBuilder<'a> {
478498
}
479499

480500
/// Build the instantiation graph.
481-
fn build(mut self) -> Result<(InstanceId, CompositionGraph<'a>)> {
501+
fn build(mut self, features: WasmFeatures) -> Result<(InstanceId, CompositionGraph<'a>)> {
482502
let mut queue: VecDeque<Dependency> = VecDeque::new();
483503

484504
// Instantiate the root and push its dependencies to the queue
485505
let (root_instance, existing) = self
486-
.instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME)?
506+
.instantiate(ROOT_COMPONENT_NAME, ROOT_COMPONENT_NAME, features)?
487507
.unwrap();
488508

489509
assert!(!existing);
@@ -492,13 +512,11 @@ impl<'a> CompositionGraphBuilder<'a> {
492512

493513
// Process all remaining dependencies in the queue
494514
while let Some(dependency) = queue.pop_front() {
495-
if let Some(instance) = self.process_dependency(dependency)? {
515+
if let Some(instance) = self.process_dependency(dependency, features)? {
496516
self.push_dependencies(instance, &mut queue)?;
497517
}
498518
}
499519

500-
self.graph.unify_imported_resources();
501-
502520
Ok((self.instances[root_instance], self.graph))
503521
}
504522
}
@@ -513,6 +531,7 @@ impl<'a> CompositionGraphBuilder<'a> {
513531
pub struct ComponentComposer<'a> {
514532
component: &'a Path,
515533
config: &'a Config,
534+
features: WasmFeatures,
516535
}
517536

518537
impl<'a> ComponentComposer<'a> {
@@ -521,8 +540,12 @@ impl<'a> ComponentComposer<'a> {
521540
/// ## Arguments
522541
/// * `component` - The path to the component to compose.
523542
/// * `config` - The configuration to use for the composition.
524-
pub fn new(component: &'a Path, config: &'a Config) -> Self {
525-
Self { component, config }
543+
pub fn new(component: &'a Path, config: &'a Config, features: WasmFeatures) -> Self {
544+
Self {
545+
component,
546+
config,
547+
features,
548+
}
526549
}
527550

528551
/// Composes a WebAssembly component based on the composer's configuration.
@@ -531,7 +554,8 @@ impl<'a> ComponentComposer<'a> {
531554
/// Returns the bytes of the composed component.
532555
pub fn compose(&self) -> Result<Vec<u8>> {
533556
let (root_instance, graph) =
534-
CompositionGraphBuilder::new(self.component, self.config)?.build()?;
557+
CompositionGraphBuilder::new(self.component, self.config, self.features)?
558+
.build(self.features)?;
535559

536560
// If only the root component was instantiated, then there are no resolved dependencies
537561
if graph.instances.len() == 1 {

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::ErrorContext => self.error_context(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_context(&self, state: &mut TypeState<'a>) -> u32 {
825+
let index = state.cur.encodable.type_count();
826+
state.cur.encodable.ty().defined_type().error_context();
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::ErrorContext => {}
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!(),

0 commit comments

Comments
 (0)