gofront 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ // src/backend/js/stdlib/simd.js
2
+ // CodeGen for the `gofront/simd` package in JS packages.
3
+
4
+ /** @typedef {import('../index.js').CodeGen} CodeGen */
5
+
6
+ /** @type {ThisType<CodeGen>} */
7
+ export const simdMethods = {
8
+ _genSimd(fn, a) {
9
+ this.useHelper("simd");
10
+ const args = a();
11
+ switch (fn) {
12
+ case "Splat":
13
+ return `__simd_splat(${args[0]})`;
14
+ case "NewF32x4":
15
+ return `__simd_f32x4(${args[0]}, ${args[1]}, ${args[2]}, ${args[3]})`;
16
+ case "LoadF32x4":
17
+ return `__simd_load(${args[0]}, ${args[1]})`;
18
+ case "StoreF32x4":
19
+ return `__simd_store(${args[0]}, ${args[1]}, ${args[2]})`;
20
+ default:
21
+ return undefined;
22
+ }
23
+ },
24
+ };
@@ -147,6 +147,11 @@ function classifyType(goType, mod) {
147
147
  return classifyFunc(t, mod);
148
148
  case "interface":
149
149
  return classifyInterface(t, mod);
150
+ case "simd":
151
+ return {
152
+ k: "unsupported",
153
+ what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
154
+ };
150
155
  default:
151
156
  return {
152
157
  k: "unsupported",
@@ -211,6 +216,16 @@ function classifyBasic(n) {
211
216
  }
212
217
 
213
218
  function classifyNamed(t, mod) {
219
+ if (
220
+ t.name === "simd.F32x4" ||
221
+ t.name === "F32x4" ||
222
+ t.underlying?.kind === "simd"
223
+ ) {
224
+ return {
225
+ k: "unsupported",
226
+ what: "simd.F32x4 cannot cross the JS↔WASM boundary in 1.7.0 (use shared.Float32 instead)",
227
+ };
228
+ }
214
229
  if (t.name === "error") return { k: "error", wType: "anyref" };
215
230
  if (t.name === "time.Duration")
216
231
  return { k: "i64", unsigned: false, wType: "i64" };
@@ -506,6 +521,9 @@ function literalConst(spec, i) {
506
521
  }
507
522
 
508
523
  function unsupportedMsg(where, what) {
524
+ if (typeof what === "string" && what.includes("boundary")) {
525
+ return `${where}: ${what}`;
526
+ }
509
527
  return `${where}: ${what} is not yet supported across the wasm boundary (planned)`;
510
528
  }
511
529
 
@@ -930,6 +930,9 @@ export class BuiltinsEmitter {
930
930
  case "base64":
931
931
  this.emitBase64Call(func.field, call, targetWasmType);
932
932
  return true;
933
+ case "simd":
934
+ this.emitSimdPackageCall(func.field, args, targetWasmType);
935
+ return true;
933
936
  default:
934
937
  return false;
935
938
  }
@@ -946,6 +949,20 @@ export class BuiltinsEmitter {
946
949
  return true;
947
950
  }
948
951
  const recvTypeName = this._getReceiverTypeName(recvType, func.expr);
952
+ if (
953
+ recvTypeName === "simd.F32x4" ||
954
+ recvTypeName === "gofront/simd.F32x4" ||
955
+ recvType?.kind === "simd" ||
956
+ recvType?.underlying?.kind === "simd" ||
957
+ recvType?.name === "simd.F32x4"
958
+ ) {
959
+ return this.emitSimdMethodCall(
960
+ func.field,
961
+ func.expr,
962
+ args,
963
+ targetWasmType,
964
+ );
965
+ }
949
966
  if (
950
967
  this._emitStdlibReceiverMethod(
951
968
  recvTypeName,
@@ -275,6 +275,17 @@ export class ExprsEmitter {
275
275
 
276
276
  emitCompositeLit(lit) {
277
277
  const litType = lit._type ?? lit.typeExpr?._type ?? lit.typeExpr;
278
+ if (
279
+ litType?.kind === "simd" ||
280
+ litType?.name === "simd.F32x4" ||
281
+ litType?.underlying?.kind === "simd" ||
282
+ (lit.typeExpr?.kind === "SelectorExpr" &&
283
+ lit.typeExpr.field === "F32x4" &&
284
+ lit.typeExpr.expr?.name === "simd")
285
+ ) {
286
+ this.emitSimdCompositeLit(lit);
287
+ return;
288
+ }
278
289
  if (isMapType(litType, this.mod.checker)) {
279
290
  const { keyType, valType } = getMapKeyValTypes(litType, this.mod.checker);
280
291
  const mapInfo = this.mod.getMapType(keyType, valType);
@@ -14,6 +14,7 @@ import {
14
14
  ExtendedPackagesEmitter,
15
15
  IoEmitter,
16
16
  PurePackagesEmitter,
17
+ SimdEmitter,
17
18
  StdlibCoreEmitter,
18
19
  TimeEmitter,
19
20
  } from "./stdlib/index.js";
@@ -958,6 +959,11 @@ export class FunctionEmitter {
958
959
  if (recvType) {
959
960
  let t = recvType;
960
961
  while (t.kind === "pointer") t = t.base;
962
+ if (t.kind === "SelectorExpr") {
963
+ const pkg = t.expr?.name;
964
+ if (pkg) return `${pkg}.${t.field}`;
965
+ }
966
+ if (t.kind === "simd") return "simd.F32x4";
961
967
  if (t.name) return t.name;
962
968
  if (t.kind === "named") return t.name;
963
969
  if (t.kind === "TypeName" || t.kind === "Ident") return t.name;
@@ -1106,6 +1112,8 @@ export class FunctionEmitter {
1106
1112
  this.pushInstruction({ op: "f32.const", value: 0.0 });
1107
1113
  } else if (wType === "f64") {
1108
1114
  this.pushInstruction({ op: "f64.const", value: 0.0 });
1115
+ } else if (wType === "v128") {
1116
+ this.pushInstruction({ op: "v128.const", bytes: new Array(16).fill(0) });
1109
1117
  } else if (
1110
1118
  typeof wType === "object" &&
1111
1119
  wType !== null &&
@@ -1402,3 +1410,4 @@ copyMethods(FunctionEmitter, IoEmitter);
1402
1410
  copyMethods(FunctionEmitter, TimeEmitter);
1403
1411
  copyMethods(FunctionEmitter, ExtendedPackagesEmitter);
1404
1412
  copyMethods(FunctionEmitter, SharedEmitter);
1413
+ copyMethods(FunctionEmitter, SimdEmitter);
@@ -393,6 +393,49 @@ const GC_OPCODES = {
393
393
  "extern.convert_any": 0x1b,
394
394
  };
395
395
 
396
+ // 0xFD prefixed SIMD instructions (Wasm SIMD / v128)
397
+ const SIMD_OPCODES = {
398
+ "v128.load": 0,
399
+ "v128.store": 11,
400
+ "v128.const": 12,
401
+ "i8x16.shuffle": 13,
402
+ "i8x16.swizzle": 14,
403
+ "i8x16.splat": 15,
404
+ "i16x8.splat": 16,
405
+ "i32x4.splat": 17,
406
+ "i64x2.splat": 18,
407
+ "f32x4.splat": 19,
408
+ "f64x2.splat": 20,
409
+ "i8x16.extract_lane_s": 21,
410
+ "i8x16.extract_lane_u": 22,
411
+ "i8x16.replace_lane": 23,
412
+ "i16x8.extract_lane_s": 24,
413
+ "i16x8.extract_lane_u": 25,
414
+ "i16x8.replace_lane": 26,
415
+ "i32x4.extract_lane": 27,
416
+ "i32x4.replace_lane": 28,
417
+ "i64x2.extract_lane": 29,
418
+ "i64x2.replace_lane": 30,
419
+ "f32x4.extract_lane": 31,
420
+ "f32x4.replace_lane": 32,
421
+ "v128.not": 77,
422
+ "v128.and": 78,
423
+ "v128.andnot": 79,
424
+ "v128.or": 80,
425
+ "v128.xor": 81,
426
+ "f32x4.abs": 224,
427
+ "f32x4.neg": 225,
428
+ "f32x4.sqrt": 227,
429
+ "f32x4.add": 228,
430
+ "f32x4.sub": 229,
431
+ "f32x4.mul": 230,
432
+ "f32x4.div": 231,
433
+ "f32x4.min": 232,
434
+ "f32x4.max": 233,
435
+ "f32x4.pmin": 234,
436
+ "f32x4.pmax": 235,
437
+ };
438
+
396
439
  // Natural alignment (log2) per load/store opcode; memarg = align, offset.
397
440
  const MEM_ALIGN = {
398
441
  "i32.load": 2,
@@ -433,6 +476,48 @@ function encodeInstruction(inst) {
433
476
  return [0xfc, ...encodeU32LEB(SAT_TRUNC_OPCODES[op])];
434
477
  }
435
478
 
479
+ // 2. Check SIMD opcodes (0xFD prefix)
480
+ if (SIMD_OPCODES[op] !== undefined) {
481
+ const subOp = SIMD_OPCODES[op];
482
+ const bytes = [0xfd, ...encodeU32LEB(subOp)];
483
+ if (op === "v128.load" || op === "v128.store") {
484
+ bytes.push(...encodeU32LEB(inst.align ?? 2));
485
+ bytes.push(...encodeU32LEB(inst.offset ?? 0));
486
+ return bytes;
487
+ }
488
+ if (op === "v128.const") {
489
+ const prefixLen = bytes.length;
490
+ if (inst.bytes) {
491
+ for (let i = 0; i < 16; i++) {
492
+ bytes.push(inst.bytes[i] ?? 0);
493
+ }
494
+ } else if (inst.values) {
495
+ const vals = inst.values.slice(0, 4);
496
+ for (const val of vals) {
497
+ bytes.push(...encodeF32(val));
498
+ }
499
+ while (bytes.length < prefixLen + 16) {
500
+ bytes.push(0);
501
+ }
502
+ } else {
503
+ for (let i = 0; i < 16; i++) bytes.push(0);
504
+ }
505
+ return bytes;
506
+ }
507
+ if (op === "i8x16.shuffle") {
508
+ const lanes = inst.lanes ?? inst.indices ?? [];
509
+ for (let i = 0; i < 16; i++) {
510
+ bytes.push(lanes[i] ?? 0);
511
+ }
512
+ return bytes;
513
+ }
514
+ if (op.includes("extract_lane") || op.includes("replace_lane")) {
515
+ bytes.push((inst.lane ?? inst.index ?? 0) & 0xff);
516
+ return bytes;
517
+ }
518
+ return bytes;
519
+ }
520
+
436
521
  // 2. Check WasmGC opcodes (0xFB prefix)
437
522
  if (GC_OPCODES[op] !== undefined) {
438
523
  const subOp = GC_OPCODES[op];
@@ -24,8 +24,8 @@ import { join } from "node:path";
24
24
  import { GOFRONT_SECTION_BYTES, isGoFrontWasm } from "./encode.js";
25
25
 
26
26
  // Binaryen feature bitmask: GC (1024) | ReferenceTypes (256) | BulkMemory (16) | Multivalue (512) |
27
- // ExceptionHandling (64) | MutableGlobals (2) | NontrappingFPToInt (4) | TailCall (128) = 2006
28
- export const DEFAULT_WASM_FEATURES = 2006;
27
+ // ExceptionHandling (64) | MutableGlobals (2) | NontrappingFPToInt (4) | TailCall (128) | SIMD (8) = 2014
28
+ export const DEFAULT_WASM_FEATURES = 2014;
29
29
 
30
30
  let _nativeWasmOptAvailable = null;
31
31
  let _npmWasmOptPath = null;
@@ -98,6 +98,7 @@ function runWasmOpt(
98
98
  "--enable-mutable-globals",
99
99
  "--enable-nontrapping-float-to-int",
100
100
  "--enable-tail-call",
101
+ "--enable-simd",
101
102
  ];
102
103
  if (options.gufa !== false) {
103
104
  args.push("--gufa");
@@ -141,6 +142,7 @@ function runWasmOpt(
141
142
  "--enable-mutable-globals",
142
143
  "--enable-nontrapping-float-to-int",
143
144
  "--enable-tail-call",
145
+ "--enable-simd",
144
146
  output,
145
147
  "-S",
146
148
  "-o",
@@ -13,6 +13,7 @@ import { BINARY_MATH_IMPORTS, StdlibCoreEmitter } from "./core.js";
13
13
  import { IoEmitter } from "./io.js";
14
14
  import { ExtendedPackagesEmitter } from "./packages.js";
15
15
  import { PurePackagesEmitter } from "./pure.js";
16
+ import { SimdEmitter } from "./simd.js";
16
17
  import { TimeEmitter } from "./time.js";
17
18
 
18
19
  // Aliases for backwards compatibility
@@ -24,6 +25,7 @@ export {
24
25
  IoEmitter as Phase7Emitter,
25
26
  PurePackagesEmitter,
26
27
  PurePackagesEmitter as Phase6Emitter,
28
+ SimdEmitter,
27
29
  StdlibCoreEmitter,
28
30
  StdlibCoreEmitter as StdlibEmitter,
29
31
  TimeEmitter,
@@ -37,4 +39,5 @@ export const stdlibEmitters = [
37
39
  IoEmitter,
38
40
  TimeEmitter,
39
41
  ExtendedPackagesEmitter,
42
+ SimdEmitter,
40
43
  ];
@@ -0,0 +1,234 @@
1
+ // src/backend/wasm/stdlib/simd.js
2
+ // FunctionEmitter mixin: gofront/simd (F32x4 vector math, load/store, composite literals).
3
+
4
+ const SIMD_BINARY_OPS = {
5
+ Add: "f32x4.add",
6
+ Sub: "f32x4.sub",
7
+ Mul: "f32x4.mul",
8
+ Div: "f32x4.div",
9
+ Min: "f32x4.min",
10
+ Max: "f32x4.max",
11
+ };
12
+
13
+ export class SimdEmitter {
14
+ emitSimdCompositeLit(lit) {
15
+ const elems = lit.elems ?? [];
16
+ if (elems.length === 0) {
17
+ this.pushInstruction({ op: "v128.const", bytes: new Array(16).fill(0) });
18
+ return;
19
+ }
20
+
21
+ const constVals = elems.map((e) => this._getConstFloatValue(e));
22
+ const allConst = constVals.every((v) => v !== null);
23
+
24
+ if (allConst) {
25
+ const vals = [0, 0, 0, 0];
26
+ for (let i = 0; i < Math.min(elems.length, 4); i++) {
27
+ vals[i] = constVals[i];
28
+ }
29
+ this.pushInstruction({ op: "v128.const", values: vals });
30
+ return;
31
+ }
32
+
33
+ // Runtime elements: splat lane 0, replace lanes 1, 2, 3
34
+ this.emitExpr(elems[0], "f32");
35
+ this.pushInstruction("f32x4.splat");
36
+ for (let i = 1; i < 4; i++) {
37
+ if (elems[i]) {
38
+ this.emitExpr(elems[i], "f32");
39
+ } else {
40
+ this.pushInstruction({ op: "f32.const", value: 0.0 });
41
+ }
42
+ this.pushInstruction({ op: "f32x4.replace_lane", lane: i });
43
+ }
44
+ }
45
+
46
+ _emitSimdDot(recvExpr, otherExpr, count) {
47
+ this.emitExpr(recvExpr, "v128");
48
+ this.emitExpr(otherExpr, "v128");
49
+ this.pushInstruction("f32x4.mul");
50
+ const tmp = this.acquireTemp("v128");
51
+ this.pushInstruction({ op: "local.set", index: tmp });
52
+ this.pushInstruction({ op: "local.get", index: tmp });
53
+ this.pushInstruction({ op: "f32x4.extract_lane", lane: 0 });
54
+ for (let i = 1; i < count; i++) {
55
+ this.pushInstruction({ op: "local.get", index: tmp });
56
+ this.pushInstruction({ op: "f32x4.extract_lane", lane: i });
57
+ this.pushInstruction("f32.add");
58
+ }
59
+ this.releaseTemp(tmp, "v128");
60
+ }
61
+
62
+ emitSimdMethodCall(method, recvExpr, args, _targetWasmType) {
63
+ const op = SIMD_BINARY_OPS[method];
64
+ if (op) {
65
+ this.emitExpr(recvExpr, "v128");
66
+ this.emitExpr(args[0], "v128");
67
+ this.pushInstruction(op);
68
+ return true;
69
+ }
70
+ if (method === "Dot3" || method === "Dot4") {
71
+ this._emitSimdDot(recvExpr, args[0], method === "Dot3" ? 3 : 4);
72
+ return true;
73
+ }
74
+ if (method === "Lane") {
75
+ const laneIdx = this._constIntValue(args[0]) ?? 0;
76
+ this.emitExpr(recvExpr, "v128");
77
+ this.pushInstruction({ op: "f32x4.extract_lane", lane: laneIdx });
78
+ return true;
79
+ }
80
+ if (method === "WithLane") {
81
+ const laneIdx = this._constIntValue(args[0]) ?? 0;
82
+ this.emitExpr(recvExpr, "v128");
83
+ this.emitExpr(args[1], "f32");
84
+ this.pushInstruction({ op: "f32x4.replace_lane", lane: laneIdx });
85
+ return true;
86
+ }
87
+ if (method === "Splat") {
88
+ this.emitExpr(args[0], "f32");
89
+ this.pushInstruction("f32x4.splat");
90
+ return true;
91
+ }
92
+ return false;
93
+ }
94
+
95
+ emitSimdPackageCall(fn, args, _targetWasmType) {
96
+ switch (fn) {
97
+ case "Splat":
98
+ this.emitExpr(args[0], "f32");
99
+ this.pushInstruction("f32x4.splat");
100
+ return true;
101
+ case "NewF32x4":
102
+ this.emitExpr(args[0], "f32");
103
+ this.pushInstruction("f32x4.splat");
104
+ this.emitExpr(args[1], "f32");
105
+ this.pushInstruction({ op: "f32x4.replace_lane", lane: 1 });
106
+ this.emitExpr(args[2], "f32");
107
+ this.pushInstruction({ op: "f32x4.replace_lane", lane: 2 });
108
+ this.emitExpr(args[3], "f32");
109
+ this.pushInstruction({ op: "f32x4.replace_lane", lane: 3 });
110
+ return true;
111
+ case "LoadF32x4":
112
+ this.emitSimdLoad(args[0], args[1]);
113
+ return true;
114
+ case "StoreF32x4":
115
+ this.emitSimdStore(args[0], args[1], args[2]);
116
+ return true;
117
+ default:
118
+ return false;
119
+ }
120
+ }
121
+
122
+ _emitSharedFloat32Address(bufNode, indexNode) {
123
+ const { wType, typeIndex } = this.mod.getSharedType();
124
+ const bufTmp = this.acquireTemp(wType);
125
+ const iTmp = this.acquireTemp("i32");
126
+
127
+ this.emitExpr(bufNode, wType);
128
+ this.pushInstruction({ op: "local.set", index: bufTmp });
129
+
130
+ // Bounds check 1: if unsigned(len) < 4 branch to OOB
131
+ this.pushInstruction({ op: "local.get", index: bufTmp });
132
+ this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 1 });
133
+ this.pushInstruction({ op: "i32.const", value: 4 });
134
+ this.pushInstruction("i32.lt_u");
135
+ this._emitBoundsBranch();
136
+
137
+ if (this._isI32IndexExpr(indexNode)) {
138
+ this.emitExpr(indexNode, "i32");
139
+ this.pushInstruction({ op: "local.set", index: iTmp });
140
+
141
+ // Bounds check: if unsigned(i) > len - 4 branch to OOB
142
+ this.pushInstruction({ op: "local.get", index: iTmp });
143
+ this.pushInstruction({ op: "local.get", index: bufTmp });
144
+ this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 1 });
145
+ this.pushInstruction({ op: "i32.const", value: 4 });
146
+ this.pushInstruction("i32.sub");
147
+ this.pushInstruction("i32.gt_u");
148
+ this._emitBoundsBranch();
149
+ } else {
150
+ const idx64 = this.acquireTemp("i64");
151
+ this.emitExpr(indexNode, "i64");
152
+ this.pushInstruction({ op: "local.set", index: idx64 });
153
+
154
+ // Bounds check in 64-bit: if unsigned(idx64) > unsigned(len - 4) branch to OOB
155
+ this.pushInstruction({ op: "local.get", index: idx64 });
156
+ this.pushInstruction({ op: "local.get", index: bufTmp });
157
+ this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 1 });
158
+ this.pushInstruction({ op: "i32.const", value: 4 });
159
+ this.pushInstruction("i32.sub");
160
+ this.pushInstruction("i64.extend_i32_u");
161
+ this.pushInstruction("i64.gt_u");
162
+ this._emitBoundsBranch();
163
+
164
+ this.pushInstruction({ op: "local.get", index: idx64 });
165
+ this.pushInstruction("i32.wrap_i64");
166
+ this.pushInstruction({ op: "local.set", index: iTmp });
167
+ this.releaseTemp(idx64, "i64");
168
+ }
169
+
170
+ // Address: buf.base + (i << 2)
171
+ this.pushInstruction({ op: "local.get", index: bufTmp });
172
+ this.pushInstruction({ op: "struct.get", typeIndex, fieldIndex: 0 });
173
+ this.pushInstruction({ op: "local.get", index: iTmp });
174
+ this.pushInstruction({ op: "i32.const", value: 2 });
175
+ this.pushInstruction("i32.shl");
176
+ this.pushInstruction("i32.add");
177
+
178
+ this.releaseTemp(bufTmp, wType);
179
+ this.releaseTemp(iTmp, "i32");
180
+ }
181
+
182
+ emitSimdLoad(bufNode, indexNode) {
183
+ this._emitSharedFloat32Address(bufNode, indexNode);
184
+ this.pushInstruction({ op: "v128.load", align: 2, offset: 0 });
185
+ }
186
+
187
+ emitSimdStore(bufNode, indexNode, valNode) {
188
+ this._emitSharedFloat32Address(bufNode, indexNode);
189
+ this.emitExpr(valNode, "v128");
190
+ this.pushInstruction({ op: "v128.store", align: 2, offset: 0 });
191
+ }
192
+
193
+ _constIntValue(node) {
194
+ if (!node) return null;
195
+ if (node.kind === "BasicLit" && node.litKind === "INT") {
196
+ return Number(node.value);
197
+ }
198
+ if (node.kind === "Ident") {
199
+ const c = this.mod?.resolveConst?.(node.name);
200
+ if (c && c.kind === "BasicLit") {
201
+ return Number(c.value);
202
+ }
203
+ }
204
+ if (node.kind === "ParenExpr") {
205
+ return this._constIntValue(node.expr);
206
+ }
207
+ if (node.kind === "UnaryExpr" && node.op === "+") {
208
+ return this._constIntValue(node.operand);
209
+ }
210
+ return null;
211
+ }
212
+
213
+ _getConstFloatValue(e) {
214
+ if (!e) return null;
215
+ if (
216
+ e.kind === "BasicLit" &&
217
+ (e.litKind === "FLOAT" || e.litKind === "INT")
218
+ ) {
219
+ return Number(e.value);
220
+ }
221
+ if (e.kind === "UnaryExpr" && (e.op === "-" || e.op === "+")) {
222
+ const v = this._getConstFloatValue(e.operand);
223
+ return v !== null ? (e.op === "-" ? -v : v) : null;
224
+ }
225
+ if (e.kind === "Ident") {
226
+ const c = this.mod?.resolveConst?.(e.name);
227
+ if (c) return this._getConstFloatValue(c);
228
+ }
229
+ if (e.kind === "ParenExpr") {
230
+ return this._getConstFloatValue(e.expr);
231
+ }
232
+ return null;
233
+ }
234
+ }
@@ -27,6 +27,7 @@ export function toWasmType(goType, checker = null, mod = null) {
27
27
  goType === "i64" ||
28
28
  goType === "f32" ||
29
29
  goType === "f64" ||
30
+ goType === "v128" ||
30
31
  goType === "anyref" ||
31
32
  goType === "externref" ||
32
33
  goType === "funcref"
@@ -39,6 +40,16 @@ export function toWasmType(goType, checker = null, mod = null) {
39
40
  }
40
41
  if (isTestingT(goType)) return "externref";
41
42
 
43
+ if (
44
+ goType.kind === "simd" ||
45
+ (goType.kind === "named" &&
46
+ (goType.name === "simd.F32x4" ||
47
+ goType.name === "F32x4" ||
48
+ goType.underlying?.kind === "simd"))
49
+ ) {
50
+ return "v128";
51
+ }
52
+
42
53
  // Pointer AST nodes
43
54
  if (goType.kind === "PointerType") {
44
55
  return toWasmType({ kind: "pointer", base: goType.base }, checker, mod);
@@ -68,6 +79,9 @@ export function toWasmType(goType, checker = null, mod = null) {
68
79
 
69
80
  // TypeName AST node
70
81
  if (goType.kind === "TypeName" || goType.kind === "Ident") {
82
+ if (goType.name === "simd.F32x4" || goType.name === "F32x4") {
83
+ return "v128";
84
+ }
71
85
  if (mod?.getStructType(goType.name)) {
72
86
  return {
73
87
  kind: "ref",
@@ -84,6 +98,13 @@ export function toWasmType(goType, checker = null, mod = null) {
84
98
 
85
99
  // Unwrap named types
86
100
  if (goType.kind === "named") {
101
+ if (
102
+ goType.name === "simd.F32x4" ||
103
+ goType.name === "F32x4" ||
104
+ goType.underlying?.kind === "simd"
105
+ ) {
106
+ return "v128";
107
+ }
87
108
  if (mod?.getStructType(goType.name)) {
88
109
  return {
89
110
  kind: "ref",
@@ -98,6 +98,43 @@ function formatInstruction(inst) {
98
98
  case "i32.store8":
99
99
  case "i32.store16":
100
100
  return inst.offset ? `${op} offset=${inst.offset}` : op;
101
+ case "v128.load":
102
+ case "v128.store": {
103
+ const parts = [op];
104
+ if (inst.offset) parts.push(`offset=${inst.offset}`);
105
+ if (inst.align !== undefined) parts.push(`align=${1 << inst.align}`);
106
+ return parts.join(" ");
107
+ }
108
+ case "v128.const": {
109
+ if (inst.values && inst.values.length === 4) {
110
+ return `${op} f32x4 ${inst.values.join(" ")}`;
111
+ }
112
+ if (inst.bytes) {
113
+ const b = new Uint8Array(16);
114
+ b.set(inst.bytes.slice(0, 16));
115
+ const dv = new DataView(b.buffer);
116
+ return `${op} i32x4 ${dv.getInt32(0, true)} ${dv.getInt32(4, true)} ${dv.getInt32(8, true)} ${dv.getInt32(12, true)}`;
117
+ }
118
+ return `${op} i32x4 0 0 0 0`;
119
+ }
120
+ case "i8x16.shuffle": {
121
+ const lanes = (inst.lanes ?? inst.indices ?? []).slice(0, 16);
122
+ while (lanes.length < 16) lanes.push(0);
123
+ return `${op} ${lanes.join(" ")}`;
124
+ }
125
+ case "i8x16.extract_lane_s":
126
+ case "i8x16.extract_lane_u":
127
+ case "i8x16.replace_lane":
128
+ case "i16x8.extract_lane_s":
129
+ case "i16x8.extract_lane_u":
130
+ case "i16x8.replace_lane":
131
+ case "i32x4.extract_lane":
132
+ case "i32x4.replace_lane":
133
+ case "i64x2.extract_lane":
134
+ case "i64x2.replace_lane":
135
+ case "f32x4.extract_lane":
136
+ case "f32x4.replace_lane":
137
+ return `${op} ${inst.lane ?? inst.index ?? 0}`;
101
138
  case "i32.const":
102
139
  case "i64.const":
103
140
  return `${op} ${inst.value ?? 0}`;