Skip to content

Commit 1db203a

Browse files
committed
feat: add stream.forward canon builtin
Implement parsing, validation, encoding, and printing support for the `stream.forward` built-in specified alongside `stream.splice` in WebAssembly/component-model#658: (canon stream.forward $streamT (core func $f)) where `$f` has type `(func (param i32 i32))`, taking a readable stream end and a writable stream end. Unlike `stream.splice`, which borrows both ends and reports the outcome back to the caller, `stream.forward` transfers both ends out of the calling instance and returns immediately with no result, so it takes neither a `n` parameter nor an `async` immediate. Like `stream.splice` it takes no `canonopt`s, since no elements pass through the caller's linear memory. The binary encoding uses the currently-unassigned opcode 0x2f, and validation gates the built-in behind the component model async and "more async builtins" features since it is not yet part of the Component Model specification. Assisted-by: claude:claude-fable-5 Signed-off-by: Roman Volosatovs <rvolosatovs@riseup.net>
1 parent 5a6af20 commit 1db203a

16 files changed

Lines changed: 167 additions & 0 deletions

File tree

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,12 @@ impl ComponentBuilder {
562562
self.core_funcs.add(Some("stream.splice"))
563563
}
564564

565+
/// Declares a new `stream.forward` intrinsic.
566+
pub fn stream_forward(&mut self, ty: u32) -> u32 {
567+
self.canonical_functions().stream_forward(ty);
568+
self.core_funcs.add(Some("stream.forward"))
569+
}
570+
565571
/// Declares a new `stream.cancel-read` intrinsic.
566572
pub fn stream_cancel_read(&mut self, ty: u32, async_: bool) -> u32 {
567573
self.canonical_functions().stream_cancel_read(ty, async_);

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,16 @@ impl CanonicalFunctionSection {
308308
self
309309
}
310310

311+
/// Defines a function to forward all remaining elements from the readable
312+
/// end of one `stream` to the writable end of another `stream` of the
313+
/// specified type, transferring both ends out of the calling instance.
314+
pub fn stream_forward(&mut self, ty: u32) -> &mut Self {
315+
self.bytes.push(0x2f);
316+
ty.encode(&mut self.bytes);
317+
self.num_added += 1;
318+
self
319+
}
320+
311321
/// Defines a function to cancel an in-progress read from a `stream` of the
312322
/// specified type.
313323
pub fn stream_cancel_read(&mut self, ty: u32, async_: bool) -> &mut Self {

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1033,6 +1033,9 @@ pub mod component_utils {
10331033
wasmparser::CanonicalFunction::StreamSplice { ty, async_ } => {
10341034
section.stream_splice(reencoder.component_type_index(ty), async_);
10351035
}
1036+
wasmparser::CanonicalFunction::StreamForward { ty } => {
1037+
section.stream_forward(reencoder.component_type_index(ty));
1038+
}
10361039
wasmparser::CanonicalFunction::StreamCancelRead { ty, async_ } => {
10371040
section.stream_cancel_read(ty, async_);
10381041
}

crates/wasmparser/src/readers/component/canonicals.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,17 @@ pub enum CanonicalFunction {
169169
/// `BLOCKED`.
170170
async_: bool,
171171
},
172+
/// A function to forward all remaining elements from the readable end of
173+
/// one `stream` to the writable end of another `stream` of the same
174+
/// specified type, transferring both ends out of the calling instance.
175+
///
176+
/// 🚧 This is an experimental builtin sketched in
177+
/// <https://github.com/WebAssembly/component-model/issues/658> and not yet
178+
/// part of the Component Model specification.
179+
StreamForward {
180+
/// The `stream` type to expect.
181+
ty: u32,
182+
},
172183
/// A function to cancel an in-progress read from a `stream` of the
173184
/// specified type.
174185
StreamCancelRead {
@@ -388,6 +399,7 @@ impl<'a> FromReader<'a> for CanonicalFunction {
388399
ty: reader.read()?,
389400
async_: reader.read()?,
390401
},
402+
0x2f => CanonicalFunction::StreamForward { ty: reader.read()? },
391403
0x11 => CanonicalFunction::StreamCancelRead {
392404
ty: reader.read()?,
393405
async_: reader.read()?,

crates/wasmparser/src/validator/component.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,7 @@ impl ComponentState {
12451245
CanonicalFunction::StreamSplice { ty, async_ } => {
12461246
self.stream_splice(ty, async_, types, offset)
12471247
}
1248+
CanonicalFunction::StreamForward { ty } => self.stream_forward(ty, types, offset),
12481249
CanonicalFunction::StreamCancelRead { ty, async_ } => {
12491250
self.stream_cancel_read(ty, async_, types, offset)
12501251
}
@@ -1759,6 +1760,28 @@ impl ComponentState {
17591760
Ok(())
17601761
}
17611762

1763+
fn stream_forward(&mut self, ty: u32, types: &mut TypeAlloc, offset: u64) -> Result<()> {
1764+
require_feature::cm_async(
1765+
self.features,
1766+
"`stream.forward` requires the component model async feature",
1767+
offset,
1768+
)?;
1769+
require_feature::cm_more_async_builtins(
1770+
self.features,
1771+
"`stream.forward` requires the component model more async builtins feature",
1772+
offset,
1773+
)?;
1774+
1775+
let ty = self.defined_type_at(ty, offset)?;
1776+
let ComponentDefinedType::Stream { .. } = &types[ty] else {
1777+
bail!(offset, "`stream.forward` requires a stream type")
1778+
};
1779+
1780+
self.core_funcs
1781+
.push(types.intern_func_type(FuncType::new([ValType::I32; 2], []), offset));
1782+
Ok(())
1783+
}
1784+
17621785
fn stream_cancel_read(
17631786
&mut self,
17641787
ty: u32,

crates/wasmprinter/src/component.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1057,6 +1057,11 @@ impl Printer<'_, '_> {
10571057
Ok(())
10581058
})?;
10591059
}
1060+
CanonicalFunction::StreamForward { ty } => {
1061+
self.print_intrinsic(state, "canon stream.forward ", &|me, state| {
1062+
me.print_idx(&state.component.type_names, ty)
1063+
})?;
1064+
}
10601065
CanonicalFunction::StreamCancelRead { ty, async_ } => {
10611066
self.print_intrinsic(state, "canon stream.cancel-read ", &|me, state| {
10621067
me.print_idx(&state.component.type_names, ty)?;

crates/wast/src/component/binary.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,6 +422,10 @@ impl<'a> Encoder<'a> {
422422
self.core_func_names.push(name);
423423
self.funcs.stream_splice((&info.ty).into(), info.async_);
424424
}
425+
CoreFuncKind::StreamForward(info) => {
426+
self.core_func_names.push(name);
427+
self.funcs.stream_forward((&info.ty).into());
428+
}
425429
CoreFuncKind::StreamCancelRead(info) => {
426430
self.core_func_names.push(name);
427431
self.funcs

crates/wast/src/component/func.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ pub enum CoreFuncKind<'a> {
6666
StreamRead(CanonStreamRead<'a>),
6767
StreamWrite(CanonStreamWrite<'a>),
6868
StreamSplice(CanonStreamSplice<'a>),
69+
StreamForward(CanonStreamForward<'a>),
6970
StreamCancelRead(CanonStreamCancelRead<'a>),
7071
StreamCancelWrite(CanonStreamCancelWrite<'a>),
7172
StreamDropReadable(CanonStreamDropReadable<'a>),
@@ -160,6 +161,8 @@ impl<'a> CoreFuncKind<'a> {
160161
Ok(CoreFuncKind::StreamWrite(parser.parse()?))
161162
} else if l.peek::<kw::stream_splice>()? {
162163
Ok(CoreFuncKind::StreamSplice(parser.parse()?))
164+
} else if l.peek::<kw::stream_forward>()? {
165+
Ok(CoreFuncKind::StreamForward(parser.parse()?))
163166
} else if l.peek::<kw::stream_cancel_read>()? {
164167
Ok(CoreFuncKind::StreamCancelRead(parser.parse()?))
165168
} else if l.peek::<kw::stream_cancel_write>()? {
@@ -744,6 +747,23 @@ impl<'a> Parse<'a> for CanonStreamSplice<'a> {
744747
}
745748
}
746749

750+
/// Information relating to the `stream.forward` intrinsic.
751+
#[derive(Debug)]
752+
pub struct CanonStreamForward<'a> {
753+
/// The stream type to forward.
754+
pub ty: ItemRef<'a, kw::r#type>,
755+
}
756+
757+
impl<'a> Parse<'a> for CanonStreamForward<'a> {
758+
fn parse(parser: Parser<'a>) -> Result<Self> {
759+
parser.parse::<kw::stream_forward>()?;
760+
761+
Ok(Self {
762+
ty: parser.parse::<IndexOrRef<'_, _>>()?.0,
763+
})
764+
}
765+
}
766+
747767
/// Information relating to the `stream.cancel-read` intrinsic.
748768
#[derive(Debug)]
749769
pub struct CanonStreamCancelRead<'a> {

crates/wast/src/component/resolve.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,9 @@ impl<'a> Resolver<'a> {
423423
CoreFuncKind::StreamSplice(info) => {
424424
self.component_item_ref(&mut info.ty)?;
425425
}
426+
CoreFuncKind::StreamForward(info) => {
427+
self.component_item_ref(&mut info.ty)?;
428+
}
426429
CoreFuncKind::StreamCancelRead(info) => {
427430
self.component_item_ref(&mut info.ty)?;
428431
}

crates/wast/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,7 @@ pub mod kw {
576576
custom_keyword!(stream_read = "stream.read");
577577
custom_keyword!(stream_write = "stream.write");
578578
custom_keyword!(stream_splice = "stream.splice");
579+
custom_keyword!(stream_forward = "stream.forward");
579580
custom_keyword!(stream_cancel_read = "stream.cancel-read");
580581
custom_keyword!(stream_cancel_write = "stream.cancel-write");
581582
custom_keyword!(stream_drop_readable = "stream.drop-readable");

0 commit comments

Comments
 (0)