Skip to content

Commit 2f609ae

Browse files
Initial implementation of resource types
This commit is an initial implementation of resource types for the component model. The support added in this commit encompasses: * `wasmparser` - parsing and validation of resource types * `wasmprinter` - converting binary to text for resource types * `wast` - parsing the text format for resource types * `wasm-encoder` - writing the binary format for resource types Other crates such as `wit-component` and `wasm-compose` saw a few small changes to continue compiling but do not have support added for resource types. WIT, for example, continues to not support resource types. That's planned to be added in a subsequent commit. This support is intended to be everything necessary for Wasmtime to start working on implementing resource types, and once that's ready I'll circle back to implement WIT support with host/guest/etc generators. This is a substantial commit. The binary format for resources is pretty minor but the major feature it requires support for is the type-checking in validation. Resources effectively add an ML-module-style type system to the component model with abstract types. This required quite a lot of meticulous and careful plumbing throughout the validator along with a rewritten form of checking subtypes. This commit in theory implements what should be all or at least a large bulk of what's specified for resources at the binary format layer. Without spec tests for formal spec wording it can't really be said how close to the current documentation it follows, but the intention was to follow it closely. Tests have been attempted to be added here in a number of interesting flavors but this implementation is highly likely to still have holes and/or bugs that break the guarantees of resources. Co-authored-by: Trevor Elliott <telliott@fastly.com>
1 parent 1e00529 commit 2f609ae

82 files changed

Lines changed: 4873 additions & 807 deletions

Some content is hidden

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

crates/wasm-compose/src/composer.rs

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use anyhow::{anyhow, bail, Result};
1212
use indexmap::IndexMap;
1313
use std::{collections::VecDeque, path::Path};
1414
use wasmparser::{
15-
types::{ComponentInstanceType, TypesRef},
15+
types::{ComponentEntityType, TypeId, TypesRef},
1616
ComponentExternalKind, ComponentTypeRef,
1717
};
1818

