gofront 1.5.1 → 1.6.1

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.
Files changed (91) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +288 -128
  4. package/src/backend/js/index.js +138 -56
  5. package/src/backend/js/runtime.js +1055 -53
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +151 -83
  8. package/src/backend/js/stdlib/base64.js +12 -0
  9. package/src/backend/js/stdlib/binary.js +102 -0
  10. package/src/backend/js/stdlib/bits.js +15 -1
  11. package/src/backend/js/stdlib/builder.js +28 -9
  12. package/src/backend/js/stdlib/bytes.js +75 -18
  13. package/src/backend/js/stdlib/errors.js +22 -3
  14. package/src/backend/js/stdlib/fmt.js +95 -20
  15. package/src/backend/js/stdlib/hex.js +17 -0
  16. package/src/backend/js/stdlib/html.js +1 -1
  17. package/src/backend/js/stdlib/index.js +19 -0
  18. package/src/backend/js/stdlib/io.js +105 -3
  19. package/src/backend/js/stdlib/maps.js +1 -1
  20. package/src/backend/js/stdlib/math.js +13 -1
  21. package/src/backend/js/stdlib/path.js +20 -6
  22. package/src/backend/js/stdlib/rand.js +10 -5
  23. package/src/backend/js/stdlib/sha256.js +12 -0
  24. package/src/backend/js/stdlib/shared.js +17 -0
  25. package/src/backend/js/stdlib/slices.js +53 -27
  26. package/src/backend/js/stdlib/sort.js +1 -1
  27. package/src/backend/js/stdlib/strconv.js +52 -11
  28. package/src/backend/js/stdlib/strings.js +152 -16
  29. package/src/backend/js/stdlib/sync.js +13 -0
  30. package/src/backend/js/stdlib/testing.js +2 -2
  31. package/src/backend/js/stdlib/time.js +93 -22
  32. package/src/backend/js/stdlib/unicode.js +2 -2
  33. package/src/backend/js/templ.js +1 -1
  34. package/src/backend/wasm/boundary.js +1677 -58
  35. package/src/backend/wasm/const-fold.js +205 -0
  36. package/src/backend/wasm/emit-builtins.js +1230 -0
  37. package/src/backend/wasm/emit-exprs.js +2096 -0
  38. package/src/backend/wasm/emit-maps.js +1999 -0
  39. package/src/backend/wasm/emit-shared.js +519 -0
  40. package/src/backend/wasm/emit-stdlib.js +13 -0
  41. package/src/backend/wasm/emit-stmts.js +2466 -0
  42. package/src/backend/wasm/emit.js +656 -5031
  43. package/src/backend/wasm/encode.js +259 -85
  44. package/src/backend/wasm/glue.js +19 -5
  45. package/src/backend/wasm/index.js +2432 -86
  46. package/src/backend/wasm/monomorph.js +711 -0
  47. package/src/backend/wasm/optimize.js +236 -0
  48. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  49. package/src/backend/wasm/stdlib/core.js +8903 -0
  50. package/src/backend/wasm/stdlib/index.js +40 -0
  51. package/src/backend/wasm/stdlib/io.js +2161 -0
  52. package/src/backend/wasm/stdlib/packages.js +2400 -0
  53. package/src/backend/wasm/stdlib/pure.js +720 -0
  54. package/src/backend/wasm/stdlib/time.js +1200 -0
  55. package/src/backend/wasm/types.js +312 -36
  56. package/src/backend/wasm/wat.js +49 -2
  57. package/src/cli-core.js +276 -73
  58. package/src/compiler.js +162 -25
  59. package/src/dts-parser.js +79 -126
  60. package/src/index.js +88 -121
  61. package/src/lexer.js +80 -29
  62. package/src/lower/boxing.js +4 -6
  63. package/src/lower/captures.js +12 -23
  64. package/src/lower/escape.js +4 -9
  65. package/src/lower/functions.js +277 -5
  66. package/src/lower/globals.js +7 -15
  67. package/src/lower/index.js +5 -2
  68. package/src/lower/ownership.js +35 -19
  69. package/src/lower/range.js +2 -5
  70. package/src/lower/walk.js +25 -0
  71. package/src/minifier.js +20 -68
  72. package/src/parser/declarations.js +3 -6
  73. package/src/parser/expressions.js +3 -8
  74. package/src/parser/index.js +18 -3
  75. package/src/parser/statements.js +25 -38
  76. package/src/parser/types.js +1 -2
  77. package/src/resolver.js +3 -27
  78. package/src/templ-lexer.js +29 -63
  79. package/src/templ-parser.js +22 -45
  80. package/src/test-runner.js +20 -0
  81. package/src/typechecker/assignability.js +123 -14
  82. package/src/typechecker/expressions.js +244 -4
  83. package/src/typechecker/index.js +27 -1
  84. package/src/typechecker/resolve.js +71 -12
  85. package/src/typechecker/statements.js +0 -7
  86. package/src/typechecker/stdlib/core.js +292 -103
  87. package/src/typechecker/stdlib/extended.js +471 -4
  88. package/src/typechecker/stdlib/shared.js +53 -0
  89. package/src/typechecker/stdlib.js +2 -0
  90. package/src/typechecker/types.js +545 -8
  91. package/src/vendor.js +2 -2
