From 45271d7cda70129c649c9549f019f59785f5a0a7 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 4 May 2026 11:13:19 -0500 Subject: [PATCH 1/2] Restrict all context.{get,set} in same component to use same elem type --- design/mvp/CanonicalABI.md | 15 +++++++-------- design/mvp/Concurrency.md | 12 ++++++------ design/mvp/Explainer.md | 23 +++++++---------------- design/mvp/canonical-abi/definitions.py | 6 ++---- 4 files changed, 22 insertions(+), 34 deletions(-) diff --git a/design/mvp/CanonicalABI.md b/design/mvp/CanonicalABI.md index 098e4c0c..9b245ac0 100644 --- a/design/mvp/CanonicalABI.md +++ b/design/mvp/CanonicalABI.md @@ -3878,22 +3878,20 @@ For a canonical definition: ``` validation specifies: * `$t` must be `i32` (see [here][thread-local storage]). - * 🐘 - `$t` may also be `i64` + * 🐘 - `$t` may also be `i64`. All `context.get` and `context.set` built-ins + defined in a single component must specify the same `$t`. * `$i` must be less than `2` * `$f` is given type `(func (result $t))` Calling `$f` invokes the following function, which reads the [thread-local -storage] of the [current thread], taking only the low 32-bits if `$t` is `i32`: +storage] of the [current thread]. ```python -MASK_32BIT = (1 << 32) - 1 - def canon_context_get(t, i): thread = current_thread() assert(t == 'i32' or t == 'i64') assert(i < len(thread.storage)) result = thread.storage[i] - if t == 'i32': - result &= MASK_32BIT + assert(result < (2 ** (ptr_size(t) * 8))) return [result] ``` @@ -3906,7 +3904,8 @@ For a canonical definition: ``` validation specifies: * `$t` must be `i32` (see [here][thread-local storage]) - * 🐘 - `$t` may also be `i64` + * 🐘 - `$t` may also be `i64`. All `context.get` and `context.set` built-ins + defined in a single component must specify the same `$t`. * `$i` must be less than `2` * `$f` is given type `(func (param $v $t))` @@ -3916,8 +3915,8 @@ storage] of the [current thread]: def canon_context_set(t, i, v): thread = current_thread() assert(t == 'i32' or t == 'i64') - assert(v <= MASK_32BIT or t == 'i64') assert(i < len(thread.storage)) + assert(v < (2 ** (ptr_size(t) * 8))) thread.storage[i] = v return [] ``` diff --git a/design/mvp/Concurrency.md b/design/mvp/Concurrency.md index d7ac155b..b79e8b16 100644 --- a/design/mvp/Concurrency.md +++ b/design/mvp/Concurrency.md @@ -420,7 +420,7 @@ current thread's thread-local storage can be read and written from core wasm code by calling the [`context.get`] and [`context.set`] built-ins. The thread-local storage array's length is currently fixed to contain exactly -2 `i64`s with the goal of allowing this array to be stored inline in whatever +2 elements with the goal of allowing this array to be stored inline in whatever existing runtime data structure is already efficiently reachable from ambient compiled wasm code. Because module instantiation is declarative in the Component Model, the imported `context.{get,set}` built-ins can be inlined by @@ -432,11 +432,11 @@ natural place to store: thread-local features Both of `context.{get,set}` take an immediate argument of `i32` or `i64` to -indicate the return or argument type. `context.set i32` will zero the high -bits of the stored value and `context.get i32` will only read the low bits of -the stored value. Generally it is expected that 32-bit components always use -the `i32` immediate and 64-bit components always use the `i64` immediate, but -mixing these calls is still valid. +indicate the return or argument type. As part of component-level validation, all +`context.{get,set}` definitions within a single component are required to +specify the *same* thread-local element type, so that there is no mixing of +types between loads and stores. This restriction would allow Core WebAssembly +reference types to be used as thread-local storage element types in the future. When threads are created explicitly by `thread.new-indirect`, the lifetime of the thread-local storage array ends when the function passed to diff --git a/design/mvp/Explainer.md b/design/mvp/Explainer.md index ceaab00f..b1d3332f 100644 --- a/design/mvp/Explainer.md +++ b/design/mvp/Explainer.md @@ -1566,14 +1566,9 @@ See the [concurrency explainer] for background. The `context.get` built-in returns the `i`th element of the [current thread]'s [thread-local storage] array. Validation currently restricts `i` to be less than 2 and `T` to be `i32` (or, with 🐘, `i64`), but these restrictions may -be relaxed in the future. - -Mixing `i32` and `i64` results in truncating or unsigned extending the -stored values: -* If `context.get i32 i` is called after `context.set i64 i v`, - only the low 32-bits are read (returning `i32.wrap_i64 v`). -* If `context.get i64 i` is called after `context.set i32 i v`, - the upper 32-bits will be zero (returning `i64.extend_i32_u v`). +be relaxed in the future. Additionally, component-level validation requires +that all `context.get` and `context.set` built-ins use the *same* `T`, so +that there is no mixing of writes with one type and reads with another. For details, see [Thread-Local Storage] in the concurrency explainer and [`canon_context_get`] in the Canonical ABI explainer. @@ -1588,14 +1583,10 @@ For details, see [Thread-Local Storage] in the concurrency explainer and The `context.set` built-in sets the `i`th element of the [current thread]'s [thread-local storage] array to the value `v`. Validation currently restricts `i` to be less than 2 and `T` to be `i32` (or, with 🐘, `i64`), but these -restrictions may be relaxed in the future. - -Mixing `i32` and `i64` results in truncating or unsigned extending the -stored values: -* If `context.get i32 i` is called after `context.set i64 i v`, - only the low 32-bits are read (returning `i32.wrap_i64 v`). -* If `context.get i64 i` is called after `context.set i32 i v`, - the upper 32-bits will be zero (returning `i64.extend_i32_u v`). +restrictions may be relaxed in the future. Additionally, component-level +validation requires that all `context.get` and `context.set` built-ins use the +*same* `T`, so that there is no mixing of writes with one type and reads with +another. For details, see [Thread-Local Storage] in the concurrency explainer and [`canon_context_set`] in the Canonical ABI explainer. diff --git a/design/mvp/canonical-abi/definitions.py b/design/mvp/canonical-abi/definitions.py index 8d8bd156..3ac09302 100644 --- a/design/mvp/canonical-abi/definitions.py +++ b/design/mvp/canonical-abi/definitions.py @@ -2236,15 +2236,13 @@ def canon_resource_rep(rt, i): return [h.rep] ### 🔀 `canon context.get` -MASK_32BIT = (1 << 32) - 1 def canon_context_get(t, i): thread = current_thread() assert(t == 'i32' or t == 'i64') assert(i < len(thread.storage)) result = thread.storage[i] - if t == 'i32': - result &= MASK_32BIT + assert(result < (2 ** (ptr_size(t) * 8))) return [result] ### 🔀 `canon context.set` @@ -2252,8 +2250,8 @@ def canon_context_get(t, i): def canon_context_set(t, i, v): thread = current_thread() assert(t == 'i32' or t == 'i64') - assert(v <= MASK_32BIT or t == 'i64') assert(i < len(thread.storage)) + assert(v < (2 ** (ptr_size(t) * 8))) thread.storage[i] = v return [] From c7009997da5816b9d0685a4b7878bc22fce17db8 Mon Sep 17 00:00:00 2001 From: Luke Wagner Date: Mon, 4 May 2026 11:35:51 -0500 Subject: [PATCH 2/2] Tweak Binary.md rules to use validation, instead of decoding, to gate i32/i64 --- design/mvp/Binary.md | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/design/mvp/Binary.md b/design/mvp/Binary.md index 8a8ccd28..28e94659 100644 --- a/design/mvp/Binary.md +++ b/design/mvp/Binary.md @@ -214,12 +214,9 @@ label' ::= len: l: