Skip to content

feat: (CM64) context.{set, get} accept i64 immediate#2502

Draft
michael-weigelt wants to merge 3 commits intobytecodealliance:mainfrom
michael-weigelt:mwe/context
Draft

feat: (CM64) context.{set, get} accept i64 immediate#2502
michael-weigelt wants to merge 3 commits intobytecodealliance:mainfrom
michael-weigelt:mwe/context

Conversation

@michael-weigelt
Copy link
Copy Markdown
Contributor

This is part of a series of PRs to enable verification of 64bit components.. The effort is tracked here.

This PR allows context.{set, get} to accept i64 as well as i32.

Comment on lines +373 to +381
;; Other value types (e.g. `f32`) are rejected regardless of the `cm64` feature.
(assert_invalid
(component
(core func (canon context.get f32 0)))
"`context.get` only supports `i32` or `i64`")
(assert_invalid
(component
(core func (canon context.set f32 0)))
"`context.set` only supports `i32` or `i64`")
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Contributor Author

@michael-weigelt michael-weigelt Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adambratschikaye
I think these ones should be malformed instead of invalid because the spec doesn't even permit the construction with other values. I guess this means you need to have a check in the parser itself as well.

michael-weigelt
Ah I wrongly assumed that this is extensible, coming from the resource rep type. Then maybe I should not use ValType anywhere at all. (?)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good question! By the letter of the spec here @adambratschikaye is correct where these tests should be assert_malformed rather than assert_invalid. Additionally following the letter of the spec all usage of ValType would want to get replaced with something else, yeah. (in Wasmtime we have an IndexType for memories/tables which may be another possible name).

cc @lukewagner on this specific point though, how do you feel about this? Should the binary format for context.{get,set} allow any core wasm value type in the binary encoding (and, thus, the text encoding too)? Or should the binary/text formats only allow i32 and i64? I could see us having a desire in the future to allow something like anyref here too for GC-using programs, in which case it'd be easiest to relax and allow any core value type here with validation restricting it to i32 and i64.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the binary format, we use the <valtype> opcodes for i32 and i64 (0x7f and 0x7e) directly in the decoding rule instead of decoding a generic <valtype> and then using validation rules to rule out everything except i32/i64 (just like we do for the (rep i32)/(rep i64) of a resource type). But in the AST, we have (canon context.{get,set} <valtype> ...) and verbiage about validation rejecting everything but i32/i64, so I suppose it's ambiguous. Probably we should align Binary.md with Explainer.md and go the decode+validate route, hence assert_invalid?

Practically speaking, I hope we don't do anyref since it'll raise gross questions of what to do if you, e.g., context.set anyref 0 and then context.get i32 0. With integers we say that they alias like 64-bit registers do on x64, but less clear what the right thing to do with anyref that avoids overhead. My thinking was that, if you wanted to store a reference, you'd store it in a table and then store the i32 index in TLS.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hadn't thought about the view-typing of slot 0 and slot 1 as different integer widths and that's a good point... Do you think it's feasible that we could guarantee that each slot is viewed as exactly one type in validation? For example within a component context.get 0 could only have one type?

Otherwise though I agree that syncing the text format and Binary.md makes sense, namely by changing Binary.md to use a valtype, which @michael-weigelt would keep this as assert_invalid yeah.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think it's feasible that we could guarantee that each slot is viewed as exactly one type in validation?

That's a good point; I suppose we could add a whole-component validation rule to that effect. It'd be the only validation rule like that and usually a rule like that has composability problems (compiling unrelated bits of code that disagree), but given that the use of context.{get,set} requires all the code in the same component to agree on the ABI anyways, I think it'd be be fine in this case. I think I'll work on a CM PR for this and the Binary.md fix.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, and sounds good! In that case @michael-weigelt let's keep all the ValType bits as-is in this PR

Comment on lines +838 to +844
fn type_name(ty: ValType) -> &'static str {
match ty {
ValType::I32 => "i32",
ValType::I64 => "i64",
_ => "<invalid>",
}
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should I define a new type instead of using ValType? Should we have a proper "MemoryType" that is future-proof? But then I am not sure if encoding/decoding will be more difficult, as that uses the known byte representation of i32/i64.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are several more awkward matches like this...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this is just a debug name, I think putting "whatever" here is fine, including dropping the value type entirely. In a debugging context for a particular module the value type probably isn't too interesting as the most interesting part is just that it's context.{get,set} in general. Putting <invalid> isn't the end of the world but if we stick with ValType and end up relaxing this in the future we'll almost surely forget to update this which is why I'd lean towards just dropping this part of the name for now.