@@ -161,7 +161,7 @@ impl<'a> CompositionGraphBuilder<'a> {
161161
instance: usize,
162162
dependent: usize,
163163
arg_name: &str,
164-
ty: &ComponentInstanceType,
164+
ty: TypeId,
165165
types: TypesRef,
166166
) -> Result<Option<ExportIndex>> {
167167
let (instance_name, instance_id) = self.instances.get_index(instance).unwrap();
@@ -204,15 +204,20 @@ impl<'a> CompositionGraphBuilder<'a> {
204204
instance: usize,
205205
dependent_path: &Path,
206206
arg_name: &str,
207-
ty: &ComponentInstanceType,
207+
ty: TypeId,
208208
types: TypesRef,
209209
) -> Result<ExportIndex> {
210210
let (_, instance_id) = self.instances.get_index(instance).unwrap();
211211
let (_, component) = self.graph.get_component_of_instance(*instance_id).unwrap();
212212
match component.export_by_name(export) {
213213
Some((export_index, _, kind, index)) if kind == ComponentExternalKind::Instance => {
214214
let export_ty = component.types.component_instance_at(index).unwrap();
215-
if !ComponentInstanceType::is_subtype_of(export_ty, component.types(), ty, types) {
215+
if !ComponentEntityType::is_subtype_of(
216+
&ComponentEntityType::Instance(export_ty),
217+
component.types(),
218+
&ComponentEntityType::Instance(ty),
219+
types,
220+
) {
216221
bail!("component `{path}` exports an instance named `{export}` but it is not compatible with import `{arg_name}` of component `{dependent_path}`",
217222
path = component.path().unwrap().display(),
218223
dependent_path = dependent_path.display(),
@@ -229,22 +234,14 @@ impl<'a> CompositionGraphBuilder<'a> {
229234
}
230235

231236
/// Resolves an import instance reference.
232-
fn resolve_import_ref(
233-
&self,
234-
r: InstanceImportRef,
235-
) -> (&Component, &str, &ComponentInstanceType) {
237+
fn resolve_import_ref(&self, r: InstanceImportRef) -> (&Component, &str, TypeId) {
236238
let component = self.graph.get_component(r.component).unwrap();
237239
let (name, _, ty) = component.import(r.import).unwrap();
238240
match ty {
239241
ComponentTypeRef::Instance(index) => (
240242
component,
241243
name,
242-
component
243-
.types
244-
.type_at(index, false)
245-
.unwrap()
246-
.as_component_instance_type()
247-
.unwrap(),
244+
component.types.id_from_type_index(index, false).unwrap(),
248245
),
249246
_ => unreachable!("should not have an instance import ref to a non-instance import"),
250247
}

crates/wasm-compose/src/encoding.rs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ impl<'a> TypeEncoder<'a> {
251251
ComponentTypeRef::Value(self.component_val_type(encodable, types, ty))
252252
}
253253
wasmparser::types::ComponentEntityType::Type { created, .. } => {
254-
ComponentTypeRef::Type(TypeBounds::Eq, self.ty(encodable, types, created))
254+
ComponentTypeRef::Type(TypeBounds::Eq(self.ty(encodable, types, created)))
255255
}
256256
wasmparser::types::ComponentEntityType::Instance(id) => {
257257
ComponentTypeRef::Instance(self.component_instance_type(encodable, types, id))
@@ -363,8 +363,8 @@ impl<'a> TypeEncoder<'a> {
363363
Entry::Occupied(e) => *e.get(),
364364
Entry::Vacant(e) => {
365365
let ty = ty.as_component_instance_type().unwrap();
366-
let instance = self.instance(ty.exports(self.0.as_ref()).map(|(n, u, t)| {
367-
(n.as_str(), u.as_ref().map(|u| u.as_str()).unwrap_or(""), t)
366+
let instance = self.instance(ty.exports.iter().map(|(n, (u, t))| {
367+
(n.as_str(), u.as_ref().map(|u| u.as_str()).unwrap_or(""), *t)
368368
}));
369369
let index = encodable.type_count();
370370
encodable.ty().instance(&instance);
@@ -471,6 +471,7 @@ impl<'a> TypeEncoder<'a> {
471471
self.component_func_type(encodable, types, id)
472472
}
473473
wasmparser::types::Type::Defined(_) => self.defined_type(encodable, types, id),
474+
wasmparser::types::Type::Resource(_) => unimplemented!(),
474475
}
475476
}
476477

@@ -529,6 +530,18 @@ impl<'a> TypeEncoder<'a> {
529530
wasmparser::types::ComponentDefinedType::Result { ok, err } => {
530531
self.result(encodable, types, *ok, *err)
531532
}
533+
wasmparser::types::ComponentDefinedType::Own(id) => {
534+
let i = self.defined_type(encodable, types, *id);
535+
let index = encodable.type_count();
536+
encodable.ty().defined_type().own(i);
537+
index
538+
}
539+
wasmparser::types::ComponentDefinedType::Borrow(id) => {
540+
let i = self.defined_type(encodable, types, *id);
541+
let index = encodable.type_count();
542+
encodable.ty().defined_type().borrow(i);
543+
index
544+
}
532545
};
533546

534547
types.insert(PtrKey(ty), index);
@@ -720,16 +733,17 @@ impl ArgumentImport<'_> {
720733
.unwrap()
721734
.as_component_instance_type()
722735
.unwrap()
723-
.exports(component.types.as_ref());
736+
.exports
737+
.iter();
724738

725739
let mut map = IndexMap::with_capacity(exports.len());
726-
for (name, url, ty) in exports {
740+
for (name, (url, ty)) in exports {
727741
map.insert(
728742
name.as_ref(),
729743
(
730744
url.as_ref().map(|u| u.as_str()).unwrap_or(""),
731745
*component,
732-
ty,
746+
*ty,
733747
),
734748
);
735749
}
@@ -756,13 +770,14 @@ impl ArgumentImport<'_> {
756770
);
757771
}
758772

759-
for (name, url, new_type) in new_component
773+
for (name, (url, new_type)) in new_component
760774
.types
761775
.type_from_id(id)
762776
.unwrap()
763777
.as_component_instance_type()
764778
.unwrap()
765-
.exports(new_component.types.as_ref())
779+
.exports
780+
.iter()
766781
{
767782
match exports.entry(name) {
768783
indexmap::map::Entry::Occupied(mut e) => {
@@ -771,7 +786,7 @@ impl ArgumentImport<'_> {
771786
existing_component,
772787
*existing_type,
773788
new_component,
774-
new_type,
789+
*new_type,
775790
) {
776791
Some((c, ty)) => {
777792
*existing_component = c;
@@ -789,7 +804,7 @@ impl ArgumentImport<'_> {
789804
e.insert((
790805
url.as_ref().map(|u| u.as_str()).unwrap_or(""),
791806
new_component,
792-
new_type,
807+
*new_type,
793808
));
794809
}
795810
}
@@ -1385,7 +1400,7 @@ impl<'a> CompositionGraphEncoder<'a> {
13851400
ComponentTypeRef::Module(_) => ("module", &mut self.modules),
13861401
ComponentTypeRef::Func(_) => ("function", &mut self.funcs),
13871402
ComponentTypeRef::Value(_) => ("value", &mut self.values),
1388-
ComponentTypeRef::Type(_, _) => ("type", &mut self.types),
1403+
ComponentTypeRef::Type(_) => ("type", &mut self.types),
13891404
ComponentTypeRef::Instance(_) => ("instance", &mut self.instances),
13901405
ComponentTypeRef::Component(_) => ("component", &mut self.components),
13911406
};

crates/wasm-compose/src/graph.rs

Lines changed: 21 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{
1010
sync::atomic::{AtomicUsize, Ordering},
1111
};
1212
use wasmparser::{
13-
types::{ComponentEntityType, ComponentInstanceType, Types, TypesRef},
13+
types::{ComponentEntityType, TypeId, Types, TypesRef},
1414
Chunk, ComponentExternalKind, ComponentTypeRef, Encoding, Parser, Payload, ValidPayload,
1515
Validator, WasmFeatures,
1616
};
@@ -277,7 +277,7 @@ impl<'a> Component<'a> {
277277
/// Finds a compatible instance export on the component for the given instance type.
278278
pub(crate) fn find_compatible_export(
279279
&self,
280-
ty: &ComponentInstanceType,
280+
ty: TypeId,
281281
types: TypesRef,
282282
) -> Option<ExportIndex> {
283283
self.exports
@@ -286,10 +286,12 @@ impl<'a> Component<'a> {
286286
if *kind != ComponentExternalKind::Instance {
287287
return false;
288288
}
289-
ComponentInstanceType::is_subtype_of(
290-
self.types.component_instance_at(*index).unwrap(),
289+
ComponentEntityType::is_subtype_of(
290+
&ComponentEntityType::Instance(
291+
self.types.component_instance_at(*index).unwrap(),
292+
),
291293
self.types.as_ref(),
292-
ty,
294+
&ComponentEntityType::Instance(ty),
293295
types,
294296
)
295297
})
@@ -298,18 +300,20 @@ impl<'a> Component<'a> {
298300

299301
/// Checks to see if an instance of this component would be a
300302
/// subtype of the given instance type.
301-
pub(crate) fn is_instance_subtype_of(
302-
&self,
303-
ty: &ComponentInstanceType,
304-
types: TypesRef,
305-
) -> bool {
306-
let exports = ty.exports(types);
303+
pub(crate) fn is_instance_subtype_of(&self, ty: TypeId, types: TypesRef) -> bool {
304+
let exports = types
305+
.type_from_id(ty)
306+
.unwrap()
307+
.as_component_instance_type()
308+
.unwrap()
309+
.exports
310+
.iter();
307311

308-
for (k, _, b) in exports {
312+
for (k, (_, b)) in exports {
309313
match self.exports.get_full(k.as_str()) {
310314
Some((ai, _, _)) => {
311315
let (_, _, a) = self.export_entity_type(ExportIndex(ai)).unwrap();
312-
if !ComponentEntityType::is_subtype_of(&a, self.types.as_ref(), &b, types) {
316+
if !ComponentEntityType::is_subtype_of(&a, self.types.as_ref(), b, types) {
313317
return false;
314318
}
315319
}
@@ -761,12 +765,7 @@ impl<'a> CompositionGraph<'a> {
761765
}
762766
} else {
763767
let ty = match import_ty {
764-
ComponentEntityType::Instance(id) => target_component
765-
.types
766-
.type_from_id(id)
767-
.unwrap()
768-
.as_component_instance_type()
769-
.unwrap(),
768+
ComponentEntityType::Instance(id) => id,
770769
_ => bail!(
771770
"source instance is not compatible with target {import_ty} import `{import_name}`",
772771
import_ty = type_desc(import_ty)
@@ -1262,7 +1261,7 @@ mod test {
12621261
)
12631262
(import "i4" (core module (;0;) (type 0)))
12641263
(type (;3;) (tuple u32 u32))
1265-
(import "i5" (type (eq 3)))
1264+
(import "i5" (type (;4;) (eq 3)))
12661265
(component (;1;)
12671266
(type (;0;) (tuple u32 u32))
12681267
(type (;1;)
@@ -1284,7 +1283,7 @@ mod test {
12841283
(module)
12851284
)
12861285
(import "i4" (core module (;0;) (type 0)))
1287-
(import "i5" (type (eq 0)))
1286+
(import "i5" (type (;4;) (eq 0)))
12881287
(export (;1;) "e1" (instance 0))
12891288
(export (;1;) "e2" (func 0))
12901289
(export (;1;) "e3" (component 0))
@@ -1312,7 +1311,7 @@ mod test {
13121311
(module)
13131312
)
13141313
(import "i4" (core module (;0;) (type 0)))
1315-
(import "i5" (type (eq 0)))
1314+
(import "i5" (type (;4;) (eq 0)))
13161315
)
13171316
(instance (;1;) (instantiate 1
13181317
(with "i1" (instance 0))

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::{encode_section, ComponentSection, ComponentSectionId, Encode};
1+
use crate::{encode_section, ComponentSection, ComponentSectionId, ComponentValType, Encode};
22

33
/// Represents options for canonical function definitions.
44
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
@@ -118,6 +118,32 @@ impl CanonicalFunctionSection {
118118
self.num_added += 1;
119119
self
120120
}
121+
122+
/// Defines a function which will create an owned handle to the resource
123+
/// specified by `ty_index`.
124+
pub fn resource_new(&mut self, ty_index: u32) -> &mut Self {
125+
self.bytes.push(0x02);
126+
ty_index.encode(&mut self.bytes);
127+
self.num_added += 1;
128+
self
129+
}
130+
131+
/// Defines a function which will drop the specified type of handle.
132+
pub fn resource_drop(&mut self, ty: ComponentValType) -> &mut Self {
133+
self.bytes.push(0x03);
134+
ty.encode(&mut self.bytes);
135+
self.num_added += 1;
136+
self
137+
}
138+
139+
/// Defines a function which will return the representation of the specified
140+
/// resource type.
141+
pub fn resource_rep(&mut self, ty_index: u32) -> &mut Self {
142+
self.bytes.push(0x04);
143+
ty_index.encode(&mut self.bytes);
144+
self.num_added += 1;
145+
self
146+
}
121147
}
122148

123149
impl Encode for CanonicalFunctionSection {

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

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@ use crate::{
66
/// Represents the possible type bounds for type references.
77
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
88
pub enum TypeBounds {
9-
/// The type is bounded by equality.
10-
Eq,
9+
/// The type is bounded by equality to the type index specified.
10+
Eq(u32),
11+
/// This type is a fresh resource type,
12+
SubResource,
1113
}
1214

1315
impl Encode for TypeBounds {
1416
fn encode(&self, sink: &mut Vec<u8>) {
1517
match self {
16-
Self::Eq => sink.push(0x00),
18+
Self::Eq(i) => {
19+
sink.push(0x00);
20+
i.encode(sink);
21+
}
22+
Self::SubResource => sink.push(0x01),
1723
}
1824
}
1925
}
@@ -32,9 +38,7 @@ pub enum ComponentTypeRef {
3238
/// The reference is to a value type.
3339
Value(ComponentValType),
3440
/// The reference is to a bounded type.
35-
///
36-
/// The index is expected to be a type index.
37-
Type(TypeBounds, u32),
41+
Type(TypeBounds),
3842
/// The reference is to an instance type.
3943
///
4044
/// The index is expected to be a type index to an instance type.
@@ -68,10 +72,7 @@ impl Encode for ComponentTypeRef {
6872
idx.encode(sink);
6973
}
7074
Self::Value(ty) => ty.encode(sink),
71-
Self::Type(bounds, idx) => {
72-
bounds.encode(sink);
73-
idx.encode(sink);
74-
}
75+
Self::Type(bounds) => bounds.encode(sink),
7576
}
7677
}
7778
}

0 commit comments

Comments
 (0)