Skip to content

Commit 669d494

Browse files
authored
Restrict all context.{get,set} in same component to use same elem type (#645)
* Restrict all context.{get,set} in same component to use same elem type * Tweak Binary.md rules to use validation, instead of decoding, to gate i32/i64
1 parent 22e5a0b commit 669d494

5 files changed

Lines changed: 27 additions & 44 deletions

File tree

design/mvp/Binary.md

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -214,12 +214,9 @@ label' ::= len:<u32> l:<label> => l (if len = |l|)
214214
| 0x01 t:<T> => t
215215
valtype ::= i:<typeidx> => i
216216
| pvt:<primvaltype> => pvt
217-
resourcetype ::= 0x3f 0x7f f?:<core:funcidx>? => (resource (rep i32) (dtor f)?)
218-
| 0x3e 0x7f f:<core:funcidx>
219-
cb?:<core:funcidx>? => (resource (rep i32) (dtor async f (callback cb)?)) 🚝
220-
| 0x3f 0x7e f?:<core:funcidx>? => (resource (rep i64) (dtor f)?) 🐘
221-
| 0x3e 0x7e f:<core:funcidx>
222-
cb?:<core:funcidx>? => (resource (rep i64) (dtor async f (callback cb)?)) 🚝🐘
217+
resourcetype ::= 0x3f v:<valtype> f?:<core:funcidx>? => (resource (rep v) (dtor f)?)
218+
| 0x3e v:<valtype> f:<core:funcidx>
219+
cb?:<core:funcidx>? => (resource (rep v) (dtor async f (callback cb)?)) 🚝
223220
functype ::= 0x40 ps:<paramlist> rs:<resultlist> => (func ps rs)
224221
| 0x43 ps:<paramlist> rs:<resultlist> => (func async ps rs)
225222
paramlist ::= lt*:vec(<labelvaltype>) => (param lt)*
@@ -302,10 +299,8 @@ canon ::= 0x00 0x00 f:<core:funcidx> opts:<opts> ft:<typeidx> => (canon lift
302299
| 0x25 => (canon backpressure.dec (core func)) 🔀
303300
| 0x09 rs:<resultlist> opts:<opts> => (canon task.return rs opts (core func)) 🔀
304301
| 0x05 => (canon task.cancel (core func)) 🔀
305-
| 0x0a 0x7f i:<u32> => (canon context.get i32 i (core func)) 🔀
306-
| 0x0a 0x7e i:<u32> => (canon context.get i64 i (core func)) 🔀🐘
307-
| 0x0b 0x7f i:<u32> => (canon context.set i32 i (core func)) 🔀
308-
| 0x0b 0x7e i:<u32> => (canon context.set i64 i (core func)) 🔀🐘
302+
| 0x0a v:<valtype> i:<u32> => (canon context.get v i (core func)) 🔀
303+
| 0x0b v:<valtype> i:<u32> => (canon context.set v i (core func)) 🔀
309304
| 0x0c cancel?:<cancel?> => (canon thread.yield cancel? (core func)) 🔀
310305
| 0x06 async?:<async?> => (canon subtask.cancel async? (core func)) 🔀
311306
| 0x0d => (canon subtask.drop (core func)) 🔀

design/mvp/CanonicalABI.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3965,22 +3965,20 @@ For a canonical definition:
39653965
```
39663966
validation specifies:
39673967
* `$t` must be `i32` (see [here][thread-local storage]).
3968-
* 🐘 - `$t` may also be `i64`
3968+
* 🐘 - `$t` may also be `i64`. All `context.get` and `context.set` built-ins
3969+
defined in a single component must specify the same `$t`.
39693970
* `$i` must be less than `2`
39703971
* `$f` is given type `(func (result $t))`
39713972

39723973
Calling `$f` invokes the following function, which reads the [thread-local
3973-
storage] of the [current thread], taking only the low 32-bits if `$t` is `i32`:
3974+
storage] of the [current thread].
39743975
```python
3975-
MASK_32BIT = (1 << 32) - 1
3976-
39773976
def canon_context_get(t, i):
39783977
thread = current_thread()
39793978
assert(t == 'i32' or t == 'i64')
39803979
assert(i < len(thread.storage))
39813980
result = thread.storage[i]
3982-
if t == 'i32':
3983-
result &= MASK_32BIT
3981+
assert(result < (2 ** (ptr_size(t) * 8)))
39843982
return [result]
39853983
```
39863984

@@ -3993,7 +3991,8 @@ For a canonical definition:
39933991
```
39943992
validation specifies:
39953993
* `$t` must be `i32` (see [here][thread-local storage])
3996-
* 🐘 - `$t` may also be `i64`
3994+
* 🐘 - `$t` may also be `i64`. All `context.get` and `context.set` built-ins
3995+
defined in a single component must specify the same `$t`.
39973996
* `$i` must be less than `2`
39983997
* `$f` is given type `(func (param $v $t))`
39993998

@@ -4003,8 +4002,8 @@ storage] of the [current thread]:
40034002
def canon_context_set(t, i, v):
40044003
thread = current_thread()
40054004
assert(t == 'i32' or t == 'i64')
4006-
assert(v <= MASK_32BIT or t == 'i64')
40074005
assert(i < len(thread.storage))
4006+
assert(v < (2 ** (ptr_size(t) * 8)))
40084007
thread.storage[i] = v
40094008
return []
40104009
```

design/mvp/Concurrency.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ current thread's thread-local storage can be read and written from core wasm
421421
code by calling the [`context.get`] and [`context.set`] built-ins.
422422

423423
The thread-local storage array's length is currently fixed to contain exactly
424-
2 `i64`s with the goal of allowing this array to be stored inline in whatever
424+
2 elements with the goal of allowing this array to be stored inline in whatever
425425
existing runtime data structure is already efficiently reachable from ambient
426426
compiled wasm code. Because module instantiation is declarative in the
427427
Component Model, the imported `context.{get,set}` built-ins can be inlined by
@@ -433,11 +433,11 @@ natural place to store:
433433
thread-local features
434434

435435
Both of `context.{get,set}` take an immediate argument of `i32` or `i64` to
436-
indicate the return or argument type. `context.set i32` will zero the high
437-
bits of the stored value and `context.get i32` will only read the low bits of
438-
the stored value. Generally it is expected that 32-bit components always use
439-
the `i32` immediate and 64-bit components always use the `i64` immediate, but
440-
mixing these calls is still valid.
436+
indicate the return or argument type. As part of component-level validation, all
437+
`context.{get,set}` definitions within a single component are required to
438+
specify the *same* thread-local element type, so that there is no mixing of
439+
types between loads and stores. This restriction would allow Core WebAssembly
440+
reference types to be used as thread-local storage element types in the future.
441441

442442
When threads are created explicitly by `thread.new-indirect`, the lifetime of
443443
the thread-local storage array ends when the function passed to

design/mvp/Explainer.md

Lines changed: 7 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1566,14 +1566,9 @@ See the [concurrency explainer] for background.
15661566
The `context.get` built-in returns the `i`th element of the [current thread]'s
15671567
[thread-local storage] array. Validation currently restricts `i` to be less
15681568
than 2 and `T` to be `i32` (or, with 🐘, `i64`), but these restrictions may
1569-
be relaxed in the future.
1570-
1571-
Mixing `i32` and `i64` results in truncating or unsigned extending the
1572-
stored values:
1573-
* If `context.get i32 i` is called after `context.set i64 i v`,
1574-
only the low 32-bits are read (returning `i32.wrap_i64 v`).
1575-
* If `context.get i64 i` is called after `context.set i32 i v`,
1576-
the upper 32-bits will be zero (returning `i64.extend_i32_u v`).
1569+
be relaxed in the future. Additionally, component-level validation requires
1570+
that all `context.get` and `context.set` built-ins use the *same* `T`, so
1571+
that there is no mixing of writes with one type and reads with another.
15771572

15781573
For details, see [Thread-Local Storage] in the concurrency explainer and
15791574
[`canon_context_get`] in the Canonical ABI explainer.
@@ -1588,14 +1583,10 @@ For details, see [Thread-Local Storage] in the concurrency explainer and
15881583
The `context.set` built-in sets the `i`th element of the [current thread]'s
15891584
[thread-local storage] array to the value `v`. Validation currently restricts
15901585
`i` to be less than 2 and `T` to be `i32` (or, with 🐘, `i64`), but these
1591-
restrictions may be relaxed in the future.
1592-
1593-
Mixing `i32` and `i64` results in truncating or unsigned extending the
1594-
stored values:
1595-
* If `context.get i32 i` is called after `context.set i64 i v`,
1596-
only the low 32-bits are read (returning `i32.wrap_i64 v`).
1597-
* If `context.get i64 i` is called after `context.set i32 i v`,
1598-
the upper 32-bits will be zero (returning `i64.extend_i32_u v`).
1586+
restrictions may be relaxed in the future. Additionally, component-level
1587+
validation requires that all `context.get` and `context.set` built-ins use the
1588+
*same* `T`, so that there is no mixing of writes with one type and reads with
1589+
another.
15991590

16001591
For details, see [Thread-Local Storage] in the concurrency explainer and
16011592
[`canon_context_set`] in the Canonical ABI explainer.

design/mvp/canonical-abi/definitions.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2268,24 +2268,22 @@ def canon_resource_rep(rt, i):
22682268
return [h.rep]
22692269

22702270
### 🔀 `canon context.get`
2271-
MASK_32BIT = (1 << 32) - 1
22722271

22732272
def canon_context_get(t, i):
22742273
thread = current_thread()
22752274
assert(t == 'i32' or t == 'i64')
22762275
assert(i < len(thread.storage))
22772276
result = thread.storage[i]
2278-
if t == 'i32':
2279-
result &= MASK_32BIT
2277+
assert(result < (2 ** (ptr_size(t) * 8)))
22802278
return [result]
22812279

22822280
### 🔀 `canon context.set`
22832281

22842282
def canon_context_set(t, i, v):
22852283
thread = current_thread()
22862284
assert(t == 'i32' or t == 'i64')
2287-
assert(v <= MASK_32BIT or t == 'i64')
22882285
assert(i < len(thread.storage))
2286+
assert(v < (2 ** (ptr_size(t) * 8)))
22892287
thread.storage[i] = v
22902288
return []
22912289

0 commit comments

Comments
 (0)