Skip to content

Commit f25d544

Browse files
committed
Add more details on the proposal.
1 parent 944d792 commit f25d544

2 files changed

Lines changed: 134 additions & 18 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
This repository is a clone of
1111
[`WebAssembly/spec`](https://github.com/WebAssembly/spec/). It is meant for
1212
discussion, prototype specification, and implementation of a proposal to add
13-
support for reading and writing multiple bytes at a time from `(array i8)`.
13+
support for reading and writing multiple bytes at a time from WebAssembly GC numeric arrays.
1414

1515
See the [overview](proposals/multibyte-array-access/Overview.md) for a
1616
high-level summary of the proposal.

proposals/multibyte-array-access/Overview.md

Lines changed: 133 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,156 @@
22

33
## Summary
44

5-
Reuse the exiting linear memory load/store instructions to allow multibyte access to `(array i8)`.
5+
Reuse the existing linear memory load/store instructions to allow multibyte access to Wasm GC numeric (and packed) array types (e.g., `i8`, `i16`, `i32`, `i64`, `f32`, `f64`).
66

77
## Motivation
88

9-
A number of languages currently use `(array i8)` as a backing store for custom data types and/or
9+
A number of languages currently use GC arrays (such as `(array i8)` or arrays of other numeric types) as a backing store for custom data types and/or
1010
byte buffers style objects (e.g., custom structs, Dart typed arrays, JVM byte arrays). Reading and
11-
writing to these custom data types requires performing sequences of single-byte operations that are
11+
writing to these custom data types requires performing sequences of single-element operations that are
1212
highly inefficient, and hinder performance.
1313

1414
## Proposal
1515

1616
### Semantics
1717

18-
The array versions of the load/store instructions will largely follow the same semantics as the
19-
linear memory instructions. The main differences are as follows:
20-
- a type index immediate and array reference argument are required before the address argument
21-
- a memory index or align immediate are not allowed
22-
23-
Valid array types for the instructions:
24-
- load:`expand($t) = array i8`
25-
- store:`expand($t) = array mut i8`
18+
The array versions of the load/store instructions follow the same data transformation semantics as the linear memory instructions, but they operate on GC arrays instead of linear memories.
19+
20+
#### Valid Array Types
21+
- **Load instructions**: The array type `$t` must expand to `array (mut? t_elem)` where `t_elem` is a numeric type (`i8`, `i16`, `i32`, `i64`, `f32`, `f64`).
22+
- **Store instructions**: The array type `$t` must expand to `array (mut t_elem)` where `t_elem` is a numeric type (`i8`, `i16`, `i32`, `i64`, `f32`, `f64`).
23+
24+
#### Validation
25+
26+
For type index `$t` and memory instruction `t_value.op`:
27+
1. The type `$t` must be a valid type index in the module, and must expand to an array type.
28+
2. The element type of the array must be a numeric type or packed numeric type.
29+
3. If it is a store instruction, the array type must be mutable.
30+
4. The instruction has the following input and output types on the stack:
31+
- **Load** (`t_value.load`):
32+
- Inputs: `[ref: (ref null $t), index: i32]`
33+
- Outputs: `[val: t_value]`
34+
- **Store** (`t_value.store`):
35+
- Inputs: `[ref: (ref null $t), index: i32, val: t_value]`
36+
- Outputs: `[]`
37+
- **Lane Load** (`v128.loadN_lane`):
38+
- Inputs: `[ref: (ref null $t), index: i32, vec: v128]`
39+
- Outputs: `[vec: v128]`
40+
- **Lane Store** (`v128.storeN_lane`):
41+
- Inputs: `[ref: (ref null $t), index: i32, vec: v128]`
42+
- Outputs: `[]`
43+
44+
#### Execution
45+
1. **Null Validation**:
46+
- If the array reference operand is null, execution traps with a null reference error.
47+
2. **Effective Address Calculation**:
48+
- The effective address $ea$ is calculated as $index + offset$, where $index$ is the `i32` stack operand (interpreted as an unsigned 32-bit integer) and $offset$ is the static byte offset immediate from `memarg`.
49+
3. **Bounds Check**:
50+
- Let $S$ be the size (in bytes) of the memory access (e.g., 4 for `i32.load`, 16 for `v128.load`).
51+
- Let $L$ be the length of the array (obtained via `array.len`).
52+
- Let $E$ be the element size in bytes of the array type `$t` (e.g., 1 for `i8`, 2 for `i16`, etc.).
53+
- The access is in bounds if $ea \ge 0$ and $ea + S \le L \times E$.
54+
- If the access is out of bounds, execution traps with an out-of-bounds array access error.
55+
4. **Operation**:
56+
- **Load**: Reads $S$ bytes from the array payload starting at byte offset $ea$, decodes them using little-endian byte order, and pushes the resulting value onto the stack.
57+
- **Store**: Encodes the value operand into $S$ bytes using little-endian byte order, and writes them to the array payload starting at byte offset $ea$.
58+
- **Lane Operations**: Loads/stores a single lane of the vector operand from/to the array payload at byte offset $ea$.
59+
5. **Alignment**:
60+
- The alignment value (expressed as power of 2 exponent in `memarg`) does not affect execution semantics, serving only as a hint for access alignment.
61+
62+
### Supported Instructions
63+
64+
The proposal supports the following memory load and store instructions:
65+
66+
| Instruction | Category | Operation |
67+
| :--- | :--- | :--- |
68+
| `i32.load` | Regular | Load |
69+
| `i64.load` | Regular | Load |
70+
| `f32.load` | Regular | Load |
71+
| `f64.load` | Regular | Load |
72+
| `i32.load8_s` | Regular | Load |
73+
| `i32.load8_u` | Regular | Load |
74+
| `i32.load16_s` | Regular | Load |
75+
| `i32.load16_u` | Regular | Load |
76+
| `i64.load8_s` | Regular | Load |
77+
| `i64.load8_u` | Regular | Load |
78+
| `i64.load16_s` | Regular | Load |
79+
| `i64.load16_u` | Regular | Load |
80+
| `i64.load32_s` | Regular | Load |
81+
| `i64.load32_u` | Regular | Load |
82+
| `i32.store` | Regular | Store |
83+
| `i64.store` | Regular | Store |
84+
| `f32.store` | Regular | Store |
85+
| `f64.store` | Regular | Store |
86+
| `i32.store8` | Regular | Store |
87+
| `i32.store16` | Regular | Store |
88+
| `i64.store8` | Regular | Store |
89+
| `i64.store16` | Regular | Store |
90+
| `i64.store32` | Regular | Store |
91+
| `v128.load` | SIMD | Load |
92+
| `v128.store` | SIMD | Store |
93+
| `v128.load8x8_s` | SIMD | Load |
94+
| `v128.load8x8_u` | SIMD | Load |
95+
| `v128.load16x4_s` | SIMD | Load |
96+
| `v128.load16x4_u` | SIMD | Load |
97+
| `v128.load32x2_s` | SIMD | Load |
98+
| `v128.load32x2_u` | SIMD | Load |
99+
| `v128.load8_splat` | SIMD | Load (Splat) |
100+
| `v128.load16_splat` | SIMD | Load (Splat) |
101+
| `v128.load32_splat` | SIMD | Load (Splat) |
102+
| `v128.load64_splat` | SIMD | Load (Splat) |
103+
| `v128.load32_zero` | SIMD | Load (Zero) |
104+
| `v128.load64_zero` | SIMD | Load (Zero) |
105+
| `v128.load8_lane` | SIMD | Load (Lane) |
106+
| `v128.load16_lane` | SIMD | Load (Lane) |
107+
| `v128.load32_lane` | SIMD | Load (Lane) |
108+
| `v128.load64_lane` | SIMD | Load (Lane) |
109+
| `v128.store8_lane` | SIMD | Store (Lane) |
110+
| `v128.store16_lane` | SIMD | Store (Lane) |
111+
| `v128.store32_lane` | SIMD | Store (Lane) |
112+
| `v128.store64_lane` | SIMD | Store (Lane) |
26113

27114
### Encoding
28115

29-
Use a reserved bit (4) in the `memarg` field to signal that the load/store instructions will be
30-
operating on an array. As mentioned above there will be a type index immediate and array reference
31-
argument.
116+
The array versions of the instructions reuse the existing opcodes for the standard memory instructions.
117+
118+
An instruction is determined to be an array access instruction if **bit 4** (value `0x10`) of the `flags` field in its `memarg` immediate is set.
119+
120+
When bit 4 of `flags` is set:
121+
- The instruction operates on a GC array instead of linear memory.
122+
- The `memarg` is parsed normally for `flags` and `offset` (both `u32` in LEB128). Bits 0-3 of `flags` represent the alignment exponent (expressed as `log_2(align)`).
123+
- **No memory index (`mem_idx`) is read/parsed**, even if bit 6 of `flags` is set. In a valid module, bit 6 of `flags` MUST be 0 when bit 4 is set. This avoids conflicts where the type index would be misparsed as a memory index.
124+
- Immediately following the `memarg` fields (`flags` and `offset`), a `typeidx` (representing the type index of the array type `$t`) is encoded as a `u32` (LEB128).
125+
- For lane instructions (e.g., `v128.load8_lane`, `v128.store8_lane`), the 1-byte `laneidx` is encoded immediately *after* the `typeidx`.
126+
127+
Thus, the binary format of a multibyte array instruction is:
128+
`instr ::= op memarg_array typeidx` (for regular and SIMD load/store)
129+
`instr ::= op memarg_array typeidx laneidx` (for SIMD lane load/store)
130+
131+
Where:
132+
- `memarg_array ::= flags:u32 offset:u32` (where `flags & 0x10 != 0`)
32133

33134
### Text Format Syntax
34135

35-
```
36-
i32.load (type <typeidx>) (<array ref>) (<address>)
136+
In the text format, the instructions reuse the keyword names of the standard memory instructions. A type index immediate `(type $t)` is required.
137+
138+
Since these instructions operate on GC arrays and not linear memory, **a memory index is not allowed**. An offset immediate (`offset=N`) and an alignment immediate (`align=N`) are allowed (with `offset` defaulting to `0` and `align` defaulting to the instruction's natural alignment if omitted).
139+
140+
For lane instructions, a lane index immediate is required at the end.
141+
142+
#### Folded (S-Expression) Form
143+
```wat
144+
;; Load
145+
(i32.load (type $t) [offset=N] [align=N] (local.get $array) (local.get $index))
146+
147+
;; Store
148+
(i32.store (type $t) [offset=N] [align=N] (local.get $array) (local.get $index) (local.get $val))
149+
150+
;; Lane Load
151+
(v128.load8_lane (type $t) [offset=N] [align=N] $lane (local.get $array) (local.get $index) (local.get $vec))
37152
38-
i32.store (type <typeidx>) (<array ref>) (<address>) (<value>)
153+
;; Lane Store
154+
(v128.store8_lane (type $t) [offset=N] [align=N] $lane (local.get $array) (local.get $index) (local.get $vec))
39155
```
40156

41157
## Alternatives

0 commit comments

Comments
 (0)