/// A `context.get` intrinsic for the `i`th slot of task-local storage.
ContextGet(u32),
ContextGet {
/// The type of the slot. Currently only `ValType::I32` and
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "currently" should go, unless we know we want to extend this.

Comment on lines +61 to +62
ContextGet(crate::core::ValType<'a>, u32),
ContextSet(crate::core::ValType<'a>, u32),
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could introduce a CanonContextSet if that is preferred.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah this is fine 👍

self.canon_opts(&mut info.opts)?;
}
CoreFuncKind::ContextGet(_) | CoreFuncKind::ContextSet(_) => {}
CoreFuncKind::ContextGet(..) | CoreFuncKind::ContextSet(..) => {}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that technically this'll need to ge handled here with a resolve_ns one way or another. Otherwise specifying (ref $some-core-type) I think would panic during binary encoding

Comment on lines +2711 to 2728
/// Parses a `[context-get-<N>]` / `[context-set-<N>]` style name, optionally
/// carrying a type width infix: `[context-get-i64-<N>]`.
///
/// Returns the value type together with the numeric slot. Additional type
/// widths can be added here by extending the match below.
fn parse_context_name(name: &str, prefix: &str) -> Option<(ValType, u32)> {
let (suffix, rest) = prefixed_intrinsic(name, prefix)?;
let n = suffix.parse().ok()?;
Some((n, rest))
if !rest.is_empty() {
return None;
}
let (ty, slot) = match suffix.split_once('-') {
Some(("i64", slot)) => (ValType::I64, slot),
Some(("i32", slot)) => (ValType::I32, slot),
_ => (ValType::I32, "unreachable"),
};
let slot = slot.parse().ok()?;
Some((ty, slot))
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While context.{get,set} is currently gated behind component-model-async and technically not shipped we're late enough in the process that we can't easily change the names here. Given that, could this accept both the old name (e.g. [context-get-0]) but additionally accept new names (e.g. [context-get-{i32,i64}-0])? That way we can transition consumers to the new names and possibly remove support for the old names in the future

Comment on lines +119 to +131
/// Translates the `context.get`/`context.set` slot type recorded by the
/// validator into the `wasm_encoder::ValType` consumed by the component
/// builder. Only `i32` and `i64` currently make it past the validator; anything
/// else is a bug.
fn context_val_type(ty: wasmparser::ValType) -> ValType {
match ty {
wasmparser::ValType::I32 => ValType::I32,
wasmparser::ValType::I64 => ValType::I64,
_ => unreachable!(
"context.get/set slot type `{ty}` should have been rejected by the validator"
),
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this perhaps use the TryFrom impl to avoid the panic here?

Comment on lines +61 to +62
ContextGet(crate::core::ValType<'a>, u32),
ContextSet(crate::core::ValType<'a>, u32),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah this is fine 👍

Comment on lines +373 to +381
;; Other value types (e.g. `f32`) are rejected regardless of the `cm64` feature.
(assert_invalid
(component
(core func (canon context.get f32 0)))
"`context.get` only supports `i32` or `i64`")
(assert_invalid
(component
(core func (canon context.set f32 0)))
"`context.set` only supports `i32` or `i64`")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a good question! By the letter of the spec here @adambratschikaye is correct where these tests should be assert_malformed rather than assert_invalid. Additionally following the letter of the spec all usage of ValType would want to get replaced with something else, yeah. (in Wasmtime we have an IndexType for memories/tables which may be another possible name).

cc @lukewagner on this specific point though, how do you feel about this? Should the binary format for context.{get,set} allow any core wasm value type in the binary encoding (and, thus, the text encoding too)? Or should the binary/text formats only allow i32 and i64? I could see us having a desire in the future to allow something like anyref here too for GC-using programs, in which case it'd be easiest to relax and allow any core value type here with validation restricting it to i32 and i64.

Comment on lines +838 to +844
fn type_name(ty: ValType) -> &'static str {
match ty {
ValType::I32 => "i32",
ValType::I64 => "i64",
_ => "<invalid>",
}
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, since this is just a debug name, I think putting "whatever" here is fine, including dropping the value type entirely. In a debugging context for a particular module the value type probably isn't too interesting as the most interesting part is just that it's context.{get,set} in general. Putting <invalid> isn't the end of the world but if we stick with ValType and end up relaxing this in the future we'll almost surely forget to update this which is why I'd lean towards just dropping this part of the name for now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants