Skip to content

Commit ad5726c

Browse files
authored
Implement context.{get,set} intrinsics (#2100)
* Implement `context.{get,set}` intrinsics This fills out the last missing set of intrinsics from the component-model async proposal. These two intrinsics are pretty simple and use preexisting idioms everywhere for integration into tooling. * A few more minor tests * Pin installation of cargo-component in CI
1 parent 5aa5315 commit ad5726c

File tree

22 files changed

+472
-96
lines changed

22 files changed

+472
-96
lines changed

.github/workflows/playground.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ jobs:
2828
node-version: 20
2929
- uses: bytecodealliance/wasmtime/.github/actions/[email protected]
3030
- uses: cargo-bins/[email protected]
31-
- run: cargo binstall cargo-component -y
31+
- run: cargo binstall cargo-component@0.20.0 -y
3232
- run: rustup component add rustfmt # needed for cargo-component, apparently?
3333
- run: rustup target add wasm32-wasip1
3434
- run: npm ci

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

+12
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,18 @@ impl ComponentBuilder {
407407
inc(&mut self.core_funcs)
408408
}
409409

410+
/// Declares a new `context.get` intrinsic.
411+
pub fn context_get(&mut self, i: u32) -> u32 {
412+
self.canonical_functions().context_get(i);
413+
inc(&mut self.core_funcs)
414+
}
415+
416+
/// Declares a new `context.set` intrinsic.
417+
pub fn context_set(&mut self, i: u32) -> u32 {
418+
self.canonical_functions().context_set(i);
419+
inc(&mut self.core_funcs)
420+
}
421+
410422
/// Declares a new `task.yield` intrinsic.
411423
pub fn yield_(&mut self, async_: bool) -> u32 {
412424
self.canonical_functions().yield_(async_);

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

+18
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,24 @@ impl CanonicalFunctionSection {
200200
self
201201
}
202202

203+
/// Defines a new `context.get` intrinsic of the ith slot.
204+
pub fn context_get(&mut self, i: u32) -> &mut Self {
205+
self.bytes.push(0x0a);
206+
self.bytes.push(0x7f);
207+
i.encode(&mut self.bytes);
208+
self.num_added += 1;
209+
self
210+
}
211+
212+
/// Defines a new `context.set` intrinsic of the ith slot.
213+
pub fn context_set(&mut self, i: u32) -> &mut Self {
214+
self.bytes.push(0x0b);
215+
self.bytes.push(0x7f);
216+
i.encode(&mut self.bytes);
217+
self.num_added += 1;
218+
self
219+
}
220+
203221
/// Defines a function which yields control to the host so that other tasks
204222
/// are able to make progress, if any.
205223
///

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

+6
Original file line numberDiff line numberDiff line change
@@ -969,6 +969,12 @@ pub mod component_utils {
969969
options.iter().map(|o| reencoder.canonical_option(*o)),
970970
);
971971
}
972+
wasmparser::CanonicalFunction::ContextGet(i) => {
973+
section.context_get(i);
974+
}
975+
wasmparser::CanonicalFunction::ContextSet(i) => {
976+
section.context_set(i);
977+
}
972978
wasmparser::CanonicalFunction::Yield { async_ } => {
973979
section.yield_(async_);
974980
}

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

+12
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@ pub enum CanonicalFunction {
9090
/// The canonical options for the function.
9191
options: Box<[CanonicalOption]>,
9292
},
93+
/// A `context.get` intrinsic for the `i`th slot of task-local storage.
94+
ContextGet(u32),
95+
/// A `context.set` intrinsic for the `i`th slot of task-local storage.
96+
ContextSet(u32),
9397
/// A function which yields control to the host so that other tasks are able
9498
/// to make progress, if any.
9599
Yield {
@@ -282,6 +286,14 @@ impl<'a> FromReader<'a> for CanonicalFunction {
282286
result: crate::read_resultlist(reader)?,
283287
options: read_opts(reader)?,
284288
},
289+
0x0a => match reader.read_u8()? {
290+
0x7f => CanonicalFunction::ContextGet(reader.read_var_u32()?),
291+
x => return reader.invalid_leading_byte(x, "context.get intrinsic type"),
292+
},
293+
0x0b => match reader.read_u8()? {
294+
0x7f => CanonicalFunction::ContextSet(reader.read_var_u32()?),
295+
x => return reader.invalid_leading_byte(x, "context.set intrinsic type"),
296+
},
285297
0x0c => CanonicalFunction::Yield {
286298
async_: reader.read()?,
287299
},

crates/wasmparser/src/validator/component.rs

+46
Original file line numberDiff line numberDiff line change
@@ -997,6 +997,8 @@ impl ComponentState {
997997
CanonicalFunction::TaskReturn { result, options } => {
998998
self.task_return(&result, &options, types, offset, features)
999999
}
1000+
CanonicalFunction::ContextGet(i) => self.context_get(i, types, offset, features),
1001+
CanonicalFunction::ContextSet(i) => self.context_set(i, types, offset, features),
10001002
CanonicalFunction::Yield { async_ } => self.yield_(async_, types, offset, features),
10011003
CanonicalFunction::SubtaskDrop => self.subtask_drop(types, offset, features),
10021004
CanonicalFunction::StreamNew { ty } => self.stream_new(ty, types, offset, features),
@@ -1246,6 +1248,50 @@ impl ComponentState {
12461248
Ok(())
12471249
}
12481250

1251+
fn context_get(
1252+
&mut self,
1253+
i: u32,
1254+
types: &mut TypeAlloc,
1255+
offset: usize,
1256+
features: &WasmFeatures,
1257+
) -> Result<()> {
1258+
if !features.cm_async() {
1259+
bail!(
1260+
offset,
1261+
"`context.get` requires the component model async feature"
1262+
)
1263+
}
1264+
if i > 2 {
1265+
bail!(offset, "`context.get` immediate larger than two: {i}")
1266+
}
1267+
1268+
self.core_funcs
1269+
.push(types.intern_func_type(FuncType::new([], [ValType::I32]), offset));
1270+
Ok(())
1271+
}
1272+
1273+
fn context_set(
1274+
&mut self,
1275+
i: u32,
1276+
types: &mut TypeAlloc,
1277+
offset: usize,
1278+
features: &WasmFeatures,
1279+
) -> Result<()> {
1280+
if !features.cm_async() {
1281+
bail!(
1282+
offset,
1283+
"`context.set` requires the component model async feature"
1284+
)
1285+
}
1286+
if i > 2 {
1287+
bail!(offset, "`context.set` immediate larger than two: {i}")
1288+
}
1289+
1290+
self.core_funcs
1291+
.push(types.intern_func_type(FuncType::new([ValType::I32], []), offset));
1292+
Ok(())
1293+
}
1294+
12491295
fn yield_(
12501296
&mut self,
12511297
async_: bool,

crates/wasmprinter/src/component.rs

+12
Original file line numberDiff line numberDiff line change
@@ -928,6 +928,18 @@ impl Printer<'_, '_> {
928928
Ok(())
929929
})?;
930930
}
931+
CanonicalFunction::ContextGet(i) => {
932+
self.print_intrinsic(state, "canon context.get", &|me, _state| {
933+
write!(me.result, " i32 {i}")?;
934+
Ok(())
935+
})?;
936+
}
937+
CanonicalFunction::ContextSet(i) => {
938+
self.print_intrinsic(state, "canon context.set", &|me, _state| {
939+
write!(me.result, " i32 {i}")?;
940+
Ok(())
941+
})?;
942+
}
931943
CanonicalFunction::Yield { async_ } => {
932944
self.print_intrinsic(state, "canon yield", &|me, _| {
933945
if async_ {

crates/wast/src/component/binary.rs

+8
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,14 @@ impl<'a> Encoder<'a> {
374374
info.opts.iter().map(Into::into),
375375
);
376376
}
377+
CoreFuncKind::ContextGet(i) => {
378+
self.core_func_names.push(name);
379+
self.funcs.context_get(*i);
380+
}
381+
CoreFuncKind::ContextSet(i) => {
382+
self.core_func_names.push(name);
383+
self.funcs.context_set(*i);
384+
}
377385
CoreFuncKind::Yield(info) => {
378386
self.core_func_names.push(name);
379387
self.funcs.yield_(info.async_);

crates/wast/src/component/func.rs

+10
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ pub enum CoreFuncKind<'a> {
5555
ThreadAvailableParallelism(CanonThreadAvailableParallelism),
5656
BackpressureSet,
5757
TaskReturn(CanonTaskReturn<'a>),
58+
ContextGet(u32),
59+
ContextSet(u32),
5860
Yield(CanonYield),
5961
SubtaskDrop,
6062
StreamNew(CanonStreamNew<'a>),
@@ -116,6 +118,14 @@ impl<'a> CoreFuncKind<'a> {
116118
Ok(CoreFuncKind::BackpressureSet)
117119
} else if l.peek::<kw::task_return>()? {
118120
Ok(CoreFuncKind::TaskReturn(parser.parse()?))
121+
} else if l.peek::<kw::context_get>()? {
122+
parser.parse::<kw::context_get>()?;
123+
parser.parse::<kw::i32>()?;
124+
Ok(CoreFuncKind::ContextGet(parser.parse()?))
125+
} else if l.peek::<kw::context_set>()? {
126+
parser.parse::<kw::context_set>()?;
127+
parser.parse::<kw::i32>()?;
128+
Ok(CoreFuncKind::ContextSet(parser.parse()?))
119129
} else if l.peek::<kw::yield_>()? {
120130
Ok(CoreFuncKind::Yield(parser.parse()?))
121131
} else if l.peek::<kw::subtask_drop>()? {

crates/wast/src/component/resolve.rs

+1
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,7 @@ impl<'a> Resolver<'a> {
401401
}
402402
self.canon_opts(&mut info.opts)?;
403403
}
404+
CoreFuncKind::ContextGet(_) | CoreFuncKind::ContextSet(_) => {}
404405
CoreFuncKind::StreamNew(info) => {
405406
self.resolve_ns(&mut info.ty, Ns::Type)?;
406407
}

crates/wast/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -589,6 +589,8 @@ pub mod kw {
589589
custom_keyword!(waitable_set_poll = "waitable-set.poll");
590590
custom_keyword!(waitable_set_drop = "waitable-set.drop");
591591
custom_keyword!(waitable_join = "waitable.join");
592+
custom_keyword!(context_get = "context.get");
593+
custom_keyword!(context_set = "context.set");
592594
}
593595

594596
/// Common annotations used to parse WebAssembly text files.

crates/wit-component/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ pretty_assertions = "1.3.0"
4242
wasmparser = { workspace = true, features = ['std', 'component-model', 'features'] }
4343
wasmprinter = { workspace = true, features = ['component-model'] }
4444
wat = { workspace = true, features = ['component-model'] }
45+
wasm-metadata = { workspace = true, features = ['oci'] }
4546

4647
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
4748
wasmtime = { workspace = true }

crates/wit-component/src/dummy.rs

+4
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,10 @@ fn push_root_async_intrinsics(dst: &mut String) {
387387
(import "$root" "[error-context-debug-message-utf16]" (func (param i32 i32)))
388388
(import "$root" "[error-context-debug-message-latin1+utf16]" (func (param i32 i32)))
389389
(import "$root" "[error-context-drop]" (func (param i32)))
390+
(import "$root" "[context-get-0]" (func (result i32)))
391+
(import "$root" "[context-get-1]" (func (result i32)))
392+
(import "$root" "[context-set-0]" (func (param i32)))
393+
(import "$root" "[context-set-1]" (func (param i32)))
390394
391395
;; deferred behind 🚝 or 🚟 upstream
392396
;;(import "$root" "[async-lower][waitable-set-wait]" (func (param i32 i32) (result i32)))

crates/wit-component/src/encoding.rs

+11-1
Original file line numberDiff line numberDiff line change
@@ -1811,6 +1811,14 @@ impl<'a> EncodingState<'a> {
18111811
let index = self.component.waitable_join();
18121812
Ok((ExportKind::Func, index))
18131813
}
1814+
Import::ContextGet(n) => {
1815+
let index = self.component.context_get(*n);
1816+
Ok((ExportKind::Func, index))
1817+
}
1818+
Import::ContextSet(n) => {
1819+
let index = self.component.context_set(*n);
1820+
Ok((ExportKind::Func, index))
1821+
}
18141822
}
18151823
}
18161824

@@ -2189,7 +2197,9 @@ impl<'a> Shims<'a> {
21892197
| Import::StreamCloseReadable { .. }
21902198
| Import::WaitableSetNew
21912199
| Import::WaitableSetDrop
2192-
| Import::WaitableJoin => {}
2200+
| Import::WaitableJoin
2201+
| Import::ContextGet(_)
2202+
| Import::ContextSet(_) => {}
21932203

21942204
// If `task.return` needs to be indirect then generate a shim
21952205
// for it, otherwise skip the shim and let it get materialized

crates/wit-component/src/validation.rs

+55-4
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,11 @@ pub enum Import {
255255
/// well.
256256
ExportedTaskReturn(WorldKey, Option<InterfaceId>, String, Option<Type>),
257257

258+
/// The `context.get` intrinsic for the nth slot of storage.
259+
ContextGet(u32),
260+
/// The `context.set` intrinsic for the nth slot of storage.
261+
ContextSet(u32),
262+
258263
/// A `canon backpressure.set` intrinsic.
259264
///
260265
/// This allows the guest to dynamically indicate whether it's ready for
@@ -634,6 +639,19 @@ impl ImportMap {
634639
return Ok(Import::ErrorContextDebugMessage { encoding });
635640
}
636641

642+
if let Some(i) = names.context_get(name) {
643+
validate_not_async()?;
644+
let expected = FuncType::new([], [ValType::I32]);
645+
validate_func_sig(name, &expected, ty)?;
646+
return Ok(Import::ContextGet(i));
647+
}
648+
if let Some(i) = names.context_set(name) {
649+
validate_not_async()?;
650+
let expected = FuncType::new([ValType::I32], []);
651+
validate_func_sig(name, &expected, ty)?;
652+
return Ok(Import::ContextSet(i));
653+
}
654+
637655
let key = WorldKey::Name(name.to_string());
638656
if let Some(WorldItem::Function(func)) = world.imports.get(&key) {
639657
validate_func(resolve, ty, func, abi)?;
@@ -829,10 +847,8 @@ impl ImportMap {
829847
let prefixed_payload = |prefix: &str| {
830848
// parse the `prefix` into `func_name` and `type_index`, bailing out
831849
// with `None` if anything doesn't match.
832-
let suffix = name.strip_prefix(prefix)?;
833-
let index = suffix.find(']')?;
834-
let func_name = &suffix[index + 1..];
835-
let type_index: usize = suffix[..index].parse().ok()?;
850+
let (type_index, func_name) = prefixed_integer(name, prefix)?;
851+
let type_index = type_index as usize;
836852

837853
// Double-check that `func_name` is indeed a function name within
838854
// this interface/world. Then additionally double-check that
@@ -1421,6 +1437,8 @@ trait NameMangling {
14211437
fn error_context_new(&self, s: &str) -> Option<StringEncoding>;
14221438
fn error_context_debug_message(&self, s: &str) -> Option<StringEncoding>;
14231439
fn error_context_drop(&self) -> Option<&str>;
1440+
fn context_get(&self, name: &str) -> Option<u32>;
1441+
fn context_set(&self, name: &str) -> Option<u32>;
14241442
fn module_to_interface(
14251443
&self,
14261444
module: &str,
@@ -1531,6 +1549,12 @@ impl NameMangling for Standard {
15311549
fn error_context_drop(&self) -> Option<&str> {
15321550
None
15331551
}
1552+
fn context_get(&self, _: &str) -> Option<u32> {
1553+
None
1554+
}
1555+
fn context_set(&self, _: &str) -> Option<u32> {
1556+
None
1557+
}
15341558
fn module_to_interface(
15351559
&self,
15361560
interface: &str,
@@ -1724,6 +1748,22 @@ impl NameMangling for Legacy {
17241748
fn error_context_drop(&self) -> Option<&str> {
17251749
Some("[error-context-drop]")
17261750
}
1751+
fn context_get(&self, name: &str) -> Option<u32> {
1752+
let (n, rest) = prefixed_integer(name, "[context-get-")?;
1753+
if rest.is_empty() {
1754+
Some(n)
1755+
} else {
1756+
None
1757+
}
1758+
}
1759+
fn context_set(&self, name: &str) -> Option<u32> {
1760+
let (n, rest) = prefixed_integer(name, "[context-set-")?;
1761+
if rest.is_empty() {
1762+
Some(n)
1763+
} else {
1764+
None
1765+
}
1766+
}
17271767
fn module_to_interface(
17281768
&self,
17291769
module: &str,
@@ -2009,6 +2049,17 @@ fn validate_func_sig(name: &str, expected: &FuncType, ty: &wasmparser::FuncType)
20092049
Ok(())
20102050
}
20112051

2052+
/// Matches `name` as `[${prefix}N]...`, and if found returns `(N, "...")`
2053+
fn prefixed_integer<'a>(name: &'a str, prefix: &str) -> Option<(u32, &'a str)> {
2054+
assert!(prefix.starts_with("["));
2055+
assert!(prefix.ends_with("-"));
2056+
let suffix = name.strip_prefix(prefix)?;
2057+
let index = suffix.find(']')?;
2058+
let rest = &suffix[index + 1..];
2059+
let n = suffix[..index].parse().ok()?;
2060+
Some((n, rest))
2061+
}
2062+
20122063
fn get_function<'a>(
20132064
resolve: &'a Resolve,
20142065
world: &'a World,

0 commit comments

Comments
 (0)