|
2 | 2 |
|
3 | 3 | ## Summary |
4 | 4 |
|
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`). |
6 | 6 |
|
7 | 7 | ## Motivation |
8 | 8 |
|
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 |
10 | 10 | 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 |
12 | 12 | highly inefficient, and hinder performance. |
13 | 13 |
|
14 | 14 | ## Proposal |
15 | 15 |
|
16 | 16 | ### Semantics |
17 | 17 |
|
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) | |
26 | 113 |
|
27 | 114 | ### Encoding |
28 | 115 |
|
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`) |
32 | 133 |
|
33 | 134 | ### Text Format Syntax |
34 | 135 |
|
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)) |
37 | 152 |
|
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)) |
39 | 155 | ``` |
40 | 156 |
|
41 | 157 | ## Alternatives |
|
0 commit comments