@@ -0,0 +1,2096 @@
1
+ // src/backend/wasm/emit-exprs.js
2
+ // FunctionEmitter mixin: expression emission (literals, operators, indexing, conversions).
3
+
4
+ import { foldConstExpr } from "./const-fold.js";
5
+ import {
6
+ getMapKeyValTypes,
7
+ isAnyType,
8
+ isArrayType,
9
+ isFuncType,
10
+ isMapType,
11
+ isPointerToStruct,
12
+ isSigned,
13
+ isSliceType,
14
+ isStringType,
15
+ isStructType,
16
+ toWasmType,
17
+ } from "./types.js";
18
+
19
+ export class ExprsEmitter {
20
+ emitExpr(expr, targetWasmType = null) {
21
+ if (!expr) return;
22
+
23
+ if (targetWasmType === "anyref") {
24
+ if (
25
+ (expr.kind === "Ident" && expr.name === "nil") ||
26
+ (expr.kind === "BasicLit" && expr.litKind === "NIL")
27
+ ) {
28
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
29
+ return;
30
+ }
31
+
32
+ const goType = this._resolveExprGoType(expr);
33
+ if (isAnyType(goType, this.mod.checker)) {
34
+ this._emitRawExpr(expr, "anyref");
35
+ return;
36
+ }
37
+
38
+ if (
39
+ isStringType(goType, this.mod.checker) ||
40
+ (expr.kind === "BasicLit" && expr.litKind === "STRING")
41
+ ) {
42
+ this._emitRawExpr(expr, "externref");
43
+ this.pushInstruction("any.convert_extern");
44
+ return;
45
+ }
46
+
47
+ // `*T` in an interface is the struct ref itself (no allocation: the
48
+ // hot Go pattern `var c Collider = &tm`). A struct *value* is a
49
+ // cloned ref wrapped in a one-field box so T and *T stay distinct
50
+ // for type switches and assertions.
51
+ if (isPointerToStruct(goType, this.mod.checker, this.mod)) {
52
+ this._emitRawExpr(expr, this.toWasmType(goType));
53
+ return;
54
+ }
55
+ if (isStructType(goType, this.mod.checker, this.mod)) {
56
+ const structWType = this.toWasmType(goType);
57
+ this._emitRawExpr(expr, structWType);
58
+ const isFresh =
59
+ expr.kind === "CompositeLit" ||
60
+ (expr.kind === "UnaryExpr" && expr.op === "*");
61
+ if (!isFresh) {
62
+ const sInfo =
63
+ this._resolveStructInfo(expr) ??
64
+ this.mod.getStructType(goType?.name);
65
+ if (sInfo) {
66
+ this.emitCloneStruct(sInfo, structWType);
67
+ }
68
+ }
69
+ this._emitBoxValue(this.mod.getIfaceBoxType(goType));
70
+ return;
71
+ }
72
+
73
+ if (
74
+ isSliceType(goType, this.mod.checker) ||
75
+ isArrayType(goType, this.mod.checker) ||
76
+ isFuncType(goType, this.mod.checker) ||
77
+ isMapType(goType, this.mod.checker)
78
+ ) {
79
+ const refWType = this.toWasmType(goType);
80
+ this._emitRawExpr(expr, refWType);
81
+ return;
82
+ }
83
+
84
+ // Scalar type -> box into struct
85
+ const scalarType = goType ?? { kind: "basic", name: "int" };
86
+ const box = this.mod.getIfaceBoxType(scalarType);
87
+ this._emitRawExpr(expr, box.wType);
88
+ this._emitBoxValue(box);
89
+ return;
90
+ }
91
+
92
+ this._emitRawExpr(expr, targetWasmType);
93
+ }
94
+
95
+ _emitBoxValue(box) {
96
+ this.pushInstruction({
97
+ op: "ref.null",
98
+ heapType: box.tagTypeIndex,
99
+ });
100
+ this.pushInstruction({ op: "struct.new", typeIndex: box.typeIndex });
101
+ }
102
+
103
+ _resolveExprGoType(expr) {
104
+ if (expr._type) return expr._type;
105
+ if (expr.kind === "BasicLit") {
106
+ switch (expr.litKind) {
107
+ case "INT":
108
+ return { kind: "basic", name: "int" };
109
+ case "FLOAT":
110
+ return { kind: "basic", name: "float64" };
111
+ case "STRING":
112
+ return { kind: "basic", name: "string" };
113
+ case "BOOL":
114
+ return { kind: "basic", name: "bool" };
115
+ case "NIL":
116
+ return { kind: "nil", name: "nil" };
117
+ }
118
+ }
119
+ if (expr.kind === "Ident") {
120
+ const local = this.resolveLocal(expr.name);
121
+ if (local) return local.goType;
122
+ if (this.cachedGlobals.has(expr.name)) {
123
+ return this.cachedGlobals.get(expr.name).globalInfo.goType;
124
+ }
125
+ const global = this.mod.resolveGlobal(expr.name);
126
+ if (global) return global.goType;
127
+ }
128
+ return null;
129
+ }
130
+
131
+ _emitRawExpr(expr, targetWasmType = null) {
132
+ if (expr.kind === "BinaryExpr" || expr.kind === "UnaryExpr") {
133
+ const folded = foldConstExpr(expr, (name) =>
134
+ this.mod.pkgConsts?.get(name),
135
+ );
136
+ if (folded) {
137
+ folded._type = expr._type;
138
+ this.emitBasicLit(folded, targetWasmType);
139
+ return;
140
+ }
141
+ }
142
+
143
+ switch (expr.kind) {
144
+ case "BasicLit":
145
+ this.emitBasicLit(expr, targetWasmType);
146
+ break;
147
+
148
+ case "Ident":
149
+ this.emitIdent(expr, targetWasmType);
150
+ break;
151
+
152
+ case "UnaryExpr":
153
+ this.emitUnaryExpr(expr, targetWasmType);
154
+ break;
155
+
156
+ case "BinaryExpr":
157
+ this.emitBinaryExpr(expr, targetWasmType);
158
+ break;
159
+
160
+ case "CallExpr":
161
+ this.emitCallExpr(expr, targetWasmType);
162
+ break;
163
+
164
+ case "TypeConversion":
165
+ this.emitTypeConversion(expr);
166
+ break;
167
+
168
+ case "CompositeLit":
169
+ this.emitCompositeLit(expr);
170
+ break;
171
+
172
+ case "SelectorExpr": {
173
+ const deq = this._dequalify(expr);
174
+ if (deq) this.emitIdent(deq, targetWasmType);
175
+ else if (this._isMathConst(expr))
176
+ this.emitMathConst(expr.field, targetWasmType);
177
+ else if (this._isUtf8Const(expr))
178
+ this.emitUtf8Const(expr.field, targetWasmType);
179
+ else if (this._isStrconvConst(expr))
180
+ this.emitStrconvConst(expr.field, targetWasmType);
181
+ else if (this._isPathConst(expr))
182
+ this.emitPathConst(expr.field, targetWasmType);
183
+ else if (this._isIoConst(expr))
184
+ this.emitIoConst(expr.field, targetWasmType);
185
+ else if (this._isTimeConst(expr))
186
+ this.emitTimeConst(expr.field, targetWasmType);
187
+ else if (this._isExtendedPkgConst?.(expr))
188
+ this.emitExtendedPkgConst(expr, targetWasmType);
189
+ else this.emitSelectorExpr(expr);
190
+ break;
191
+ }
192
+
193
+ case "IndexExpr":
194
+ this.emitIndexExpr(expr, targetWasmType);
195
+ break;
196
+
197
+ case "SliceExpr":
198
+ this.emitSliceExpr(expr);
199
+ break;
200
+
201
+ case "ParenExpr":
202
+ this.emitExpr(expr.expr, targetWasmType);
203
+ break;
204
+
205
+ case "FuncLit":
206
+ this.emitFuncLit(expr, targetWasmType);
207
+ break;
208
+
209
+ case "TypeAssertExpr": {
210
+ const targetGoType = expr._type ?? expr.type;
211
+ const targetWType = targetWasmType ?? this.toWasmType(targetGoType);
212
+ const anyTmp = this.acquireTemp("anyref");
213
+ this.emitExpr(expr.expr, "anyref");
214
+ this.pushInstruction({ op: "local.set", index: anyTmp });
215
+
216
+ this.pushInstruction({ op: "local.get", index: anyTmp });
217
+ this._emitTypeTest(targetGoType);
218
+ this.pushInstruction("i32.eqz");
219
+ this.pushInstruction({ op: "if", blockType: "void" });
220
+ this.emitPanic("interface conversion: type assertion failed");
221
+ this.pushInstruction("end");
222
+
223
+ this.pushInstruction({ op: "local.get", index: anyTmp });
224
+ this._emitTypeCast(targetGoType, targetWType);
225
+ this.releaseTemp(anyTmp, "anyref");
226
+ break;
227
+ }
228
+
229
+ default:
230
+ throw new Error(`Unsupported expression kind: ${expr.kind}`);
231
+ }
232
+ }
233
+
234
+ emitFuncLit(funcLit) {
235
+ const liftedFn = funcLit._liftedFn;
236
+ const globalFuncIdx = liftedFn._globalFuncIndex;
237
+ const closureInfo = this.mod.getClosureType(funcLit._type ?? funcLit);
238
+
239
+ // 1. Function reference
240
+ this.pushInstruction({ op: "ref.func", funcIndex: globalFuncIdx });
241
+
242
+ // 2. Environment reference
243
+ const capturedNames = Array.from(
244
+ this.captureAnalysis?.capturesByClosure?.get(funcLit) ?? [],
245
+ ).sort();
246
+
247
+ if (capturedNames.length === 0) {
248
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
249
+ } else {
250
+ const envFields = this._buildEnvFields(capturedNames);
251
+ const envInfo = this.mod.getEnvType(envFields);
252
+
253
+ for (const field of envFields) {
254
+ const local = this.resolveLocal(field.name);
255
+ if (!local) {
256
+ throw new Error(
257
+ `Captured variable '${field.name}' not found in locals`,
258
+ );
259
+ }
260
+ this.pushInstruction({ op: "local.get", index: local.index });
261
+ }
262
+
263
+ this.pushInstruction({
264
+ op: "struct.new",
265
+ typeIndex: envInfo.typeIndex,
266
+ });
267
+ }
268
+
269
+ // 3. Closure struct
270
+ this.pushInstruction({
271
+ op: "struct.new",
272
+ typeIndex: closureInfo.typeIndex,
273
+ });
274
+ }
275
+
276
+ emitCompositeLit(lit) {
277
+ const litType = lit._type ?? lit.typeExpr?._type ?? lit.typeExpr;
278
+ if (isMapType(litType, this.mod.checker)) {
279
+ const { keyType, valType } = getMapKeyValTypes(litType, this.mod.checker);
280
+ const mapInfo = this.mod.getMapType(keyType, valType);
281
+ const count = lit.elems?.length ?? 0;
282
+ this.pushInstruction({ op: "i32.const", value: count });
283
+ this.pushInstruction({
284
+ op: "call",
285
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.makeFuncName),
286
+ });
287
+ if (count > 0) {
288
+ const mTmp = this.acquireTemp(mapInfo.wType);
289
+ this.pushInstruction({ op: "local.set", index: mTmp });
290
+ for (const elem of lit.elems) {
291
+ this.pushInstruction({ op: "local.get", index: mTmp });
292
+ this.emitExpr(elem.key, mapInfo.keyWType);
293
+ this.emitExpr(elem.value, mapInfo.valWType);
294
+ this._emitCopyIfValueStruct(elem.value, valType, mapInfo.valWType);
295
+ this.pushInstruction({
296
+ op: "call",
297
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
298
+ });
299
+ }
300
+ this.pushInstruction({ op: "local.get", index: mTmp });
301
+ this.releaseTemp(mTmp, mapInfo.wType);
302
+ }
303
+ return;
304
+ }
305
+
306
+ if (isSliceType(litType, this.mod.checker)) {
307
+ const elemType = litType.elem;
308
+ const sliceInfo = this.mod.getSliceType(elemType);
309
+ const count = lit.elems?.length ?? 0;
310
+ if (count === 0) {
311
+ this.pushInstruction({ op: "i32.const", value: 0 });
312
+ this.pushInstruction({
313
+ op: "array.new_default",
314
+ typeIndex: sliceInfo.arrInfo.typeIndex,
315
+ });
316
+ this.pushInstruction({ op: "i32.const", value: 0 }); // offset
317
+ this.pushInstruction({ op: "i32.const", value: 0 }); // len
318
+ this.pushInstruction({ op: "i32.const", value: 0 }); // cap
319
+ this.pushInstruction({
320
+ op: "struct.new",
321
+ typeIndex: sliceInfo.typeIndex,
322
+ });
323
+ return;
324
+ }
325
+ for (const elem of lit.elems) {
326
+ this.emitExpr(elem, sliceInfo.elemWType);
327
+ this._emitCopyIfValueStruct(elem, elem._type, sliceInfo.elemWType);
328
+ }
329
+ this.pushInstruction({
330
+ op: "array.new_fixed",
331
+ typeIndex: sliceInfo.arrInfo.typeIndex,
332
+ size: count,
333
+ });
334
+ this.pushInstruction({ op: "i32.const", value: 0 }); // offset
335
+ this.pushInstruction({ op: "i32.const", value: count }); // len
336
+ this.pushInstruction({ op: "i32.const", value: count }); // cap
337
+ this.pushInstruction({
338
+ op: "struct.new",
339
+ typeIndex: sliceInfo.typeIndex,
340
+ });
341
+ return;
342
+ }
343
+
344
+ if (isArrayType(litType, this.mod.checker)) {
345
+ const elemType = litType.elem;
346
+ const arrInfo = this.mod.getArrayType(elemType);
347
+ const count = lit.elems?.length ?? 0;
348
+ const targetSize = this._getArraySize(litType, count);
349
+ for (const elem of lit.elems ?? []) {
350
+ this.emitExpr(elem, arrInfo.elemWType);
351
+ this._emitCopyIfValueStruct(elem, elem._type, arrInfo.elemWType);
352
+ }
353
+ for (let i = count; i < targetSize; i++) {
354
+ this.emitZeroValue(elemType, arrInfo.elemWType);
355
+ }
356
+ this.pushInstruction({
357
+ op: "array.new_fixed",
358
+ typeIndex: arrInfo.typeIndex,
359
+ size: targetSize,
360
+ });
361
+ return;
362
+ }
363
+
364
+ const typeName =
365
+ lit.typeExpr?.name ??
366
+ (litType?.kind === "named" ? litType.name : litType?.name);
367
+ let structInfo = typeName ? this.mod.getStructType(typeName) : null;
368
+ if (!structInfo) {
369
+ structInfo = this._resolveStructInfo(lit);
370
+ }
371
+ if (!structInfo) {
372
+ throw new Error(`Unknown struct in CompositeLit: ${typeName}`);
373
+ }
374
+
375
+ const fields = structInfo.fields;
376
+ const map = new Map();
377
+ let isKeyed = false;
378
+
379
+ for (const elem of lit.elems ?? []) {
380
+ if (elem.kind === "KeyValueExpr") {
381
+ isKeyed = true;
382
+ map.set(elem.key.name, elem.value);
383
+ }
384
+ }
385
+
386
+ for (let i = 0; i < fields.length; i++) {
387
+ const f = fields[i];
388
+ if (isKeyed) {
389
+ if (map.has(f.name)) {
390
+ this.emitExpr(map.get(f.name), f.wType);
391
+ } else {
392
+ this.emitZeroValue(f.goType, f.wType);
393
+ }
394
+ } else {
395
+ if (i < (lit.elems?.length ?? 0)) {
396
+ this.emitExpr(lit.elems[i], f.wType);
397
+ } else {
398
+ this.emitZeroValue(f.goType, f.wType);
399
+ }
400
+ }
401
+ }
402
+
403
+ this.pushInstruction({
404
+ op: "struct.new",
405
+ typeIndex: structInfo.typeIndex,
406
+ });
407
+ }
408
+
409
+ emitSelectorExpr(expr) {
410
+ const structInfo = this._resolveStructInfo(expr.expr);
411
+ if (!structInfo) {
412
+ throw new Error(`Cannot resolve struct for selector: ${expr.field}`);
413
+ }
414
+ const path = this._resolveFieldPath(structInfo, expr.field);
415
+ if (!path) {
416
+ throw new Error(
417
+ `Unknown field '${expr.field}' on struct '${structInfo.name}'`,
418
+ );
419
+ }
420
+
421
+ const baseWType = this.toWasmType(expr.expr._type);
422
+ this.emitExpr(expr.expr, baseWType);
423
+
424
+ for (let i = 0; i < path.length; i++) {
425
+ const step = path[i];
426
+ this.pushInstruction({
427
+ op: "struct.get",
428
+ typeIndex: step.structInfo.typeIndex,
429
+ fieldIndex: step.fieldIndex,
430
+ });
431
+ }
432
+ }
433
+
434
+ emitIndexExpr(expr, targetWasmType = null) {
435
+ const baseNode = expr.expr;
436
+ const baseType = baseNode._type ?? this._resolveExprGoType(baseNode);
437
+
438
+ if (isMapType(baseType, this.mod.checker)) {
439
+ const { keyType, valType } = getMapKeyValTypes(
440
+ baseType,
441
+ this.mod.checker,
442
+ );
443
+ const mapInfo = this.mod.getMapType(keyType, valType);
444
+ this.emitExpr(baseNode, mapInfo.wType);
445
+ this.emitExpr(expr.index, mapInfo.keyWType);
446
+ this.pushInstruction({
447
+ op: "call",
448
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.getFuncName),
449
+ });
450
+ return;
451
+ }
452
+
453
+ const sharedInfo = this._sharedInfo(baseType);
454
+ if (sharedInfo) {
455
+ this.emitSharedIndexExpr(expr, sharedInfo, targetWasmType);
456
+ return;
457
+ }
458
+
459
+ const isSlice = isSliceType(baseType, this.mod.checker);
460
+ const isArr = isArrayType(baseType, this.mod.checker);
461
+ const isPtrToArr =
462
+ baseType?.kind === "pointer" &&
463
+ isArrayType(baseType.base, this.mod.checker);
464
+
465
+ const isStr = isStringType(baseType, this.mod.checker);
466
+
467
+ if (isStr) {
468
+ const strTmp = this.acquireTemp("externref");
469
+ this.emitExpr(baseNode, "externref");
470
+ this.pushInstruction({ op: "local.tee", index: strTmp });
471
+
472
+ this._emitCheckedIndex(expr.index, () => {
473
+ this.pushInstruction({ op: "local.get", index: strTmp });
474
+ this.pushInstruction({
475
+ op: "call",
476
+ funcIndex: this.mod.getStringLenImportIndex(),
477
+ });
478
+ });
479
+ this.pushInstruction({
480
+ op: "call",
481
+ funcIndex: this.mod.getStringGetImportIndex(),
482
+ });
483
+
484
+ this.releaseTemp(strTmp, "externref");
485
+
486
+ if ((targetWasmType ?? "i64") === "i64") {
487
+ this.pushInstruction("i64.extend_i32_u");
488
+ }
489
+ return;
490
+ }
491
+
492
+ if (!isSlice && !isArr && !isPtrToArr) {
493
+ throw new Error(
494
+ `Unsupported IndexExpr on type: ${JSON.stringify(baseType)}`,
495
+ );
496
+ }
497
+
498
+ const elemGoType = isSlice
499
+ ? baseType.elem
500
+ : isPtrToArr
501
+ ? baseType.base.elem
502
+ : baseType.elem;
503
+ const arrInfo = this.mod.getArrayType(elemGoType);
504
+ const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
505
+
506
+ this._emitElemAddr(baseNode, expr.index, sliceInfo, arrInfo);
507
+ this.pushInstruction({
508
+ op: "array.get",
509
+ typeIndex: arrInfo.typeIndex,
510
+ });
511
+ }
512
+
513
+ emitSliceExpr(expr) {
514
+ const baseNode = expr.expr;
515
+ const baseType = baseNode._type;
516
+ const isSlice = isSliceType(baseType, this.mod.checker);
517
+ const isArr = isArrayType(baseType, this.mod.checker);
518
+ const isPtrToArr =
519
+ baseType?.kind === "pointer" &&
520
+ isArrayType(baseType.base, this.mod.checker);
521
+
522
+ const isStr = isStringType(baseType, this.mod.checker);
523
+
524
+ if (isStr) {
525
+ const strTmp = this.acquireTemp("externref");
526
+ this.emitExpr(baseNode, "externref");
527
+ this.pushInstruction({ op: "local.set", index: strTmp });
528
+
529
+ const srcLenTmp = this.acquireTemp("i32");
530
+ this.pushInstruction({ op: "local.get", index: strTmp });
531
+ this.pushInstruction({
532
+ op: "call",
533
+ funcIndex: this.mod.getStringLenImportIndex(),
534
+ });
535
+ this.pushInstruction({ op: "local.set", index: srcLenTmp });
536
+
537
+ const lowTmp = this.acquireTemp("i32");
538
+ const highTmp = this.acquireTemp("i32");
539
+
540
+ if (expr.low) {
541
+ this._emitIndexExprToI32(expr.low, lowTmp);
542
+ } else {
543
+ this.pushInstruction({ op: "i32.const", value: 0 });
544
+ this.pushInstruction({ op: "local.set", index: lowTmp });
545
+ }
546
+
547
+ if (expr.high) {
548
+ this._emitIndexExprToI32(expr.high, highTmp);
549
+ } else {
550
+ this.pushInstruction({ op: "local.get", index: srcLenTmp });
551
+ this.pushInstruction({ op: "local.set", index: highTmp });
552
+ }
553
+
554
+ // Bounds check: 0 <= low <= high <= srcLenTmp
555
+ this.pushInstruction({ op: "local.get", index: lowTmp });
556
+ this.pushInstruction({ op: "i32.const", value: 0 });
557
+ this.pushInstruction("i32.lt_s");
558
+ this.pushInstruction({ op: "local.get", index: highTmp });
559
+ this.pushInstruction({ op: "local.get", index: lowTmp });
560
+ this.pushInstruction("i32.lt_s");
561
+ this.pushInstruction("i32.or");
562
+ this.pushInstruction({ op: "local.get", index: highTmp });
563
+ this.pushInstruction({ op: "local.get", index: srcLenTmp });
564
+ this.pushInstruction("i32.gt_u");
565
+ this.pushInstruction("i32.or");
566
+
567
+ this.pushInstruction({ op: "if", blockType: "void" });
568
+ this.pushInstruction({
569
+ op: "call",
570
+ funcIndex: this.mod.getSliceBoundsPanicFuncIndex(),
571
+ });
572
+ this.pushInstruction("unreachable");
573
+ this.pushInstruction("end");
574
+
575
+ this.pushInstruction({ op: "local.get", index: strTmp });
576
+ this.pushInstruction({ op: "local.get", index: lowTmp });
577
+ this.pushInstruction({ op: "local.get", index: highTmp });
578
+ this.pushInstruction({
579
+ op: "call",
580
+ funcIndex: this.mod.getStringSliceImportIndex(),
581
+ });
582
+
583
+ this.releaseTemp(highTmp, "i32");
584
+ this.releaseTemp(lowTmp, "i32");
585
+ this.releaseTemp(srcLenTmp, "i32");
586
+ this.releaseTemp(strTmp, "externref");
587
+ return;
588
+ }
589
+
590
+ if (!isSlice && !isArr && !isPtrToArr) {
591
+ throw new Error(
592
+ `Unsupported SliceExpr on type: ${JSON.stringify(baseType)}`,
593
+ );
594
+ }
595
+
596
+ const elemGoType = isSlice
597
+ ? baseType.elem
598
+ : isPtrToArr
599
+ ? baseType.base.elem
600
+ : baseType.elem;
601
+ const arrInfo = this.mod.getArrayType(elemGoType);
602
+ const sliceInfo = this.mod.getSliceType(elemGoType);
603
+
604
+ const arrTmp = this.acquireTemp({
605
+ kind: "ref",
606
+ nullable: true,
607
+ typeIndex: arrInfo.typeIndex,
608
+ });
609
+ const srcOffTmp = this.acquireTemp("i32");
610
+ const srcLenTmp = this.acquireTemp("i32");
611
+ const srcCapTmp = this.acquireTemp("i32");
612
+
613
+ if (isSlice) {
614
+ const sliceWType = {
615
+ kind: "ref",
616
+ nullable: true,
617
+ typeIndex: sliceInfo.typeIndex,
618
+ };
619
+ const sliceTmp = this.acquireTemp(sliceWType);
620
+ this.emitExpr(baseNode, sliceWType);
621
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
622
+
623
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
624
+ this.pushInstruction({
625
+ op: "struct.get",
626
+ typeIndex: sliceInfo.typeIndex,
627
+ fieldIndex: 2,
628
+ });
629
+ this.pushInstruction({ op: "local.set", index: srcLenTmp });
630
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
631
+ this.pushInstruction({
632
+ op: "struct.get",
633
+ typeIndex: sliceInfo.typeIndex,
634
+ fieldIndex: 3,
635
+ });
636
+ this.pushInstruction({ op: "local.set", index: srcCapTmp });
637
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
638
+ this.pushInstruction({
639
+ op: "struct.get",
640
+ typeIndex: sliceInfo.typeIndex,
641
+ fieldIndex: 1,
642
+ });
643
+ this.pushInstruction({ op: "local.set", index: srcOffTmp });
644
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
645
+ this.pushInstruction({
646
+ op: "struct.get",
647
+ typeIndex: sliceInfo.typeIndex,
648
+ fieldIndex: 0,
649
+ });
650
+ this.pushInstruction({ op: "local.set", index: arrTmp });
651
+ this.releaseTemp(sliceTmp, sliceWType);
652
+ } else {
653
+ const arrWType = {
654
+ kind: "ref",
655
+ nullable: true,
656
+ typeIndex: arrInfo.typeIndex,
657
+ };
658
+ this.emitExpr(baseNode, arrWType);
659
+ this.pushInstruction({ op: "local.set", index: arrTmp });
660
+ this.pushInstruction({ op: "local.get", index: arrTmp });
661
+ this.pushInstruction("array.len");
662
+ this.pushInstruction({ op: "local.tee", index: srcLenTmp });
663
+ this.pushInstruction({ op: "local.set", index: srcCapTmp });
664
+ this.pushInstruction({ op: "i32.const", value: 0 });
665
+ this.pushInstruction({ op: "local.set", index: srcOffTmp });
666
+ }
667
+
668
+ const lowTmp = this.acquireTemp("i32");
669
+ if (expr.low) {
670
+ this._emitIndexExprToI32(expr.low, lowTmp);
671
+ } else {
672
+ this.pushInstruction({ op: "i32.const", value: 0 });
673
+ this.pushInstruction({ op: "local.set", index: lowTmp });
674
+ }
675
+
676
+ const highTmp = this.acquireTemp("i32");
677
+ if (expr.high) {
678
+ this._emitIndexExprToI32(expr.high, highTmp);
679
+ } else {
680
+ this.pushInstruction({ op: "local.get", index: srcLenTmp });
681
+ this.pushInstruction({ op: "local.set", index: highTmp });
682
+ }
683
+
684
+ const maxTmp = this.acquireTemp("i32");
685
+ if (expr.max) {
686
+ this._emitIndexExprToI32(expr.max, maxTmp);
687
+ } else {
688
+ this.pushInstruction({ op: "local.get", index: srcCapTmp });
689
+ this.pushInstruction({ op: "local.set", index: maxTmp });
690
+ }
691
+
692
+ // Bounds check: 0 <= low <= high <= max <= srcCap
693
+ this.pushInstruction({ op: "local.get", index: lowTmp });
694
+ this.pushInstruction({ op: "local.get", index: highTmp });
695
+ this.pushInstruction("i32.gt_u");
696
+ this.pushInstruction({ op: "local.get", index: highTmp });
697
+ this.pushInstruction({ op: "local.get", index: maxTmp });
698
+ this.pushInstruction("i32.gt_u");
699
+ this.pushInstruction("i32.or");
700
+ this.pushInstruction({ op: "local.get", index: maxTmp });
701
+ this.pushInstruction({ op: "local.get", index: srcCapTmp });
702
+ this.pushInstruction("i32.gt_u");
703
+ this.pushInstruction("i32.or");
704
+
705
+ this.pushInstruction({ op: "if", blockType: "void" });
706
+ this.pushInstruction({
707
+ op: "call",
708
+ funcIndex: this.mod.getSliceBoundsPanicFuncIndex(),
709
+ });
710
+ this.pushInstruction("unreachable");
711
+ this.pushInstruction("end");
712
+
713
+ this.pushInstruction({ op: "local.get", index: arrTmp });
714
+
715
+ this.pushInstruction({ op: "local.get", index: srcOffTmp });
716
+ this.pushInstruction({ op: "local.get", index: lowTmp });
717
+ this.pushInstruction("i32.add");
718
+
719
+ this.pushInstruction({ op: "local.get", index: highTmp });
720
+ this.pushInstruction({ op: "local.get", index: lowTmp });
721
+ this.pushInstruction("i32.sub");
722
+
723
+ this.pushInstruction({ op: "local.get", index: maxTmp });
724
+ this.pushInstruction({ op: "local.get", index: lowTmp });
725
+ this.pushInstruction("i32.sub");
726
+
727
+ this.pushInstruction({
728
+ op: "struct.new",
729
+ typeIndex: sliceInfo.typeIndex,
730
+ });
731
+
732
+ this.releaseTemp(maxTmp, "i32");
733
+ this.releaseTemp(highTmp, "i32");
734
+ this.releaseTemp(lowTmp, "i32");
735
+ this.releaseTemp(srcCapTmp, "i32");
736
+ this.releaseTemp(srcLenTmp, "i32");
737
+ this.releaseTemp(srcOffTmp, "i32");
738
+ this.releaseTemp(arrTmp, {
739
+ kind: "ref",
740
+ nullable: true,
741
+ typeIndex: arrInfo.typeIndex,
742
+ });
743
+ }
744
+
745
+ _emitArrayCopy(arrInfo, elemGoType) {
746
+ const isValStruct =
747
+ isStructType(elemGoType, this.mod.checker, this.mod) &&
748
+ !isPointerToStruct(elemGoType, this.mod.checker, this.mod);
749
+
750
+ if (!isValStruct) {
751
+ this.pushInstruction({
752
+ op: "array.copy",
753
+ typeIndexDst: arrInfo.typeIndex,
754
+ typeIndexSrc: arrInfo.typeIndex,
755
+ });
756
+ return;
757
+ }
758
+
759
+ const sInfo =
760
+ this._resolveStructInfo({ _type: elemGoType }) ??
761
+ this.mod.getStructType(
762
+ elemGoType?.name ??
763
+ (elemGoType?.kind === "named" ? elemGoType.name : null),
764
+ );
765
+
766
+ const lenTmp = this.acquireTemp("i32");
767
+ const srcOffTmp = this.acquireTemp("i32");
768
+ const srcArrTmp = this.acquireTemp({
769
+ kind: "ref",
770
+ nullable: true,
771
+ typeIndex: arrInfo.typeIndex,
772
+ });
773
+ const dstOffTmp = this.acquireTemp("i32");
774
+ const dstArrTmp = this.acquireTemp({
775
+ kind: "ref",
776
+ nullable: true,
777
+ typeIndex: arrInfo.typeIndex,
778
+ });
779
+
780
+ this.pushInstruction({ op: "local.set", index: lenTmp });
781
+ this.pushInstruction({ op: "local.set", index: srcOffTmp });
782
+ this.pushInstruction({ op: "local.set", index: srcArrTmp });
783
+ this.pushInstruction({ op: "local.set", index: dstOffTmp });
784
+ this.pushInstruction({ op: "local.set", index: dstArrTmp });
785
+
786
+ const iTmp = this.acquireTemp("i32");
787
+
788
+ // If copying within the same array and shifting right (dstOff > srcOff),
789
+ // copy backwards to prevent clobbering overlapping elements.
790
+ this.pushInstruction({ op: "local.get", index: dstArrTmp });
791
+ this.pushInstruction({ op: "local.get", index: srcArrTmp });
792
+ this.pushInstruction("ref.eq");
793
+ this.pushInstruction({ op: "local.get", index: dstOffTmp });
794
+ this.pushInstruction({ op: "local.get", index: srcOffTmp });
795
+ this.pushInstruction("i32.gt_u");
796
+ this.pushInstruction("i32.and");
797
+ this.pushInstruction({ op: "if", blockType: "void" });
798
+
799
+ // Backwards copy: i from lenTmp - 1 down to 0
800
+ this.pushInstruction({ op: "local.get", index: lenTmp });
801
+ this.pushInstruction({ op: "i32.const", value: 1 });
802
+ this.pushInstruction("i32.sub");
803
+ this.pushInstruction({ op: "local.set", index: iTmp });
804
+
805
+ this.pushInstruction({ op: "block", blockType: "void" });
806
+ this.pushInstruction({ op: "loop", blockType: "void" });
807
+ this.pushInstruction({ op: "local.get", index: iTmp });
808
+ this.pushInstruction({ op: "i32.const", value: 0 });
809
+ this.pushInstruction("i32.lt_s");
810
+ this.pushInstruction({ op: "br_if", depth: 1 });
811
+
812
+ this.pushInstruction({ op: "local.get", index: dstArrTmp });
813
+ this.pushInstruction({ op: "local.get", index: dstOffTmp });
814
+ this.pushInstruction({ op: "local.get", index: iTmp });
815
+ this.pushInstruction("i32.add");
816
+
817
+ this.pushInstruction({ op: "local.get", index: srcArrTmp });
818
+ this.pushInstruction({ op: "local.get", index: srcOffTmp });
819
+ this.pushInstruction({ op: "local.get", index: iTmp });
820
+ this.pushInstruction("i32.add");
821
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
822
+
823
+ if (sInfo) {
824
+ this.emitCloneStruct(sInfo, arrInfo.elemWType);
825
+ }
826
+
827
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
828
+
829
+ this.pushInstruction({ op: "local.get", index: iTmp });
830
+ this.pushInstruction({ op: "i32.const", value: 1 });
831
+ this.pushInstruction("i32.sub");
832
+ this.pushInstruction({ op: "local.set", index: iTmp });
833
+ this.pushInstruction({ op: "br", depth: 0 });
834
+ this.pushInstruction("end");
835
+ this.pushInstruction("end");
836
+
837
+ this.pushInstruction("else");
838
+
839
+ // Forwards copy: i from 0 up to lenTmp - 1
840
+ this.pushInstruction({ op: "i32.const", value: 0 });
841
+ this.pushInstruction({ op: "local.set", index: iTmp });
842
+
843
+ this.pushInstruction({ op: "block", blockType: "void" });
844
+ this.pushInstruction({ op: "loop", blockType: "void" });
845
+ this.pushInstruction({ op: "local.get", index: iTmp });
846
+ this.pushInstruction({ op: "local.get", index: lenTmp });
847
+ this.pushInstruction("i32.ge_s");
848
+ this.pushInstruction({ op: "br_if", depth: 1 });
849
+
850
+ this.pushInstruction({ op: "local.get", index: dstArrTmp });
851
+ this.pushInstruction({ op: "local.get", index: dstOffTmp });
852
+ this.pushInstruction({ op: "local.get", index: iTmp });
853
+ this.pushInstruction("i32.add");
854
+
855
+ this.pushInstruction({ op: "local.get", index: srcArrTmp });
856
+ this.pushInstruction({ op: "local.get", index: srcOffTmp });
857
+ this.pushInstruction({ op: "local.get", index: iTmp });
858
+ this.pushInstruction("i32.add");
859
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
860
+
861
+ if (sInfo) {
862
+ this.emitCloneStruct(sInfo, arrInfo.elemWType);
863
+ }
864
+
865
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
866
+
867
+ this.pushInstruction({ op: "local.get", index: iTmp });
868
+ this.pushInstruction({ op: "i32.const", value: 1 });
869
+ this.pushInstruction("i32.add");
870
+ this.pushInstruction({ op: "local.set", index: iTmp });
871
+ this.pushInstruction({ op: "br", depth: 0 });
872
+ this.pushInstruction("end");
873
+ this.pushInstruction("end");
874
+
875
+ this.pushInstruction("end");
876
+
877
+ this.releaseTemp(iTmp, "i32");
878
+ this.releaseTemp(dstArrTmp, {
879
+ kind: "ref",
880
+ nullable: true,
881
+ typeIndex: arrInfo.typeIndex,
882
+ });
883
+ this.releaseTemp(dstOffTmp, "i32");
884
+ this.releaseTemp(srcArrTmp, {
885
+ kind: "ref",
886
+ nullable: true,
887
+ typeIndex: arrInfo.typeIndex,
888
+ });
889
+ this.releaseTemp(srcOffTmp, "i32");
890
+ this.releaseTemp(lenTmp, "i32");
891
+ }
892
+
893
+ // True when `expr` can be evaluated as an i32 without losing information:
894
+ // i32-backed locals (induction variables, narrow ints), small literals,
895
+ // `len`/`cap`, +/-/* over such operands, or any expression whose Go type
896
+ // already lowers to i32. Everything else goes through the i64 path.
897
+ _isI32IndexExpr(expr) {
898
+ if (!expr) return false;
899
+ switch (expr.kind) {
900
+ case "Ident": {
901
+ const local = this.resolveLocal(expr.name);
902
+ if (local) return local.type === "i32";
903
+ break;
904
+ }
905
+ case "BasicLit":
906
+ if (expr.litKind === "INT") {
907
+ const v = BigInt(expr.value);
908
+ return v >= 0n && v < 2147483648n;
909
+ }
910
+ break;
911
+ case "ParenExpr":
912
+ return this._isI32IndexExpr(expr.expr);
913
+ case "BinaryExpr":
914
+ if (
915
+ (expr.op === "+" || expr.op === "-" || expr.op === "*") &&
916
+ this._isI32IndexExpr(expr.left) &&
917
+ this._isI32IndexExpr(expr.right)
918
+ ) {
919
+ return true;
920
+ }
921
+ break;
922
+ case "CallExpr":
923
+ if (
924
+ expr.func?.kind === "Ident" &&
925
+ (expr.func.name === "len" || expr.func.name === "cap")
926
+ ) {
927
+ return true;
928
+ }
929
+ break;
930
+ }
931
+ return this.toWasmType(expr._type) === "i32";
932
+ }
933
+
934
+ _emitBoundsBranch() {
935
+ const oobDepth = this.resolveBranchDepthToOob();
936
+ if (oobDepth !== null) {
937
+ this.pushInstruction({ op: "br_if", depth: oobDepth });
938
+ } else {
939
+ this.pushInstruction({ op: "if", blockType: "void" });
940
+ this.pushInstruction({
941
+ op: "call",
942
+ funcIndex: this.mod.getBoundsPanicFuncIndex(),
943
+ });
944
+ this.pushInstruction("unreachable");
945
+ this.pushInstruction("end");
946
+ }
947
+ }
948
+
949
+ // Pushes the bounds-checked i32 index onto the stack. `emitLen` must push
950
+ // the i32 length. Constant and plain-local indexes are re-emitted instead
951
+ // of spilled; everything else goes through one temp.
952
+ _emitCheckedIndex(indexNode, emitLen) {
953
+ const simple =
954
+ (indexNode.kind === "BasicLit" && indexNode.litKind === "INT") ||
955
+ (indexNode.kind === "Ident" && this.resolveLocal(indexNode.name));
956
+ if (this._isI32IndexExpr(indexNode)) {
957
+ this.emitExpr(indexNode, "i32");
958
+ let tmp = null;
959
+ if (simple) {
960
+ this.emitExpr(indexNode, "i32");
961
+ } else {
962
+ tmp = this.acquireTemp("i32");
963
+ this.pushInstruction({ op: "local.tee", index: tmp });
964
+ this.pushInstruction({ op: "local.get", index: tmp });
965
+ }
966
+ emitLen();
967
+ this.pushInstruction("i32.ge_u");
968
+ this._emitBoundsBranch();
969
+ if (tmp !== null) this.releaseTemp(tmp, "i32");
970
+ return;
971
+ }
972
+
973
+ const idx64 = this.acquireTemp("i64");
974
+ this.emitExpr(indexNode, "i64");
975
+ this.pushInstruction({ op: "local.tee", index: idx64 });
976
+ emitLen();
977
+ this.pushInstruction("i64.extend_i32_u");
978
+ this.pushInstruction("i64.ge_u");
979
+ this._emitBoundsBranch();
980
+ this.pushInstruction({ op: "local.get", index: idx64 });
981
+ this.pushInstruction("i32.wrap_i64");
982
+ this.releaseTemp(idx64, "i64");
983
+ }
984
+
985
+ // Leaves `[arr, off+idx]` on the stack for `base[idx]` over a slice or
986
+ // (pointer to) array, with the bounds check already emitted.
987
+ _emitElemAddr(baseNode, indexNode, sliceInfo, arrInfo) {
988
+ if (sliceInfo) {
989
+ const sliceWType = {
990
+ kind: "ref",
991
+ nullable: true,
992
+ typeIndex: sliceInfo.typeIndex,
993
+ };
994
+ const sliceTmp = this.acquireTemp(sliceWType);
995
+ this.emitExpr(baseNode, sliceWType);
996
+ this.pushInstruction({ op: "local.tee", index: sliceTmp });
997
+ this.pushInstruction({
998
+ op: "struct.get",
999
+ typeIndex: sliceInfo.typeIndex,
1000
+ fieldIndex: 0,
1001
+ });
1002
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1003
+ this.pushInstruction({
1004
+ op: "struct.get",
1005
+ typeIndex: sliceInfo.typeIndex,
1006
+ fieldIndex: 1,
1007
+ });
1008
+ this._emitCheckedIndex(indexNode, () => {
1009
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1010
+ this.pushInstruction({
1011
+ op: "struct.get",
1012
+ typeIndex: sliceInfo.typeIndex,
1013
+ fieldIndex: 2,
1014
+ });
1015
+ });
1016
+ this.pushInstruction("i32.add");
1017
+ this.releaseTemp(sliceTmp, sliceWType);
1018
+ return;
1019
+ }
1020
+
1021
+ const arrWType = {
1022
+ kind: "ref",
1023
+ nullable: true,
1024
+ typeIndex: arrInfo.typeIndex,
1025
+ };
1026
+
1027
+ const rawBaseType = baseNode._type ?? this._resolveExprGoType(baseNode);
1028
+ const baseType =
1029
+ rawBaseType?.kind === "pointer" ? rawBaseType.base : rawBaseType;
1030
+ const arrayLen =
1031
+ baseType?.kind === "array" ? (baseType.len ?? baseType.size) : null;
1032
+ let constIdx = null;
1033
+ if (indexNode.kind === "BasicLit" && indexNode.litKind === "INT") {
1034
+ try {
1035
+ const v = Number(indexNode.value);
1036
+ if (Number.isInteger(v)) constIdx = v;
1037
+ } catch {}
1038
+ }
1039
+ if (
1040
+ arrayLen !== null &&
1041
+ arrayLen !== undefined &&
1042
+ constIdx !== null &&
1043
+ constIdx >= 0 &&
1044
+ constIdx < arrayLen
1045
+ ) {
1046
+ this.emitExpr(baseNode, arrWType);
1047
+ this.pushInstruction({ op: "i32.const", value: constIdx });
1048
+ return;
1049
+ }
1050
+
1051
+ const arrTmp = this.acquireTemp(arrWType);
1052
+ this.emitExpr(baseNode, arrWType);
1053
+ this.pushInstruction({ op: "local.tee", index: arrTmp });
1054
+ this._emitCheckedIndex(indexNode, () => {
1055
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1056
+ this.pushInstruction("array.len");
1057
+ });
1058
+ this.releaseTemp(arrTmp, arrWType);
1059
+ }
1060
+
1061
+ _emitIndexExprToI32(node, tmp32) {
1062
+ if (this._isI32IndexExpr(node)) {
1063
+ this.emitExpr(node, "i32");
1064
+ this.pushInstruction({ op: "local.set", index: tmp32 });
1065
+ return;
1066
+ }
1067
+ const wType = this.toWasmType(node._type);
1068
+ if (wType === "i64") {
1069
+ this.emitExpr(node, "i64");
1070
+ this.pushInstruction("i32.wrap_i64");
1071
+ this.pushInstruction({ op: "local.set", index: tmp32 });
1072
+ } else {
1073
+ this.emitExpr(node, "i32");
1074
+ this.pushInstruction({ op: "local.set", index: tmp32 });
1075
+ }
1076
+ }
1077
+
1078
+ emitBasicLit(lit, targetWasmType) {
1079
+ const wType =
1080
+ targetWasmType ??
1081
+ toWasmType(lit._type, this.mod.checker) ??
1082
+ (lit.litKind === "INT" ? "i64" : lit.litKind === "FLOAT" ? "f64" : "i32");
1083
+
1084
+ if (lit.litKind === "INT") {
1085
+ const s = String(lit.value);
1086
+ const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
1087
+ const n = Number(clean);
1088
+ if (wType === "i32") {
1089
+ this.pushInstruction({ op: "i32.const", value: n | 0 });
1090
+ } else if (wType === "f64" || wType === "f32") {
1091
+ // Untyped integer constant in a float context (`x * 2`).
1092
+ this.pushInstruction({ op: `${wType}.const`, value: n });
1093
+ } else {
1094
+ this.pushInstruction({ op: "i64.const", value: BigInt(clean) });
1095
+ }
1096
+ return;
1097
+ }
1098
+
1099
+ if (lit.litKind === "FLOAT") {
1100
+ const n = Number(lit.value);
1101
+ if (wType === "f32") {
1102
+ this.pushInstruction({ op: "f32.const", value: n });
1103
+ } else {
1104
+ this.pushInstruction({ op: "f64.const", value: n });
1105
+ }
1106
+ return;
1107
+ }
1108
+
1109
+ if (lit.litKind === "BOOL") {
1110
+ this.pushInstruction({
1111
+ op: "i32.const",
1112
+ value: lit.value === "true" ? 1 : 0,
1113
+ });
1114
+ return;
1115
+ }
1116
+
1117
+ if (lit.litKind === "NIL") {
1118
+ this._emitNilValue(lit._type, targetWasmType);
1119
+ return;
1120
+ }
1121
+
1122
+ if (lit.litKind === "STRING") {
1123
+ // Intern string in module string table
1124
+ const strVal = String(lit.value ?? "");
1125
+ const strIdx = this.mod.internString(strVal);
1126
+ const funcIdx = this.mod.getStringImportIndex();
1127
+ this.pushInstruction({ op: "i32.const", value: strIdx });
1128
+ this.pushInstruction({ op: "call", funcIndex: funcIdx });
1129
+ return;
1130
+ }
1131
+ }
1132
+
1133
+ _emitNilValue(goType, targetWasmType) {
1134
+ const effectiveType = targetWasmType ?? this.toWasmType(goType);
1135
+ if (
1136
+ (goType && isSliceType(goType, this.mod.checker)) ||
1137
+ (effectiveType &&
1138
+ typeof effectiveType === "object" &&
1139
+ typeof effectiveType.typeIndex === "number" &&
1140
+ this.mod.getSliceTypeByIndex(effectiveType.typeIndex))
1141
+ ) {
1142
+ const elemType = goType ? this._getSliceElemType(goType) : null;
1143
+ const sliceInfo = elemType
1144
+ ? this.mod.getSliceType(elemType)
1145
+ : this.mod.getSliceTypeByIndex(effectiveType.typeIndex);
1146
+ if (sliceInfo) {
1147
+ this.pushInstruction({
1148
+ op: "global.get",
1149
+ index: sliceInfo.emptyGlobalIndex,
1150
+ });
1151
+ return;
1152
+ }
1153
+ }
1154
+ const heapType =
1155
+ typeof effectiveType === "object" && effectiveType !== null
1156
+ ? (effectiveType.typeIndex ?? effectiveType.heapType ?? "any")
1157
+ : "any";
1158
+ this.pushInstruction({ op: "ref.null", heapType });
1159
+ }
1160
+
1161
+ _coerceIntWidth(srcType, targetWasmType) {
1162
+ if (srcType === "i32" && targetWasmType === "i64") {
1163
+ this.pushInstruction("i64.extend_i32_s");
1164
+ } else if (srcType === "i64" && targetWasmType === "i32") {
1165
+ this.pushInstruction("i32.wrap_i64");
1166
+ }
1167
+ }
1168
+
1169
+ emitIdent(ident, targetWasmType = null) {
1170
+ if (ident.name === "true") {
1171
+ this.pushInstruction({ op: "i32.const", value: 1 });
1172
+ return;
1173
+ }
1174
+ if (ident.name === "false") {
1175
+ this.pushInstruction({ op: "i32.const", value: 0 });
1176
+ return;
1177
+ }
1178
+ if (ident.name === "nil") {
1179
+ this._emitNilValue(ident._type, targetWasmType);
1180
+ return;
1181
+ }
1182
+
1183
+ const localInfo = this.resolveLocal(ident.name);
1184
+ if (localInfo) {
1185
+ if (localInfo.isBoxed) {
1186
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
1187
+ this.pushInstruction({
1188
+ op: "struct.get",
1189
+ typeIndex: localInfo.boxInfo.typeIndex,
1190
+ fieldIndex: 0,
1191
+ });
1192
+ } else {
1193
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
1194
+ }
1195
+ this._coerceIntWidth(localInfo.type, targetWasmType);
1196
+ return;
1197
+ }
1198
+
1199
+ if (this.cachedGlobals.has(ident.name)) {
1200
+ const cached = this.cachedGlobals.get(ident.name);
1201
+ this.pushInstruction({ op: "local.get", index: cached.index });
1202
+ this._coerceIntWidth(cached.globalInfo.type, targetWasmType);
1203
+ return;
1204
+ }
1205
+
1206
+ const globalInfo = this.mod.resolveGlobal(ident.name);
1207
+ if (globalInfo) {
1208
+ this.pushInstruction({ op: "global.get", index: globalInfo.index });
1209
+ this._coerceIntWidth(globalInfo.type, targetWasmType);
1210
+ return;
1211
+ }
1212
+
1213
+ const constLit = this.mod.resolveConst(ident.name);
1214
+ if (constLit) {
1215
+ this.emitBasicLit(
1216
+ constLit,
1217
+ targetWasmType ?? this.toWasmType(ident._type),
1218
+ );
1219
+ return;
1220
+ }
1221
+
1222
+ const trampName = `_tramp$${ident.name}`;
1223
+ const trampFuncIdx = this.mod.resolveFuncIndex(trampName);
1224
+ if (trampFuncIdx !== null) {
1225
+ const targetFn = this.mod.topLevelFuncMap?.get(ident.name);
1226
+ const goType =
1227
+ ident._type ??
1228
+ (targetFn
1229
+ ? {
1230
+ kind: "func",
1231
+ params: (targetFn.params ?? []).map((p) => p.type ?? p),
1232
+ returns: targetFn.returnType
1233
+ ? targetFn.returnType.kind === "TupleType" ||
1234
+ targetFn.returnType.kind === "tuple"
1235
+ ? targetFn.returnType.types
1236
+ : [targetFn.returnType]
1237
+ : [],
1238
+ }
1239
+ : null);
1240
+ const closureInfo = this.mod.getClosureType(goType);
1241
+ this.pushInstruction({ op: "ref.func", funcIndex: trampFuncIdx });
1242
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
1243
+ this.pushInstruction({
1244
+ op: "struct.new",
1245
+ typeIndex: closureInfo.typeIndex,
1246
+ });
1247
+ return;
1248
+ }
1249
+
1250
+ throw new Error(`Unresolved identifier: ${ident.name}`);
1251
+ }
1252
+
1253
+ emitUnaryExpr(expr, targetWasmType = null) {
1254
+ const { op, operand } = expr;
1255
+
1256
+ // Optimize negative numeric literals directly
1257
+ if (op === "-" && operand.kind === "BasicLit") {
1258
+ if (operand.litKind === "INT" || operand.litKind === "FLOAT") {
1259
+ this.emitBasicLit(
1260
+ { ...operand, value: `-${operand.value}` },
1261
+ targetWasmType,
1262
+ );
1263
+ return;
1264
+ }
1265
+ }
1266
+
1267
+ const wType =
1268
+ targetWasmType ??
1269
+ toWasmType(expr._type, this.mod.checker) ??
1270
+ toWasmType(operand._type, this.mod.checker);
1271
+
1272
+ switch (op) {
1273
+ case "+":
1274
+ this.emitExpr(operand, wType);
1275
+ break;
1276
+
1277
+ case "-":
1278
+ if (wType === "i32") {
1279
+ this.pushInstruction({ op: "i32.const", value: 0 });
1280
+ this.emitExpr(operand, wType);
1281
+ this.pushInstruction("i32.sub");
1282
+ this.emitNarrowIntWrap(operand._type);
1283
+ } else if (wType === "i64") {
1284
+ this.pushInstruction({ op: "i64.const", value: 0n });
1285
+ this.emitExpr(operand, wType);
1286
+ this.pushInstruction("i64.sub");
1287
+ } else if (wType === "f32") {
1288
+ this.emitExpr(operand, wType);
1289
+ this.pushInstruction("f32.neg");
1290
+ } else if (wType === "f64") {
1291
+ this.emitExpr(operand, wType);
1292
+ this.pushInstruction("f64.neg");
1293
+ }
1294
+ break;
1295
+
1296
+ case "!":
1297
+ this.emitExpr(operand, "i32");
1298
+ this.pushInstruction("i32.eqz");
1299
+ break;
1300
+
1301
+ case "^": // bitwise NOT
1302
+ this.emitExpr(operand, wType);
1303
+ if (wType === "i32") {
1304
+ this.pushInstruction({ op: "i32.const", value: -1 });
1305
+ this.pushInstruction("i32.xor");
1306
+ this.emitNarrowIntWrap(operand._type);
1307
+ } else if (wType === "i64") {
1308
+ this.pushInstruction({ op: "i64.const", value: -1n });
1309
+ this.pushInstruction("i64.xor");
1310
+ }
1311
+ break;
1312
+
1313
+ case "&": {
1314
+ if (operand.kind === "Ident") {
1315
+ const loc = this.resolveLocal(operand.name);
1316
+ if (loc?.isBoxed) {
1317
+ this.pushInstruction({ op: "local.get", index: loc.index });
1318
+ return;
1319
+ }
1320
+ }
1321
+ if (operand.kind === "CompositeLit") {
1322
+ this.emitExpr(operand);
1323
+ return;
1324
+ }
1325
+ const structInfo = this._resolveStructInfo(operand);
1326
+ if (structInfo) {
1327
+ this.emitExpr(operand);
1328
+ return;
1329
+ }
1330
+ const opWType = this.toWasmType(operand._type);
1331
+ const box = this.mod.getBoxType(operand._type);
1332
+ this.emitExpr(operand, opWType);
1333
+ this.pushInstruction({ op: "struct.new", typeIndex: box.typeIndex });
1334
+ return;
1335
+ }
1336
+
1337
+ case "*": {
1338
+ const structInfo = this._resolveStructInfo(operand);
1339
+ if (structInfo) {
1340
+ const baseWType = this.toWasmType(operand._type);
1341
+ this.emitExpr(operand, baseWType);
1342
+ this.emitCloneStruct(structInfo, baseWType);
1343
+ return;
1344
+ }
1345
+ const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
1346
+ const baseWType = {
1347
+ kind: "ref",
1348
+ nullable: true,
1349
+ typeIndex: box.typeIndex,
1350
+ };
1351
+ this.emitExpr(operand, baseWType);
1352
+ this.pushInstruction({
1353
+ op: "struct.get",
1354
+ typeIndex: box.typeIndex,
1355
+ fieldIndex: 0,
1356
+ });
1357
+ return;
1358
+ }
1359
+
1360
+ default:
1361
+ throw new Error(`Unsupported unary operator: ${op}`);
1362
+ }
1363
+ }
1364
+
1365
+ emitBinaryExpr(expr, targetWasmType = null) {
1366
+ const { op, left, right } = expr;
1367
+
1368
+ // Short-circuiting logical operators
1369
+ if (op === "&&") {
1370
+ this.emitExpr(left, "i32");
1371
+ this.pushInstruction({ op: "if", blockType: "i32" });
1372
+ this.emitExpr(right, "i32");
1373
+ this.pushInstruction("else");
1374
+ this.pushInstruction({ op: "i32.const", value: 0 });
1375
+ this.pushInstruction("end");
1376
+ return;
1377
+ }
1378
+
1379
+ if (op === "||") {
1380
+ this.emitExpr(left, "i32");
1381
+ this.pushInstruction({ op: "if", blockType: "i32" });
1382
+ this.pushInstruction({ op: "i32.const", value: 1 });
1383
+ this.pushInstruction("else");
1384
+ this.emitExpr(right, "i32");
1385
+ this.pushInstruction("end");
1386
+ return;
1387
+ }
1388
+
1389
+ // Division & Modulo with zero-guard and overflow-guard
1390
+ if (op === "/" || op === "%") {
1391
+ this.emitDivRem(op, left, right, targetWasmType);
1392
+ return;
1393
+ }
1394
+
1395
+ // Shifts with Go semantics
1396
+ if (op === "<<" || op === ">>") {
1397
+ this.emitShift(op, left, right, targetWasmType);
1398
+ return;
1399
+ }
1400
+
1401
+ // Standard binary operators
1402
+ const isCmp =
1403
+ op === "==" ||
1404
+ op === "!=" ||
1405
+ op === "<" ||
1406
+ op === "<=" ||
1407
+ op === ">" ||
1408
+ op === ">=";
1409
+
1410
+ const isNil = (n) =>
1411
+ (n.kind === "BasicLit" && n.litKind === "NIL") ||
1412
+ (n.kind === "Ident" && n.name === "nil");
1413
+
1414
+ if ((op === "==" || op === "!=") && (isNil(left) || isNil(right))) {
1415
+ const nonNil = isNil(right) ? left : right;
1416
+ if (isSliceType(nonNil._type, this.mod.checker)) {
1417
+ const elemType = this._getSliceElemType(nonNil._type);
1418
+ const sliceInfo = this.mod.getSliceType(elemType);
1419
+ const sliceWType = {
1420
+ kind: "ref",
1421
+ nullable: true,
1422
+ typeIndex: sliceInfo.typeIndex,
1423
+ };
1424
+ this.emitExpr(nonNil, sliceWType);
1425
+ this.pushInstruction({
1426
+ op: "struct.get",
1427
+ typeIndex: sliceInfo.typeIndex,
1428
+ fieldIndex: 0,
1429
+ });
1430
+ this.pushInstruction("ref.is_null");
1431
+ if (op === "!=") {
1432
+ this.pushInstruction("i32.eqz");
1433
+ }
1434
+ return;
1435
+ }
1436
+ const nonNilWType = this.toWasmType(nonNil._type);
1437
+ this.emitExpr(nonNil, nonNilWType);
1438
+ this.pushInstruction("ref.is_null");
1439
+ if (op === "!=") {
1440
+ this.pushInstruction("i32.eqz");
1441
+ }
1442
+ return;
1443
+ }
1444
+
1445
+ const leftType = left._type;
1446
+ const isLeftI32 = this._isI32IndexExpr(left);
1447
+ const isRightI32 = this._isI32IndexExpr(right);
1448
+ const leftGoType = left._type ?? this._resolveExprGoType(left);
1449
+ const isIntType =
1450
+ !leftGoType ||
1451
+ (leftGoType.kind === "basic" &&
1452
+ (leftGoType.name === "int" ||
1453
+ leftGoType.name === "untyped int" ||
1454
+ leftGoType.name === "int32" ||
1455
+ leftGoType.name === "uint32"));
1456
+ const cmpI32 = isIntType && isLeftI32 && isRightI32;
1457
+
1458
+ const wType = isCmp
1459
+ ? cmpI32
1460
+ ? "i32"
1461
+ : this.toWasmType(leftType)
1462
+ : (targetWasmType ?? this.toWasmType(leftType));
1463
+
1464
+ this.emitExpr(left, wType);
1465
+ this.emitExpr(right, wType);
1466
+
1467
+ if (
1468
+ (op === "==" || op === "!=") &&
1469
+ isStructType(leftType, this.mod.checker, this.mod) &&
1470
+ !isPointerToStruct(leftType, this.mod.checker, this.mod)
1471
+ ) {
1472
+ const sInfo =
1473
+ this._resolveStructInfo({ _type: leftType }) ??
1474
+ this.mod.getStructType(
1475
+ leftType?.name ?? (leftType?.kind === "named" ? leftType.name : null),
1476
+ );
1477
+ if (sInfo) {
1478
+ this._emitStructEq(sInfo);
1479
+ if (op === "!=") {
1480
+ this.pushInstruction("i32.eqz");
1481
+ }
1482
+ return;
1483
+ }
1484
+ }
1485
+
1486
+ this.emitBinaryOp(op, wType, leftType);
1487
+ }
1488
+
1489
+ _emitStructEq(structInfo) {
1490
+ const bTmp = this.acquireTemp({
1491
+ kind: "ref",
1492
+ nullable: true,
1493
+ typeIndex: structInfo.typeIndex,
1494
+ });
1495
+ const aTmp = this.acquireTemp({
1496
+ kind: "ref",
1497
+ nullable: true,
1498
+ typeIndex: structInfo.typeIndex,
1499
+ });
1500
+ this.pushInstruction({ op: "local.set", index: bTmp });
1501
+ this.pushInstruction({ op: "local.set", index: aTmp });
1502
+
1503
+ const resTmp = this.acquireTemp("i32");
1504
+ this.pushInstruction({ op: "i32.const", value: 1 });
1505
+ this.pushInstruction({ op: "local.set", index: resTmp });
1506
+
1507
+ this.pushInstruction({ op: "block", blockType: "void" });
1508
+
1509
+ for (let i = 0; i < structInfo.fields.length; i++) {
1510
+ const f = structInfo.fields[i];
1511
+ this.pushInstruction({ op: "local.get", index: aTmp });
1512
+ this.pushInstruction({
1513
+ op: "struct.get",
1514
+ typeIndex: structInfo.typeIndex,
1515
+ fieldIndex: i,
1516
+ });
1517
+ this.pushInstruction({ op: "local.get", index: bTmp });
1518
+ this.pushInstruction({
1519
+ op: "struct.get",
1520
+ typeIndex: structInfo.typeIndex,
1521
+ fieldIndex: i,
1522
+ });
1523
+
1524
+ if (
1525
+ isStructType(f.goType, this.mod.checker, this.mod) &&
1526
+ !isPointerToStruct(f.goType, this.mod.checker, this.mod)
1527
+ ) {
1528
+ const sInfo =
1529
+ this._resolveStructInfo({ _type: f.goType }) ??
1530
+ this.mod.getStructType(
1531
+ f.goType?.name ??
1532
+ (f.goType?.kind === "named" ? f.goType.name : null),
1533
+ );
1534
+ this._emitStructEq(sInfo);
1535
+ } else if (isStringType(f.goType, this.mod.checker)) {
1536
+ const cmpIdx = this.mod.getStringCmpImportIndex("==");
1537
+ this.pushInstruction({ op: "call", funcIndex: cmpIdx });
1538
+ } else if (f.wType === "anyref") {
1539
+ this._emitAnyEq();
1540
+ } else if (typeof f.wType === "object" && f.wType !== null) {
1541
+ this.pushInstruction("ref.eq");
1542
+ } else {
1543
+ this.pushInstruction(`${f.wType}.eq`);
1544
+ }
1545
+
1546
+ this.pushInstruction("i32.eqz");
1547
+ this.pushInstruction({ op: "if", blockType: "void" });
1548
+ this.pushInstruction({ op: "i32.const", value: 0 });
1549
+ this.pushInstruction({ op: "local.set", index: resTmp });
1550
+ this.pushInstruction({ op: "br", depth: 1 });
1551
+ this.pushInstruction("end");
1552
+ }
1553
+ this.pushInstruction("end");
1554
+
1555
+ this.pushInstruction({ op: "local.get", index: resTmp });
1556
+
1557
+ this.releaseTemp(resTmp, "i32");
1558
+ this.releaseTemp(aTmp, {
1559
+ kind: "ref",
1560
+ nullable: true,
1561
+ typeIndex: structInfo.typeIndex,
1562
+ });
1563
+ this.releaseTemp(bTmp, {
1564
+ kind: "ref",
1565
+ nullable: true,
1566
+ typeIndex: structInfo.typeIndex,
1567
+ });
1568
+ }
1569
+
1570
+ emitBinaryOp(op, wType, goType) {
1571
+ const signed = isSigned(goType);
1572
+
1573
+ switch (op) {
1574
+ case "+":
1575
+ if (isStringType(goType, this.mod.checker) || wType === "externref") {
1576
+ const concatIdx = this.mod.getStringConcatImportIndex();
1577
+ this.pushInstruction({ op: "call", funcIndex: concatIdx });
1578
+ return;
1579
+ }
1580
+ this.pushInstruction(`${wType}.add`);
1581
+ this.emitNarrowIntWrap(goType);
1582
+ break;
1583
+ case "-":
1584
+ this.pushInstruction(`${wType}.sub`);
1585
+ this.emitNarrowIntWrap(goType);
1586
+ break;
1587
+ case "*":
1588
+ this.pushInstruction(`${wType}.mul`);
1589
+ this.emitNarrowIntWrap(goType);
1590
+ break;
1591
+ case "/":
1592
+ if (wType === "f32" || wType === "f64") {
1593
+ this.pushInstruction(`${wType}.div`);
1594
+ } else {
1595
+ this.pushInstruction(signed ? `${wType}.div_s` : `${wType}.div_u`);
1596
+ this.emitNarrowIntWrap(goType);
1597
+ }
1598
+ break;
1599
+ case "%":
1600
+ this.pushInstruction(signed ? `${wType}.rem_s` : `${wType}.rem_u`);
1601
+ this.emitNarrowIntWrap(goType);
1602
+ break;
1603
+ case "<<":
1604
+ this.pushInstruction(`${wType}.shl`);
1605
+ this.emitNarrowIntWrap(goType);
1606
+ break;
1607
+ case ">>":
1608
+ this.pushInstruction(signed ? `${wType}.shr_s` : `${wType}.shr_u`);
1609
+ this.emitNarrowIntWrap(goType);
1610
+ break;
1611
+
1612
+ case "&":
1613
+ this.pushInstruction(`${wType}.and`);
1614
+ break;
1615
+ case "|":
1616
+ this.pushInstruction(`${wType}.or`);
1617
+ break;
1618
+ case "^":
1619
+ this.pushInstruction(`${wType}.xor`);
1620
+ break;
1621
+ case "&^": // AND NOT: a &^ b -> a & (~b)
1622
+ if (wType === "i32") {
1623
+ this.pushInstruction({ op: "i32.const", value: -1 });
1624
+ this.pushInstruction("i32.xor");
1625
+ this.pushInstruction("i32.and");
1626
+ } else if (wType === "i64") {
1627
+ this.pushInstruction({ op: "i64.const", value: -1n });
1628
+ this.pushInstruction("i64.xor");
1629
+ this.pushInstruction("i64.and");
1630
+ }
1631
+ break;
1632
+
1633
+ // Comparisons
1634
+ case "==":
1635
+ if (isStringType(goType, this.mod.checker) || wType === "externref") {
1636
+ const cmpIdx = this.mod.getStringCmpImportIndex("==");
1637
+ this.pushInstruction({ op: "call", funcIndex: cmpIdx });
1638
+ } else if (wType === "anyref") {
1639
+ this._emitAnyEq();
1640
+ } else if (typeof wType === "object" && wType !== null) {
1641
+ this.pushInstruction("ref.eq");
1642
+ } else {
1643
+ this.pushInstruction(`${wType}.eq`);
1644
+ }
1645
+ break;
1646
+ case "!=":
1647
+ if (isStringType(goType, this.mod.checker) || wType === "externref") {
1648
+ const cmpIdx = this.mod.getStringCmpImportIndex("!=");
1649
+ this.pushInstruction({ op: "call", funcIndex: cmpIdx });
1650
+ } else if (wType === "anyref") {
1651
+ this._emitAnyEq();
1652
+ this.pushInstruction("i32.eqz");
1653
+ } else if (typeof wType === "object" && wType !== null) {
1654
+ this.pushInstruction("ref.eq");
1655
+ this.pushInstruction("i32.eqz");
1656
+ } else {
1657
+ this.pushInstruction(`${wType}.ne`);
1658
+ }
1659
+ break;
1660
+ case "<":
1661
+ case "<=":
1662
+ case ">":
1663
+ case ">=": {
1664
+ if (isStringType(goType, this.mod.checker) || wType === "externref") {
1665
+ const cmpIdx = this.mod.getStringCmpImportIndex(op);
1666
+ this.pushInstruction({ op: "call", funcIndex: cmpIdx });
1667
+ } else {
1668
+ const name =
1669
+ op === "<" ? "lt" : op === "<=" ? "le" : op === ">" ? "gt" : "ge";
1670
+ const suffix =
1671
+ wType === "f32" || wType === "f64" ? "" : `_${signed ? "s" : "u"}`;
1672
+ this.pushInstruction(`${wType}.${name}${suffix}`);
1673
+ }
1674
+ break;
1675
+ }
1676
+
1677
+ default:
1678
+ throw new Error(`Unsupported binary operator: ${op}`);
1679
+ }
1680
+ }
1681
+
1682
+ emitNarrowIntWrap(goType) {
1683
+ if (!goType) return;
1684
+ if (goType.kind === "TypeName" || goType.kind === "Ident") {
1685
+ return this.emitNarrowIntWrap({ kind: "basic", name: goType.name });
1686
+ }
1687
+ if (goType.kind === "named" && goType.underlying) {
1688
+ return this.emitNarrowIntWrap(goType.underlying);
1689
+ }
1690
+ if (goType.kind === "basic") {
1691
+ switch (goType.name) {
1692
+ case "int8":
1693
+ this.pushInstruction("i32.extend8_s");
1694
+ break;
1695
+ case "int16":
1696
+ this.pushInstruction("i32.extend16_s");
1697
+ break;
1698
+ case "uint8":
1699
+ case "byte":
1700
+ this.pushInstruction({ op: "i32.const", value: 0xff });
1701
+ this.pushInstruction("i32.and");
1702
+ break;
1703
+ case "uint16":
1704
+ this.pushInstruction({ op: "i32.const", value: 0xffff });
1705
+ this.pushInstruction("i32.and");
1706
+ break;
1707
+ }
1708
+ }
1709
+ }
1710
+
1711
+ emitDivRem(op, left, right, targetWasmType = null) {
1712
+ const goType = left._type;
1713
+ const wType = targetWasmType ?? toWasmType(goType, this.mod.checker);
1714
+
1715
+ if (wType === "f32" || wType === "f64") {
1716
+ this.emitExpr(left, wType);
1717
+ this.emitExpr(right, wType);
1718
+ this.pushInstruction(`${wType}.div`);
1719
+ return;
1720
+ }
1721
+
1722
+ const signed = isSigned(goType);
1723
+ const is64 = wType === "i64";
1724
+ const divOp = `${wType}.${op === "/" ? "div" : "rem"}_${signed ? "s" : "u"}`;
1725
+
1726
+ let constDivisor = null;
1727
+ if (right.kind === "BasicLit" && right.litKind === "INT") {
1728
+ try {
1729
+ constDivisor = BigInt(right.value);
1730
+ } catch {}
1731
+ }
1732
+
1733
+ if (constDivisor !== null && constDivisor !== 0n && constDivisor !== -1n) {
1734
+ // Specialize unsigned powers-of-two to shifts or bitwise masks
1735
+ if (
1736
+ !signed &&
1737
+ constDivisor > 0n &&
1738
+ (constDivisor & (constDivisor - 1n)) === 0n
1739
+ ) {
1740
+ let k = 0n;
1741
+ let temp = constDivisor;
1742
+ while (temp > 1n) {
1743
+ temp >>= 1n;
1744
+ k++;
1745
+ }
1746
+ if (op === "/") {
1747
+ this.emitExpr(left, wType);
1748
+ this.pushInstruction({
1749
+ op: `${wType}.const`,
1750
+ value: is64 ? k : Number(k),
1751
+ });
1752
+ this.pushInstruction(`${wType}.shr_u`);
1753
+ } else {
1754
+ this.emitExpr(left, wType);
1755
+ this.pushInstruction({
1756
+ op: `${wType}.const`,
1757
+ value: is64 ? constDivisor - 1n : Number(constDivisor - 1n),
1758
+ });
1759
+ this.pushInstruction(`${wType}.and`);
1760
+ }
1761
+ if (!is64) this.emitNarrowIntWrap(goType);
1762
+ return;
1763
+ }
1764
+
1765
+ // Constant divisor != 0 and != -1: no dynamic zero or overflow check needed
1766
+ this.emitExpr(left, wType);
1767
+ this.emitExpr(right, wType);
1768
+ this.pushInstruction(divOp);
1769
+ if (!is64) this.emitNarrowIntWrap(goType);
1770
+ return;
1771
+ }
1772
+
1773
+ const tmpA = this.acquireTemp(wType);
1774
+ const tmpB = this.acquireTemp(wType);
1775
+
1776
+ this.emitExpr(left, wType);
1777
+ this.pushInstruction({ op: "local.set", index: tmpA });
1778
+
1779
+ this.emitExpr(right, wType);
1780
+ this.pushInstruction({ op: "local.set", index: tmpB });
1781
+
1782
+ // Zero check
1783
+ this.pushInstruction({ op: "local.get", index: tmpB });
1784
+ this.pushInstruction(`${wType}.eqz`);
1785
+ this.pushInstruction({ op: "if", blockType: "void" });
1786
+ this.emitPanic("runtime error: integer divide by zero");
1787
+ this.pushInstruction("end");
1788
+
1789
+ if (wType === "i32" || wType === "i64") {
1790
+ if (signed) {
1791
+ const minVal = is64 ? -9223372036854775808n : -2147483648;
1792
+ const negOne = is64 ? -1n : -1;
1793
+ const zeroVal = is64 ? 0n : 0;
1794
+ this.pushInstruction({ op: "local.get", index: tmpA });
1795
+ this.pushInstruction({ op: `${wType}.const`, value: minVal });
1796
+ this.pushInstruction(`${wType}.eq`);
1797
+ this.pushInstruction({ op: "local.get", index: tmpB });
1798
+ this.pushInstruction({ op: `${wType}.const`, value: negOne });
1799
+ this.pushInstruction(`${wType}.eq`);
1800
+ this.pushInstruction("i32.and");
1801
+ this.pushInstruction({ op: "if", blockType: wType });
1802
+ this.pushInstruction({
1803
+ op: `${wType}.const`,
1804
+ value: op === "/" ? minVal : zeroVal,
1805
+ });
1806
+ this.pushInstruction("else");
1807
+ this.pushInstruction({ op: "local.get", index: tmpA });
1808
+ this.pushInstruction({ op: "local.get", index: tmpB });
1809
+ this.pushInstruction(divOp);
1810
+ this.pushInstruction("end");
1811
+ } else {
1812
+ this.pushInstruction({ op: "local.get", index: tmpA });
1813
+ this.pushInstruction({ op: "local.get", index: tmpB });
1814
+ this.pushInstruction(divOp);
1815
+ }
1816
+ if (!is64) this.emitNarrowIntWrap(goType);
1817
+ }
1818
+
1819
+ this.releaseTemp(tmpB, wType);
1820
+ this.releaseTemp(tmpA, wType);
1821
+ }
1822
+
1823
+ emitShift(op, left, right, targetWasmType = null) {
1824
+ const goType = left._type;
1825
+ const wType = targetWasmType ?? toWasmType(goType, this.mod.checker);
1826
+ const signed = isSigned(goType);
1827
+ const rightWType = toWasmType(right._type, this.mod.checker);
1828
+ const is64 = wType === "i64";
1829
+ const zeroVal = is64 ? 0n : 0;
1830
+ const negOne = is64 ? -1n : -1;
1831
+ const widthVal = is64 ? 64n : 32;
1832
+
1833
+ const tmpVal = this.acquireTemp(wType);
1834
+ const tmpShift = this.acquireTemp(wType);
1835
+
1836
+ this.emitExpr(left, wType);
1837
+ this.pushInstruction({ op: "local.set", index: tmpVal });
1838
+
1839
+ this.emitExpr(right, rightWType);
1840
+ if (wType === "i64" && rightWType === "i32") {
1841
+ this.pushInstruction("i64.extend_i32_u");
1842
+ } else if (wType === "i32" && rightWType === "i64") {
1843
+ this.pushInstruction("i32.wrap_i64");
1844
+ }
1845
+ this.pushInstruction({ op: "local.set", index: tmpShift });
1846
+
1847
+ this.pushInstruction({ op: "local.get", index: tmpShift });
1848
+ this.pushInstruction({ op: `${wType}.const`, value: widthVal });
1849
+ this.pushInstruction(`${wType}.ge_u`);
1850
+ this.pushInstruction({ op: "if", blockType: wType });
1851
+
1852
+ if (op === ">>" && signed) {
1853
+ this.pushInstruction({ op: "local.get", index: tmpVal });
1854
+ this.pushInstruction({ op: `${wType}.const`, value: zeroVal });
1855
+ this.pushInstruction(`${wType}.lt_s`);
1856
+ this.pushInstruction({ op: "if", blockType: wType });
1857
+ this.pushInstruction({ op: `${wType}.const`, value: negOne });
1858
+ this.pushInstruction("else");
1859
+ this.pushInstruction({ op: `${wType}.const`, value: zeroVal });
1860
+ this.pushInstruction("end");
1861
+ } else {
1862
+ this.pushInstruction({ op: `${wType}.const`, value: zeroVal });
1863
+ }
1864
+
1865
+ this.pushInstruction("else");
1866
+
1867
+ this.pushInstruction({ op: "local.get", index: tmpVal });
1868
+ this.pushInstruction({ op: "local.get", index: tmpShift });
1869
+ this.pushInstruction(
1870
+ op === "<<" ? `${wType}.shl` : `${wType}.shr_${signed ? "s" : "u"}`,
1871
+ );
1872
+
1873
+ this.pushInstruction("end");
1874
+ if (!is64) this.emitNarrowIntWrap(goType);
1875
+
1876
+ this.releaseTemp(tmpShift, wType);
1877
+ this.releaseTemp(tmpVal, wType);
1878
+ }
1879
+
1880
+ emitTypeConversion(conv) {
1881
+ const { targetType, expr } = conv;
1882
+ const fromType = expr._type;
1883
+ const fromWType = toWasmType(fromType, this.mod.checker);
1884
+ const toWType = toWasmType(targetType, this.mod.checker);
1885
+
1886
+ this.emitExpr(expr, fromWType);
1887
+
1888
+ if (fromWType === toWType) {
1889
+ this.emitNarrowIntWrap(targetType);
1890
+ return;
1891
+ }
1892
+
1893
+ if (
1894
+ fromWType === "externref" &&
1895
+ isSliceType(targetType, this.mod.checker) &&
1896
+ (targetType.elem?.name === "byte" || targetType.elem?.name === "uint8")
1897
+ ) {
1898
+ this._emitStringToByteSlice(targetType);
1899
+ return;
1900
+ }
1901
+
1902
+ if (
1903
+ toWType === "externref" &&
1904
+ isSliceType(fromType, this.mod.checker) &&
1905
+ (fromType.elem?.name === "byte" || fromType.elem?.name === "uint8")
1906
+ ) {
1907
+ this._emitByteSliceToString(fromType);
1908
+ return;
1909
+ }
1910
+
1911
+ if (toWType === "externref") {
1912
+ if (fromWType === "i64") {
1913
+ this.pushInstruction("i32.wrap_i64");
1914
+ }
1915
+ const funcIdx = this.mod.getStringFromCodePointImportIndex();
1916
+ this.pushInstruction({ op: "call", funcIndex: funcIdx });
1917
+ return;
1918
+ }
1919
+
1920
+ if (toWType === "anyref") {
1921
+ if (fromWType === "externref") {
1922
+ this.pushInstruction("any.convert_extern");
1923
+ }
1924
+ return;
1925
+ }
1926
+
1927
+ const srcSign = isSigned(fromType) ? "s" : "u";
1928
+ const dstSign = isSigned(targetType) ? "s" : "u";
1929
+ if (fromWType === "i32" && toWType === "i64") {
1930
+ this.pushInstruction(`i64.extend_i32_${srcSign}`);
1931
+ } else if (fromWType === "i64" && toWType === "i32") {
1932
+ this.pushInstruction("i32.wrap_i64");
1933
+ this.emitNarrowIntWrap(targetType);
1934
+ } else if (
1935
+ (fromWType === "i32" || fromWType === "i64") &&
1936
+ (toWType === "f32" || toWType === "f64")
1937
+ ) {
1938
+ this.pushInstruction(`${toWType}.convert_${fromWType}_${srcSign}`);
1939
+ } else if (fromWType === "f32" && toWType === "f64") {
1940
+ this.pushInstruction("f64.promote_f32");
1941
+ } else if (fromWType === "f64" && toWType === "f32") {
1942
+ this.pushInstruction("f32.demote_f64");
1943
+ } else if (
1944
+ (fromWType === "f32" || fromWType === "f64") &&
1945
+ (toWType === "i32" || toWType === "i64")
1946
+ ) {
1947
+ this.pushInstruction(`${toWType}.trunc_sat_${fromWType}_${dstSign}`);
1948
+ if (toWType === "i32") this.emitNarrowIntWrap(targetType);
1949
+ }
1950
+ }
1951
+
1952
+ _emitStringToByteSlice(targetType) {
1953
+ const elemGo = targetType.elem;
1954
+ const sliceInfo = this.mod.getSliceType(elemGo);
1955
+ const arrInfo = sliceInfo.arrInfo;
1956
+ const strTmp = this.acquireTemp("externref");
1957
+ this.pushInstruction({ op: "local.set", index: strTmp });
1958
+ const lenTmp = this.acquireTemp("i32");
1959
+ this.pushInstruction({ op: "local.get", index: strTmp });
1960
+ this.pushInstruction({
1961
+ op: "call",
1962
+ funcIndex: this.mod.getStrToBytesImportIndex(),
1963
+ });
1964
+ this.pushInstruction({ op: "local.tee", index: lenTmp });
1965
+ this.pushInstruction({
1966
+ op: "array.new_default",
1967
+ typeIndex: arrInfo.typeIndex,
1968
+ });
1969
+ const arrTmp = this.acquireTemp({
1970
+ kind: "ref",
1971
+ nullable: true,
1972
+ typeIndex: arrInfo.typeIndex,
1973
+ });
1974
+ this.pushInstruction({ op: "local.set", index: arrTmp });
1975
+ const iTmp = this.acquireTemp("i32");
1976
+ this.pushInstruction({ op: "i32.const", value: 0 });
1977
+ this.pushInstruction({ op: "local.set", index: iTmp });
1978
+
1979
+ this.pushInstruction({ op: "block", blockType: "void" });
1980
+ this.pushInstruction({ op: "loop", blockType: "void" });
1981
+ this.pushInstruction({ op: "local.get", index: iTmp });
1982
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1983
+ this.pushInstruction("i32.ge_s");
1984
+ this.pushInstruction({ op: "br_if", depth: 1 });
1985
+
1986
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1987
+ this.pushInstruction({ op: "local.get", index: iTmp });
1988
+ this.pushInstruction({ op: "local.get", index: iTmp });
1989
+ this.pushInstruction({
1990
+ op: "call",
1991
+ funcIndex: this.mod.getStrByteAtImportIndex(),
1992
+ });
1993
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1994
+
1995
+ this.pushInstruction({ op: "local.get", index: iTmp });
1996
+ this.pushInstruction({ op: "i32.const", value: 1 });
1997
+ this.pushInstruction("i32.add");
1998
+ this.pushInstruction({ op: "local.set", index: iTmp });
1999
+ this.pushInstruction({ op: "br", depth: 0 });
2000
+ this.pushInstruction("end");
2001
+ this.pushInstruction("end");
2002
+
2003
+ this.pushInstruction({ op: "local.get", index: arrTmp });
2004
+ this.pushInstruction({ op: "i32.const", value: 0 });
2005
+ this.pushInstruction({ op: "local.get", index: lenTmp });
2006
+ this.pushInstruction({ op: "local.get", index: lenTmp });
2007
+ this.pushInstruction({
2008
+ op: "struct.new",
2009
+ typeIndex: sliceInfo.typeIndex,
2010
+ });
2011
+
2012
+ this.releaseTemp(arrTmp, {
2013
+ kind: "ref",
2014
+ nullable: true,
2015
+ typeIndex: arrInfo.typeIndex,
2016
+ });
2017
+ this.releaseTemp(iTmp, "i32");
2018
+ this.releaseTemp(lenTmp, "i32");
2019
+ this.releaseTemp(strTmp, "externref");
2020
+ }
2021
+
2022
+ _emitByteSliceToString(fromType) {
2023
+ const elemGo = fromType.elem;
2024
+ const sliceInfo = this.mod.getSliceType(elemGo);
2025
+ const arrInfo = sliceInfo.arrInfo;
2026
+ const sliceW = {
2027
+ kind: "ref",
2028
+ nullable: true,
2029
+ typeIndex: sliceInfo.typeIndex,
2030
+ };
2031
+ const arrW = {
2032
+ kind: "ref",
2033
+ nullable: true,
2034
+ typeIndex: arrInfo.typeIndex,
2035
+ };
2036
+ const sTmp = this.acquireTemp(sliceW);
2037
+ this.pushInstruction({ op: "local.set", index: sTmp });
2038
+ const arrTmp = this.acquireTemp(arrW);
2039
+ const offTmp = this.acquireTemp("i32");
2040
+ const lenTmp = this.acquireTemp("i32");
2041
+ const capTmp = this.acquireTemp("i32");
2042
+ this._emitSliceUnpack(sTmp, sliceInfo, {
2043
+ arr: arrTmp,
2044
+ off: offTmp,
2045
+ len: lenTmp,
2046
+ cap: capTmp,
2047
+ });
2048
+ this.releaseTemp(sTmp, sliceW);
2049
+ this.releaseTemp(capTmp, "i32");
2050
+
2051
+ this.pushInstruction({ op: "local.get", index: lenTmp });
2052
+ this.pushInstruction({
2053
+ op: "call",
2054
+ funcIndex: this.mod.getBytesToStrInitImportIndex(),
2055
+ });
2056
+
2057
+ const iTmp = this.acquireTemp("i32");
2058
+ this.pushInstruction({ op: "i32.const", value: 0 });
2059
+ this.pushInstruction({ op: "local.set", index: iTmp });
2060
+
2061
+ this.pushInstruction({ op: "block", blockType: "void" });
2062
+ this.pushInstruction({ op: "loop", blockType: "void" });
2063
+ this.pushInstruction({ op: "local.get", index: iTmp });
2064
+ this.pushInstruction({ op: "local.get", index: lenTmp });
2065
+ this.pushInstruction("i32.ge_s");
2066
+ this.pushInstruction({ op: "br_if", depth: 1 });
2067
+
2068
+ this.pushInstruction({ op: "local.get", index: arrTmp });
2069
+ this.pushInstruction({ op: "local.get", index: offTmp });
2070
+ this.pushInstruction({ op: "local.get", index: iTmp });
2071
+ this.pushInstruction("i32.add");
2072
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
2073
+ this.pushInstruction({
2074
+ op: "call",
2075
+ funcIndex: this.mod.getBytesToStrPushImportIndex(),
2076
+ });
2077
+
2078
+ this.pushInstruction({ op: "local.get", index: iTmp });
2079
+ this.pushInstruction({ op: "i32.const", value: 1 });
2080
+ this.pushInstruction("i32.add");
2081
+ this.pushInstruction({ op: "local.set", index: iTmp });
2082
+ this.pushInstruction({ op: "br", depth: 0 });
2083
+ this.pushInstruction("end");
2084
+ this.pushInstruction("end");
2085
+
2086
+ this.pushInstruction({
2087
+ op: "call",
2088
+ funcIndex: this.mod.getBytesToStrFinishImportIndex(),
2089
+ });
2090
+
2091
+ this.releaseTemp(iTmp, "i32");
2092
+ this.releaseTemp(lenTmp, "i32");
2093
+ this.releaseTemp(offTmp, "i32");
2094
+ this.releaseTemp(arrTmp, arrW);
2095
+ }
2096
+ }