Skip to content

Commit e2485b8

Browse files
committed
Add support for resources to wit-component
This commit fully integrates resource types in the component model with the `wit-component` crate, implementing features such as: * A WIT package using `resource` types can now be round-tripped through its WebAssembly encoding. This enables use cases such as `wit-bindgen` which embed type information in custom sections of core wasm binaries produced by native compilers. * Core modules can be lifted into components and the component can use resources. This provides a target for `wit-bindgen` and other code generators to use when generating guest code. Resource intrinsics and destructors are all available to the core wasm as desired. * WIT can be inferred from components using resources, where functions are represented as `resource`-related functions in WIT. * The `roundtrip-wit` fuzzer is extended with resources support meaning all of the above support will be fuzzed on OSS-Fuzz. This required a number of refactorings in `wit-component` especially around how type information was handled. Previous processing was a bit fast-and-loose because where exactly a type was defined didn't really matter since everything was nominal. With resource types, however, definition locations are significant and this required some fixes to previous processing. One example of this is that WebAssembly/component-model#208 was discovered through this work and the fixes required were implemented previously and further handled here in `wit-component`. Overall this PR has been almost exclusively fuzz-driven in its development. I started out with the bare bones of getting simple components working with resources being imported and exported, then added fuzzing support to `wit-smith`, then let the fuzzer go wild. Quite a few issues were discovered which led to all of the refactorings and processing here in this PR. I definitely won't claim that this is a simplification at all to `wit-component` by any measure. Rather it's taking a complicated codebase and making it more complicated. In my mind though the "saving grace" is that I'm pretty confident in the testing/fuzzing story here. It's relatively easy to isolate issues and add test cases for the various things that can crop up and the fuzzer has quite good coverage of all the various paths through `wit-component`. All that's to say that this is surely not the "best" or easiest to understand implementation of resources, but it's intended to be sufficient for now.
1 parent 189d7ef commit e2485b8

File tree

82 files changed

+2777
-530
lines changed

Some content is hidden

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

82 files changed

+2777
-530
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,12 @@ impl ComponentTypeSection {
744744
pub fn defined_type(&mut self) -> ComponentDefinedTypeEncoder<'_> {
745745
self.ty().defined_type()
746746
}
747+
748+
/// Defines a new resource type.
749+
pub fn resource(&mut self, rep: ValType, dtor: Option<u32>) -> &mut Self {
750+
self.ty().resource(rep, dtor);
751+
self
752+
}
747753
}
748754

