Skip to content

Commit 145be84

Browse files
alexcrichtondicej
andauthored
Support special import names for unit futures/streams (#2390)
* Support special import names for unit futures/streams This commit enhances `wit-component`'s import detection to support "unit" `future` and `stream` types which don't have a payload in a special way. Previously it was required that these types were mentioned in some WIT somewhere to get connected to a function, but now it's possible to import these intrinsics directly even if the type isn't mentioned in WIT. The motivation for this change is to support guest language async runtimes which are world-agnostic to still operate on unit futures/streams as necessary without binding them to any particular WIT import or export. For example it's expected that unit streams are a primitive for inter-task communication in the component model. * Update crates/wit-component/src/validation.rs Co-authored-by: Joel Dice <[email protected]> --------- Co-authored-by: Joel Dice <[email protected]>
1 parent 0f3f212 commit 145be84

File tree

8 files changed

+291
-66
lines changed

8 files changed

+291
-66
lines changed

crates/wit-component/src/encoding.rs

Lines changed: 27 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,9 @@
7373
7474
use crate::StringEncoding;
7575
use crate::metadata::{self, Bindgen, ModuleMetadata};
76-
use crate::validation::{Export, ExportMap, Import, ImportInstance, ImportMap, PayloadInfo};
76+
use crate::validation::{
77+
Export, ExportMap, Import, ImportInstance, ImportMap, PayloadInfo, PayloadType,
78+
};
7779
use anyhow::{Context, Result, anyhow, bail};
7880
use indexmap::{IndexMap, IndexSet};
7981
use std::borrow::Cow;
@@ -1337,20 +1339,21 @@ impl<'a> EncodingState<'a> {
13371339
let metadata = self.info.module_metadata_for(*for_module);
13381340
let exports = self.info.exports_for(*for_module);
13391341
let instance_index = self.instance_for(*for_module);
1340-
let (encoding, realloc) = if info.imported {
1341-
(
1342-
metadata
1343-
.import_encodings
1344-
.get(resolve, &info.key, &info.function),
1345-
exports.import_realloc_for(info.interface, &info.function),
1346-
)
1347-
} else {
1348-
(
1349-
metadata
1350-
.export_encodings
1351-
.get(resolve, &info.key, &info.function),
1352-
exports.export_realloc_for(&info.key, &info.function),
1353-
)
1342+
let (encoding, realloc) = match &info.ty {
1343+
PayloadType::Type { function, .. } => {
1344+
if info.imported {
1345+
(
1346+
metadata.import_encodings.get(resolve, &info.key, function),
1347+
exports.import_realloc_for(info.interface, function),
1348+
)
1349+
} else {
1350+
(
1351+
metadata.export_encodings.get(resolve, &info.key, function),
1352+
exports.export_realloc_for(&info.key, function),
1353+
)
1354+
}
1355+
}
1356+
PayloadType::UnitFuture | PayloadType::UnitStream => (None, None),
13541357
};
13551358
let encoding = encoding.unwrap_or(StringEncoding::UTF8);
13561359
let realloc_index = realloc.map(|name| {
@@ -1506,16 +1509,19 @@ impl<'a> EncodingState<'a> {
15061509
// Finally though exports do use `info.interface`. Honestly I'm not
15071510
// really entirely sure why. Fuzzing is happy though, and truly
15081511
// everything must be ok if the fuzzers are happy, right?
1509-
let ComponentValType::Type(type_index) = if info.imported || info.interface.is_none() {
1512+
let mut encoder = if info.imported || info.interface.is_none() {
15101513
self.root_import_type_encoder(None)
15111514
} else {
15121515
self.root_export_type_encoder(info.interface)
1513-
}
1514-
.encode_valtype(resolve, &Type::Id(info.ty))?
1515-
else {
1516-
unreachable!()
15171516
};
1518-
Ok(type_index)
1517+
match info.ty {
1518+
PayloadType::Type { id, .. } => match encoder.encode_valtype(resolve, &Type::Id(id))? {
1519+
ComponentValType::Type(index) => Ok(index),
1520+
ComponentValType::Primitive(_) => unreachable!(),
1521+
},
1522+
PayloadType::UnitFuture => Ok(encoder.encode_unit_future()),
1523+
PayloadType::UnitStream => Ok(encoder.encode_unit_stream()),
1524+
}
15191525
}
15201526

15211527
/// This is a helper function that will declare any types necessary for

crates/wit-component/src/encoding/types.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ pub struct TypeEncodingMaps<'a> {
2020
pub id_to_index: HashMap<TypeId, u32>,
2121
pub def_to_index: HashMap<&'a TypeDefKind, u32>,
2222
pub func_type_map: HashMap<FunctionKey<'a>, u32>,
23+
pub unit_future: Option<u32>,
24+
pub unit_stream: Option<u32>,
2325
}
2426

2527
impl<'a> TypeEncodingMaps<'a> {
@@ -372,6 +374,26 @@ pub trait ValtypeEncoder<'a> {
372374
encoder.stream(ty);
373375
Ok(ComponentValType::Type(index))
374376
}
377+
378+
fn encode_unit_future(&mut self) -> u32 {
379+
if let Some(index) = self.type_encoding_maps().unit_future {
380+
return index;
381+
}
382+
let (index, encoder) = self.defined_type();
383+
encoder.future(None);
384+
self.type_encoding_maps().unit_future = Some(index);
385+
index
386+
}
387+
388+
fn encode_unit_stream(&mut self) -> u32 {
389+
if let Some(index) = self.type_encoding_maps().unit_stream {
390+
return index;
391+
}
392+
let (index, encoder) = self.defined_type();
393+
encoder.stream(None);
394+
self.type_encoding_maps().unit_stream = Some(index);
395+
index
396+
}
375397
}
376398

377399
pub struct RootTypeEncoder<'state, 'a> {

crates/wit-component/src/encoding/world.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use super::{Adapter, ComponentEncoder, LibraryInfo, RequiredOptions};
22
use crate::validation::{
3-
Import, ImportMap, ValidatedModule, validate_adapter_module, validate_module,
3+
Import, ImportMap, PayloadType, ValidatedModule, validate_adapter_module, validate_module,
44
};
55
use anyhow::{Context, Result};
66
use indexmap::{IndexMap, IndexSet};
@@ -396,7 +396,9 @@ impl<'a> ComponentWorld<'a> {
396396
| Import::FutureCancelWrite { info, async_: _ }
397397
| Import::FutureDropReadable(info)
398398
| Import::FutureDropWritable(info) => {
399-
live.add_type_id(resolve, info.ty);
399+
if let PayloadType::Type { id, .. } = info.ty {
400+
live.add_type_id(resolve, id);
401+
}
400402
}
401403

402404
// The `task.return` intrinsic needs to be able to refer to the

crates/wit-component/src/validation.rs

Lines changed: 74 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{ComponentEncoder, StringEncoding};
33
use anyhow::{Context, Result, anyhow, bail};
44
use indexmap::{IndexMap, IndexSet, map::Entry};
55
use std::fmt;
6-
use std::hash::{Hash, Hasher};
6+
use std::hash::Hash;
77
use std::mem;
88
use wasm_encoder::ExportKind;
99
use wasmparser::names::{ComponentName, ComponentNameKind};
@@ -135,16 +135,16 @@ pub enum ImportInstance {
135135
/// type appears, consider encoding them in the name mangling stream on an
136136
/// individual basis, similar to how we encode `error-context.*` built-in
137137
/// imports.
138-
#[derive(Debug, Eq, PartialEq, Clone)]
138+
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
139139
pub struct PayloadInfo {
140140
/// The original, mangled import name used to import this built-in
141141
/// (currently used only for hashing and debugging).
142142
pub name: String,
143143
/// The resolved type id for the `stream` or `future` type of interest.
144-
pub ty: TypeId,
145-
/// The component-level function import or export where the type appeared as
146-
/// a parameter or result type.
147-
pub function: String,
144+
///
145+
/// If `Unit{Future,Stream}` this means that it's a "unit" payload or has no associated
146+
/// type being sent.
147+
pub ty: PayloadType,
148148
/// The world key representing the import or export context of `function`.
149149
pub key: WorldKey,
150150
/// The interface that `function` was imported from or exported in, if any.
@@ -156,30 +156,37 @@ pub struct PayloadInfo {
156156
pub imported: bool,
157157
}
158158

159+
/// The type of future/stream referenced by a `PayloadInfo`
160+
#[derive(Debug, Eq, PartialEq, Clone, Hash)]
161+
pub enum PayloadType {
162+
/// This is a future or stream located in a `Resolve` where `id` points to
163+
/// either of `TypeDefKind::{Future, Stream}`.
164+
Type {
165+
id: TypeId,
166+
/// The component-level function import or export where the type
167+
/// appeared as a parameter or result type.
168+
function: String,
169+
},
170+
/// This is a `future` (no type)
171+
UnitFuture,
172+
/// This is a `stream` (no type)
173+
UnitStream,
174+
}
175+
159176
impl PayloadInfo {
160177
/// Returns the payload type that this future/stream type is using.
161178
pub fn payload(&self, resolve: &Resolve) -> Option<Type> {
162-
match resolve.types[self.ty].kind {
179+
let id = match self.ty {
180+
PayloadType::Type { id, .. } => id,
181+
PayloadType::UnitFuture | PayloadType::UnitStream => return None,
182+
};
183+
match resolve.types[id].kind {
163184
TypeDefKind::Future(payload) | TypeDefKind::Stream(payload) => payload,
164185
_ => unreachable!(),
165186
}
166187
}
167188
}
168189

169-
impl Hash for PayloadInfo {
170-
/// We derive `Hash` for this type by hand and exclude the `function` field
171-
/// because (A) `Function` doesn't implement `Hash` and (B) the other fields
172-
/// are sufficient to uniquely identify the type of interest, which function
173-
/// it appeared in, and which parameter or return type we found it in.
174-
fn hash<H: Hasher>(&self, state: &mut H) {
175-
self.name.hash(state);
176-
self.ty.hash(state);
177-
self.key.hash(state);
178-
self.interface.hash(state);
179-
self.imported.hash(state);
180-
}
181-
}
182-
183190
/// The different kinds of items that a module or an adapter can import.
184191
///
185192
/// This is intended to be an exhaustive definition of what can be imported into
@@ -1994,30 +2001,48 @@ impl Legacy {
19942001
) -> Option<PayloadInfo> {
19952002
// parse the `prefix` into `func_name` and `type_index`, bailing out
19962003
// with `None` if anything doesn't match.
1997-
let (type_index, func_name) = prefixed_integer(name, prefix)?;
1998-
let type_index = type_index as usize;
1999-
2000-
// Double-check that `func_name` is indeed a function name within
2001-
// this interface/world. Then additionally double-check that
2002-
// `type_index` is indeed a valid index for this function's type
2003-
// signature.
2004-
let function = get_function(
2005-
lookup_context.resolve,
2006-
lookup_context.world,
2007-
func_name,
2008-
lookup_context.id,
2009-
lookup_context.import,
2010-
)
2011-
.ok()?;
2012-
let ty = *function
2013-
.find_futures_and_streams(lookup_context.resolve)
2014-
.get(type_index)?;
2004+
let (index_or_unit, func_name) = prefixed_intrinsic(name, prefix)?;
2005+
let ty = match index_or_unit {
2006+
"unit" => {
2007+
if name.starts_with("[future") {
2008+
PayloadType::UnitFuture
2009+
} else if name.starts_with("[stream") {
2010+
PayloadType::UnitStream
2011+
} else {
2012+
unreachable!()
2013+
}
2014+
}
2015+
other => {
2016+
// Note that this is parsed as a `u32` to ensure that the
2017+
// integer parsing is the same across platforms regardless of
2018+
// the the width of `usize`.
2019+
let type_index = other.parse::<u32>().ok()? as usize;
2020+
2021+
// Double-check that `func_name` is indeed a function name within
2022+
// this interface/world. Then additionally double-check that
2023+
// `type_index` is indeed a valid index for this function's type
2024+
// signature.
2025+
let function = get_function(
2026+
lookup_context.resolve,
2027+
lookup_context.world,
2028+
func_name,
2029+
lookup_context.id,
2030+
lookup_context.import,
2031+
)
2032+
.ok()?;
2033+
PayloadType::Type {
2034+
id: *function
2035+
.find_futures_and_streams(lookup_context.resolve)
2036+
.get(type_index)?,
2037+
function: function.name.clone(),
2038+
}
2039+
}
2040+
};
20152041

20162042
// And if all that passes wrap up everything in a `PayloadInfo`.
20172043
Some(PayloadInfo {
20182044
name: name.to_string(),
20192045
ty,
2020-
function: function.name.clone(),
20212046
key: lookup_context
20222047
.key
20232048
.clone()
@@ -2631,14 +2656,20 @@ fn validate_func_sig(name: &str, expected: &FuncType, ty: &wasmparser::FuncType)
26312656
Ok(())
26322657
}
26332658

2634-
/// Matches `name` as `[${prefix}N]...`, and if found returns `(N, "...")`
2635-
fn prefixed_integer<'a>(name: &'a str, prefix: &str) -> Option<(u32, &'a str)> {
2659+
/// Matches `name` as `[${prefix}S]...`, and if found returns `("S", "...")`
2660+
fn prefixed_intrinsic<'a>(name: &'a str, prefix: &str) -> Option<(&'a str, &'a str)> {
26362661
assert!(prefix.starts_with("["));
26372662
assert!(prefix.ends_with("-"));
26382663
let suffix = name.strip_prefix(prefix)?;
26392664
let index = suffix.find(']')?;
26402665
let rest = &suffix[index + 1..];
2641-
let n = suffix[..index].parse().ok()?;
2666+
Some((&suffix[..index], rest))
2667+
}
2668+
2669+
/// Matches `name` as `[${prefix}N]...`, and if found returns `(N, "...")`
2670+
fn prefixed_integer<'a>(name: &'a str, prefix: &str) -> Option<(u32, &'a str)> {
2671+
let (suffix, rest) = prefixed_intrinsic(name, prefix)?;
2672+
let n = suffix.parse().ok()?;
26422673
Some((n, rest))
26432674
}
26442675

0 commit comments

Comments
 (0)