749755
impl Encode for ComponentTypeSection {

crates/wasmprinter/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1785,7 +1785,7 @@ impl Printer {
17851785
self.print_valtype(rep)?;
17861786
self.result.push_str(")");
17871787
if let Some(dtor) = dtor {
1788-
self.result.push_str("(dtor (func ");
1788+
self.result.push_str(" (dtor (func ");
17891789
self.print_idx(&states.last().unwrap().core.func_names, dtor)?;
17901790
self.result.push_str("))");
17911791
}

crates/wit-component/src/builder.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,11 @@ impl ComponentBuilder {
169169
(inc(&mut self.types), self.types().function())
170170
}
171171

172+
pub fn resource(&mut self, rep: ValType, dtor: Option<u32>) -> u32 {
173+
self.types().resource(rep, dtor);
174+
inc(&mut self.types)
175+
}
176+
172177
pub fn alias_type_export(&mut self, instance: u32, name: &str) -> u32 {
173178
self.aliases().alias(Alias::InstanceExport {
174179
instance,
@@ -200,6 +205,21 @@ impl ComponentBuilder {
200205
pub fn add_producers(&mut self, producers: &Producers) {
201206
self.producers.merge(producers)
202207
}
208+
209+
pub fn resource_drop(&mut self, ty: ComponentValType) -> u32 {
210+
self.canonical_functions().resource_drop(ty);
211+
inc(&mut self.core_funcs)
212+
}
213+
214+
pub fn resource_new(&mut self, ty: u32) -> u32 {
215+
self.canonical_functions().resource_new(ty);
216+
inc(&mut self.core_funcs)
217+
}
218+
219+
pub fn resource_rep(&mut self, ty: u32) -> u32 {
220+
self.canonical_functions().resource_rep(ty);
221+
inc(&mut self.core_funcs)
222+
}
203223
}
204224

205225
// Helper macro to generate methods on `ComponentBuilder` to get specific

crates/wit-component/src/decoding.rs

Lines changed: 86 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ impl<'a> ComponentInfo<'a> {
9999
foreign_packages: Default::default(),
100100
iface_to_package_index: Default::default(),
101101
named_interfaces: Default::default(),
102+
resources: Default::default(),
102103
};
103104

104105
let mut pkg = None;
@@ -150,6 +151,7 @@ impl<'a> ComponentInfo<'a> {
150151
foreign_packages: Default::default(),
151152
iface_to_package_index: Default::default(),
152153
named_interfaces: Default::default(),
154+
resources: Default::default(),
153155
};
154156
let mut package = Package {
155157
// Similar to `world_name` above this is arbitrarily chosen as it's
@@ -237,6 +239,15 @@ struct WitPackageDecoder<'a> {
237239
iface_to_package_index: HashMap<InterfaceId, usize>,
238240
named_interfaces: HashMap<String, InterfaceId>,
239241

242+
/// A map which tracks named resources to what their corresponding `TypeId`
243+
/// is. This first layer of key in this map is the owner scope of a
244+
/// resource, more-or-less the `world` or `interface` that it's defined
245+
/// within. The second layer of this map is keyed by name of the resource
246+
/// and points to the actual ID of the resource.
247+
///
248+
/// This map is populated in `register_type_export`.
249+
resources: HashMap<TypeOwner, HashMap<String, TypeId>>,
250+
240251
/// A map from a type id to what it's been translated to.
241252
type_map: HashMap<types::TypeId, TypeId>,
242253
}
@@ -316,6 +327,7 @@ impl WitPackageDecoder<'_> {
316327
.types
317328
.component_entity_type_of_import(import.name.as_str())
318329
.unwrap();
330+
let owner = TypeOwner::World(world);
319331
let (name, item) = match ty {
320332
types::ComponentEntityType::Instance(i) => {
321333
let ty = match self.info.types.type_from_id(i) {
@@ -337,7 +349,7 @@ impl WitPackageDecoder<'_> {
337349
_ => unreachable!(),
338350
};
339351
let func = self
340-
.convert_function(name, ty)
352+
.convert_function(name, ty, owner)
341353
.with_context(|| format!("failed to decode function from import `{name}`"))?;
342354
(WorldKey::Name(name.to_string()), WorldItem::Function(func))
343355
}
@@ -346,7 +358,7 @@ impl WitPackageDecoder<'_> {
346358
created,
347359
} => {
348360
let id = self
349-
.register_type_export(name, TypeOwner::World(world), referenced, created)
361+
.register_type_export(name, owner, referenced, created)
350362
.with_context(|| format!("failed to decode type from export `{name}`"))?;
351363
(WorldKey::Name(name.to_string()), WorldItem::Type(id))
352364
}
@@ -373,7 +385,7 @@ impl WitPackageDecoder<'_> {
373385
_ => unreachable!(),
374386
};
375387
let func = self
376-
.convert_function(name, ty)
388+
.convert_function(name, ty, TypeOwner::World(world))
377389
.with_context(|| format!("failed to decode function from export `{name}`"))?;
378390

379391
(WorldKey::Name(name.to_string()), WorldItem::Function(func))
@@ -418,18 +430,13 @@ impl WitPackageDecoder<'_> {
418430
Some(id) => (true, *id),
419431
None => (false, self.extract_dep_interface(name)?),
420432
};
421-
433+
let owner = TypeOwner::Interface(interface);
422434
for (name, ty) in ty.exports.iter() {
423435
match *ty {
424436
types::ComponentEntityType::Type {
425437
referenced,
426438
created,
427439
} => {
428-
let def = match self.info.types.type_from_id(referenced) {
429-
Some(types::Type::Defined(ty)) => ty,
430-
_ => unreachable!(),
431-
};
432-
433440
match self.resolve.interfaces[interface]
434441
.types
435442
.get(name.as_str())
@@ -453,7 +460,11 @@ impl WitPackageDecoder<'_> {
453460
// is not strictly necessary but assists with
454461
// roundtripping assertions during fuzzing.
455462
Some(id) => {
456-
self.register_defined(id, def)?;
463+
match self.info.types.type_from_id(referenced) {
464+
Some(types::Type::Defined(ty)) => self.register_defined(id, ty)?,
465+
Some(types::Type::Resource(_)) => {}
466+
_ => unreachable!(),
467+
}
457468
let prev = self.type_map.insert(created, id);
458469
assert!(prev.is_none());
459470
}
@@ -477,7 +488,7 @@ impl WitPackageDecoder<'_> {
477488
}
478489
let id = self.register_type_export(
479490
name.as_str(),
480-
TypeOwner::Interface(interface),
491+
owner,
481492
referenced,
482493
created,
483494
)?;
@@ -508,7 +519,7 @@ impl WitPackageDecoder<'_> {
508519
if is_local {
509520
bail!("instance function export `{name}` not defined in interface");
510521
}
511-
let func = self.convert_function(name.as_str(), def)?;
522+
let func = self.convert_function(name.as_str(), def, owner)?;
512523
let prev = self.resolve.interfaces[interface]
513524
.functions
514525
.insert(name.to_string(), func);
@@ -639,19 +650,15 @@ impl WitPackageDecoder<'_> {
639650
package: None,
640651
};
641652

653+
let owner = TypeOwner::Interface(self.resolve.interfaces.next_id());
642654
for (name, ty) in ty.exports.iter() {
643655
match *ty {
644656
types::ComponentEntityType::Type {
645657
referenced,
646658
created,
647659
} => {
648660
let ty = self
649-
.register_type_export(
650-
name.as_str(),
651-
TypeOwner::Interface(self.resolve.interfaces.next_id()),
652-
referenced,
653-
created,
654-
)
661+
.register_type_export(name.as_str(), owner, referenced, created)
655662
.with_context(|| format!("failed to register type export '{name}'"))?;
656663
let prev = interface.types.insert(name.to_string(), ty);
657664
assert!(prev.is_none());
@@ -663,7 +670,7 @@ impl WitPackageDecoder<'_> {
663670
_ => unreachable!(),
664671
};
665672
let func = self
666-
.convert_function(name.as_str(), ty)
673+
.convert_function(name.as_str(), ty, owner)
667674
.with_context(|| format!("failed to convert function '{name}'"))?;
668675
let prev = interface.functions.insert(name.to_string(), func);
669676
assert!(prev.is_none());
@@ -712,10 +719,6 @@ impl WitPackageDecoder<'_> {
712719
referenced: types::TypeId,
713720
created: types::TypeId,
714721
) -> Result<TypeId> {
715-
let ty = match self.info.types.type_from_id(referenced) {
716-
Some(types::Type::Defined(ty)) => ty,
717-
_ => unreachable!(),
718-
};
719722
let kind = match self.find_alias(referenced) {
720723
// If this `TypeId` points to a type which has
721724
// previously been defined, meaning we're aliasing a
@@ -724,9 +727,13 @@ impl WitPackageDecoder<'_> {
724727

725728
// ... or this `TypeId`'s source definition has never
726729
// been seen before, so declare the full type.
727-
None => self
728-
.convert_defined(ty)
729-
.context("failed to convert unaliased type")?,
730+
None => match self.info.types.type_from_id(referenced) {
731+
Some(types::Type::Defined(ty)) => self
732+
.convert_defined(ty)
733+
.context("failed to convert unaliased type")?,
734+
Some(types::Type::Resource(_)) => TypeDefKind::Resource,
735+
_ => unreachable!(),
736+
},
730737
};
731738
let ty = self.resolve.types.alloc(TypeDef {
732739
name: Some(name.to_string()),
@@ -735,6 +742,18 @@ impl WitPackageDecoder<'_> {
735742
owner,
736743
});
737744

745+
// If this is a resource then doubly-register it in `self.resources` so
746+
// the ID allocated here can be looked up via name later on during
747+
// `convert_function`.
748+
if let TypeDefKind::Resource = self.resolve.types[ty].kind {
749+
let prev = self
750+
.resources
751+
.entry(owner)
752+
.or_insert(HashMap::new())
753+
.insert(name.to_string(), ty);
754+
assert!(prev.is_none());
755+
}
756+
738757
let prev = self.type_map.insert(created, ty);
739758
assert!(prev.is_none());
740759
Ok(ty)
@@ -759,6 +778,7 @@ impl WitPackageDecoder<'_> {
759778
package: None,
760779
};
761780

781+
let owner = TypeOwner::World(self.resolve.worlds.next_id());
762782
for (name, ty) in ty.imports.iter() {
763783
let (name, item) = match ty {
764784
types::ComponentEntityType::Instance(idx) => {
@@ -785,20 +805,16 @@ impl WitPackageDecoder<'_> {
785805
created,
786806
referenced,
787807
} => {
788-
let ty = self.register_type_export(
789-
name.as_str(),
790-
TypeOwner::World(self.resolve.worlds.next_id()),
791-
*referenced,
792-
*created,
793-
)?;
808+
let ty =
809+
self.register_type_export(name.as_str(), owner, *referenced, *created)?;
794810
(WorldKey::Name(name.to_string()), WorldItem::Type(ty))
795811
}
796812
types::ComponentEntityType::Func(idx) => {
797813
let ty = match self.info.types.type_from_id(*idx) {
798814
Some(types::Type::ComponentFunc(ty)) => ty,
799815
_ => unreachable!(),
800816
};
801-
let func = self.convert_function(name.as_str(), ty)?;
817+
let func = self.convert_function(name.as_str(), ty, owner)?;
802818
(WorldKey::Name(name.to_string()), WorldItem::Function(func))
803819
}
804820
_ => bail!("component import `{name}` is not an instance, func, or type"),
@@ -833,7 +849,7 @@ impl WitPackageDecoder<'_> {
833849
Some(types::Type::ComponentFunc(ty)) => ty,
834850
_ => unreachable!(),
835851
};
836-
let func = self.convert_function(name.as_str(), ty)?;
852+
let func = self.convert_function(name.as_str(), ty, owner)?;
837853
(WorldKey::Name(name.to_string()), WorldItem::Function(func))
838854
}
839855

@@ -847,7 +863,13 @@ impl WitPackageDecoder<'_> {
847863
Ok(id)
848864
}
849865

850-
fn convert_function(&mut self, name: &str, ty: &types::ComponentFuncType) -> Result<Function> {
866+
fn convert_function(
867+
&mut self,
868+
name: &str,
869+
ty: &types::ComponentFuncType,
870+
owner: TypeOwner,
871+
) -> Result<Function> {
872+
let name = KebabName::new(ComponentExternName::Kebab(name), 0).unwrap();
851873
let params = ty
852874
.params
853875
.iter()
@@ -875,7 +897,26 @@ impl WitPackageDecoder<'_> {
875897
};
876898
Ok(Function {
877899
docs: Default::default(),
878-
kind: FunctionKind::Freestanding,
900+
kind: match name.kind() {
901+
KebabNameKind::Normal(_) => FunctionKind::Freestanding,
902+
KebabNameKind::Constructor(resource) => {
903+
FunctionKind::Constructor(self.resources[&owner][resource.as_str()])
904+
}
905+
KebabNameKind::Method { resource, .. } => {
906+
FunctionKind::Method(self.resources[&owner][resource.as_str()])
907+
}
908+
KebabNameKind::Static { resource, .. } => {
909+
FunctionKind::Static(self.resources[&owner][resource.as_str()])
910+
}
911+
912+
// Functions shouldn't have ID-based names at this time.
913+
KebabNameKind::Id { .. } => unreachable!(),
914+
},
915+
916+
// Note that this name includes "name mangling" such as
917+
// `[method]foo.bar` which is intentional. The `FunctionKind`
918+
// discriminant calculated above indicates how to interpret this
919+
// name.
879920
name: name.to_string(),
880921
params,
881922
results,
@@ -1049,8 +1090,15 @@ impl WitPackageDecoder<'_> {
10491090
Ok(TypeDefKind::Enum(Enum { cases }))
10501091
}
10511092

1052-
types::ComponentDefinedType::Own(_) => unimplemented!(),
1053-
types::ComponentDefinedType::Borrow(_) => unimplemented!(),
1093+
types::ComponentDefinedType::Own(id) => {
1094+
let id = self.type_map[id];
1095+
Ok(TypeDefKind::Handle(Handle::Own(id)))
1096+
}
1097+
1098+
types::ComponentDefinedType::Borrow(id) => {
1099+
let id = self.type_map[id];
1100+
Ok(TypeDefKind::Handle(Handle::Borrow(id)))
1101+
}
10541102
}
10551103
}
10561104

0 commit comments

Comments
 (0)