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,2466 @@
1
+ // src/backend/wasm/emit-stmts.js
2
+ // FunctionEmitter mixin: statement emission (decls, assignment, control flow, return).
3
+
4
+ import { isIntRangeType, isRangeFor } from "../../lower/range.js";
5
+ import {
6
+ getMapKeyValTypes,
7
+ isArrayType,
8
+ isFuncType,
9
+ isInterfaceType,
10
+ isMapType,
11
+ isNonEmptyInterface,
12
+ isPointerToStruct,
13
+ isSliceType,
14
+ isStringType,
15
+ isStructType,
16
+ } from "./types.js";
17
+
18
+ export class StmtsEmitter {
19
+ emitVarDecl(stmt) {
20
+ const decls = stmt.decls ?? stmt.specs ?? [stmt];
21
+ for (const spec of decls) {
22
+ const names = spec.names ?? (spec.name ? [spec.name] : []);
23
+ const values = spec.value ?? (spec.init ? [spec.init] : []);
24
+
25
+ for (let i = 0; i < names.length; i++) {
26
+ const name = names[i];
27
+ const rawType = spec.type ?? values[i]?._type;
28
+ const wType = this.toWasmType(rawType);
29
+ const localIdx = this.allocLocal(name, wType, rawType);
30
+ const localInfo = this.locals.get(name);
31
+ if (values?.[i]) {
32
+ const r = values[i];
33
+ this.emitExpr(r, wType);
34
+ this._emitCopyIfValueStruct(r, rawType, wType);
35
+ if (localInfo?.isBoxed) {
36
+ this.pushInstruction({
37
+ op: "struct.new",
38
+ typeIndex: localInfo.boxInfo.typeIndex,
39
+ });
40
+ }
41
+ this.pushInstruction({ op: "local.set", index: localIdx });
42
+ } else {
43
+ this.emitZeroValue(rawType, wType);
44
+ if (localInfo?.isBoxed) {
45
+ this.pushInstruction({
46
+ op: "struct.new",
47
+ typeIndex: localInfo.boxInfo.typeIndex,
48
+ });
49
+ }
50
+ this.pushInstruction({ op: "local.set", index: localIdx });
51
+ }
52
+ }
53
+ }
54
+ }
55
+
56
+ emitDefineStmt(stmt) {
57
+ const lhsNodes = Array.isArray(stmt.lhs) ? stmt.lhs : [stmt.lhs];
58
+ const rhsNodes = Array.isArray(stmt.rhs) ? stmt.rhs : [stmt.rhs];
59
+
60
+ if (
61
+ rhsNodes.length === 1 &&
62
+ lhsNodes.length === 2 &&
63
+ rhsNodes[0].kind === "TypeAssertExpr"
64
+ ) {
65
+ this._emitCommaOkTypeAssert(lhsNodes[0], lhsNodes[1], rhsNodes[0], true);
66
+ return;
67
+ }
68
+
69
+ if (
70
+ rhsNodes.length === 1 &&
71
+ lhsNodes.length === 2 &&
72
+ rhsNodes[0].kind === "IndexExpr" &&
73
+ isMapType(
74
+ rhsNodes[0].expr._type ?? this._resolveExprGoType(rhsNodes[0].expr),
75
+ this.mod.checker,
76
+ )
77
+ ) {
78
+ this._emitCommaOkMapIndex(lhsNodes[0], lhsNodes[1], rhsNodes[0], true);
79
+ return;
80
+ }
81
+
82
+ if (
83
+ rhsNodes.length === 1 &&
84
+ lhsNodes.length > 1 &&
85
+ rhsNodes[0]._type?.kind === "tuple"
86
+ ) {
87
+ this._emitTupleDefine(lhsNodes, rhsNodes[0]);
88
+ return;
89
+ }
90
+
91
+ for (let i = 0; i < lhsNodes.length; i++) {
92
+ this._emitDefineSingle(lhsNodes[i], rhsNodes[i]);
93
+ }
94
+ }
95
+
96
+ _emitDefineSingle(l, r) {
97
+ if (!r || l.kind !== "Ident") return;
98
+ if (l.name === "_") {
99
+ this.emitExpr(r);
100
+ this.pushInstruction("drop");
101
+ return;
102
+ }
103
+ const wType = this.toWasmType(r._type);
104
+ const idx = this.allocLocal(l.name, wType, r._type);
105
+ const localInfo = this.locals.get(l.name);
106
+ this.emitExpr(r, wType);
107
+ this._emitCopyIfValueStruct(r, r._type, wType);
108
+ if (localInfo?.isBoxed) {
109
+ this.pushInstruction({
110
+ op: "struct.new",
111
+ typeIndex: localInfo.boxInfo.typeIndex,
112
+ });
113
+ }
114
+ this.pushInstruction({ op: "local.set", index: idx });
115
+ }
116
+
117
+ _emitTupleDefine(lhsNodes, rhsNode) {
118
+ const tupleTypes = rhsNode._type.types;
119
+ const localIndices = [];
120
+ for (let i = 0; i < lhsNodes.length; i++) {
121
+ const l = lhsNodes[i];
122
+ const wType = this.toWasmType(tupleTypes[i]);
123
+ const idx =
124
+ l.kind === "Ident" && l.name !== "_"
125
+ ? this.allocLocal(l.name, wType, tupleTypes[i])
126
+ : null;
127
+ localIndices.push(idx);
128
+ }
129
+
130
+ this.emitExpr(rhsNode);
131
+
132
+ for (let i = lhsNodes.length - 1; i >= 0; i--) {
133
+ const idx = localIndices[i];
134
+ if (idx !== null) {
135
+ const localInfo = this.locals.get(lhsNodes[i].name);
136
+ if (localInfo?.isBoxed) {
137
+ this.pushInstruction({
138
+ op: "struct.new",
139
+ typeIndex: localInfo.boxInfo.typeIndex,
140
+ });
141
+ }
142
+ this.pushInstruction({ op: "local.set", index: idx });
143
+ } else {
144
+ this.pushInstruction("drop");
145
+ }
146
+ }
147
+ }
148
+
149
+ emitAssignStmt(stmt) {
150
+ const { lhs, rhs, op } = stmt;
151
+ if (op === ":=" || op === "=") {
152
+ this._emitAssign(lhs, rhs, op);
153
+ } else {
154
+ this._emitCompoundAssign(lhs, rhs, op);
155
+ }
156
+ }
157
+
158
+ _emitAssign(lhs, rhs, op) {
159
+ const lhsNodes = Array.isArray(lhs) ? lhs : [lhs];
160
+ const rhsNodes = Array.isArray(rhs) ? rhs : [rhs];
161
+
162
+ if (
163
+ rhsNodes.length === 1 &&
164
+ lhsNodes.length === 2 &&
165
+ rhsNodes[0].kind === "TypeAssertExpr"
166
+ ) {
167
+ this._emitCommaOkTypeAssert(
168
+ lhsNodes[0],
169
+ lhsNodes[1],
170
+ rhsNodes[0],
171
+ op === ":=",
172
+ );
173
+ return;
174
+ }
175
+
176
+ if (
177
+ rhsNodes.length === 1 &&
178
+ lhsNodes.length === 2 &&
179
+ rhsNodes[0].kind === "IndexExpr" &&
180
+ isMapType(
181
+ rhsNodes[0].expr._type ?? this._resolveExprGoType(rhsNodes[0].expr),
182
+ this.mod.checker,
183
+ )
184
+ ) {
185
+ this._emitCommaOkMapIndex(
186
+ lhsNodes[0],
187
+ lhsNodes[1],
188
+ rhsNodes[0],
189
+ op === ":=",
190
+ );
191
+ return;
192
+ }
193
+
194
+ if (
195
+ rhsNodes.length === 1 &&
196
+ lhsNodes.length > 1 &&
197
+ rhsNodes[0]._type?.kind === "tuple"
198
+ ) {
199
+ this._emitTupleAssign(lhsNodes, rhsNodes[0], op);
200
+ return;
201
+ }
202
+
203
+ for (let i = 0; i < lhsNodes.length; i++) {
204
+ this._emitAssignSingle(lhsNodes[i], rhsNodes[i], op);
205
+ }
206
+ }
207
+
208
+ _emitAssignToTarget(target, wasmType, goType, emitValue) {
209
+ const tempName = `__assign_tmp_${this.tempCounter++}`;
210
+ const effectiveGoType = goType ??
211
+ target._type ?? {
212
+ kind: "basic",
213
+ name: wasmType === "i64" ? "int64" : "int",
214
+ };
215
+ const effectiveWasmType = wasmType ?? this.toWasmType(effectiveGoType);
216
+ const tempIdx = this.allocLocal(
217
+ tempName,
218
+ effectiveWasmType,
219
+ effectiveGoType,
220
+ true,
221
+ );
222
+ emitValue();
223
+ this.pushInstruction({ op: "local.set", index: tempIdx });
224
+ const rNode = {
225
+ kind: "Ident",
226
+ name: tempName,
227
+ _type: effectiveGoType,
228
+ };
229
+ this._emitAssignSingle(target, rNode, "=");
230
+ this.locals.delete(tempName);
231
+ }
232
+
233
+ _emitAssignSingle(l, r, op) {
234
+ if (!r) return;
235
+ if (l.kind === "Ident") {
236
+ if (l.name === "_") {
237
+ this.emitExpr(r);
238
+ this.pushInstruction("drop");
239
+ return;
240
+ }
241
+ let localInfo = this.resolveLocal(l.name);
242
+ if (!localInfo && op === ":=") {
243
+ const wType = this.toWasmType(r._type);
244
+ this.allocLocal(l.name, wType, r._type);
245
+ localInfo = this.locals.get(l.name);
246
+ }
247
+ if (localInfo) {
248
+ if (localInfo.isBoxed) {
249
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
250
+ this.emitExpr(r, localInfo.boxInfo.wType);
251
+ this.pushInstruction({
252
+ op: "struct.set",
253
+ typeIndex: localInfo.boxInfo.typeIndex,
254
+ fieldIndex: 0,
255
+ });
256
+ return;
257
+ }
258
+ this.emitExpr(r, localInfo.type);
259
+ this._emitCopyIfValueStruct(r, localInfo.goType, localInfo.type);
260
+ this.pushInstruction({ op: "local.set", index: localInfo.index });
261
+ } else {
262
+ const globalInfo = this.mod.resolveGlobal(l.name);
263
+ if (globalInfo) {
264
+ this.emitExpr(r, globalInfo.type);
265
+ this._emitCopyIfValueStruct(r, globalInfo.goType, globalInfo.type);
266
+ this.pushInstruction({
267
+ op: "global.set",
268
+ index: globalInfo.index,
269
+ });
270
+ }
271
+ }
272
+ return;
273
+ }
274
+
275
+ if (l.kind === "SelectorExpr") {
276
+ const structInfo = this._resolveStructInfo(l.expr);
277
+ if (!structInfo) {
278
+ throw new Error(
279
+ `Cannot resolve struct for selector assignment: ${l.field}`,
280
+ );
281
+ }
282
+ const path = this._resolveFieldPath(structInfo, l.field);
283
+ if (!path) {
284
+ throw new Error(
285
+ `Unknown field '${l.field}' on struct '${structInfo.name}'`,
286
+ );
287
+ }
288
+ const lastStep = path[path.length - 1];
289
+ const exprGoType =
290
+ l.expr._type ??
291
+ this.resolveLocal(l.expr.name)?.goType ??
292
+ this._resolveExprGoType(l.expr);
293
+ const baseWType = this.toWasmType(exprGoType);
294
+
295
+ this.emitExpr(l.expr, baseWType);
296
+ for (let i = 0; i < path.length - 1; i++) {
297
+ const step = path[i];
298
+ this.pushInstruction({
299
+ op: "struct.get",
300
+ typeIndex: step.structInfo.typeIndex,
301
+ fieldIndex: step.fieldIndex,
302
+ });
303
+ }
304
+ this.emitExpr(r, lastStep.field.wType);
305
+ this._emitCopyIfValueStruct(
306
+ r,
307
+ lastStep.field.goType,
308
+ lastStep.field.wType,
309
+ );
310
+ this.pushInstruction({
311
+ op: "struct.set",
312
+ typeIndex: lastStep.structInfo.typeIndex,
313
+ fieldIndex: lastStep.fieldIndex,
314
+ });
315
+ return;
316
+ }
317
+
318
+ if (l.kind === "UnaryExpr" && l.op === "*") {
319
+ const structInfo = this._resolveStructInfo(l.operand);
320
+ if (structInfo) {
321
+ const baseWType = this.toWasmType(l.operand._type);
322
+ const tmpL = this.acquireTemp(baseWType);
323
+ const tmpR = this.acquireTemp(baseWType);
324
+ this.emitExpr(l.operand, baseWType);
325
+ this.pushInstruction({ op: "local.set", index: tmpL });
326
+ this.emitExpr(r, baseWType);
327
+ this.pushInstruction({ op: "local.set", index: tmpR });
328
+ for (let i = 0; i < structInfo.fields.length; i++) {
329
+ this.pushInstruction({ op: "local.get", index: tmpL });
330
+ this.pushInstruction({ op: "local.get", index: tmpR });
331
+ this.pushInstruction({
332
+ op: "struct.get",
333
+ typeIndex: structInfo.typeIndex,
334
+ fieldIndex: i,
335
+ });
336
+ this.pushInstruction({
337
+ op: "struct.set",
338
+ typeIndex: structInfo.typeIndex,
339
+ fieldIndex: i,
340
+ });
341
+ }
342
+ this.releaseTemp(tmpR, baseWType);
343
+ this.releaseTemp(tmpL, baseWType);
344
+ return;
345
+ }
346
+ const operandGoType =
347
+ l.operand._type ??
348
+ (l.operand.kind === "Ident"
349
+ ? this.resolveLocal(l.operand.name)?.goType
350
+ : null) ??
351
+ this._resolveExprGoType(l.operand);
352
+ const box = this.mod.getBoxType(operandGoType?.base ?? operandGoType);
353
+ const baseWType = {
354
+ kind: "ref",
355
+ nullable: true,
356
+ typeIndex: box.typeIndex,
357
+ };
358
+ const boxTmp = this.acquireTemp(baseWType);
359
+ this.emitExpr(l.operand, baseWType);
360
+ this.pushInstruction({ op: "local.set", index: boxTmp });
361
+ this.pushInstruction({ op: "local.get", index: boxTmp });
362
+ this.emitExpr(r, box.wType);
363
+ this.pushInstruction({
364
+ op: "struct.set",
365
+ typeIndex: box.typeIndex,
366
+ fieldIndex: 0,
367
+ });
368
+ this.releaseTemp(boxTmp, baseWType);
369
+ return;
370
+ }
371
+
372
+ if (l.kind === "IndexExpr") {
373
+ const baseNode = l.expr;
374
+ const baseType = baseNode._type ?? this._resolveExprGoType(baseNode);
375
+ if (isMapType(baseType, this.mod.checker)) {
376
+ const { keyType, valType } = getMapKeyValTypes(
377
+ baseType,
378
+ this.mod.checker,
379
+ );
380
+ const mapInfo = this.mod.getMapType(keyType, valType);
381
+ this.emitExpr(baseNode, mapInfo.wType);
382
+ this.emitExpr(l.index, mapInfo.keyWType);
383
+ this.emitExpr(r, mapInfo.valWType);
384
+ this._emitCopyIfValueStruct(r, valType, mapInfo.valWType);
385
+ this.pushInstruction({
386
+ op: "call",
387
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
388
+ });
389
+ return;
390
+ }
391
+
392
+ const sharedInfo = this._sharedInfo(baseType);
393
+ if (sharedInfo) {
394
+ this.emitSharedIndexAssign(l, r, sharedInfo);
395
+ return;
396
+ }
397
+
398
+ const isSlice = isSliceType(baseType, this.mod.checker);
399
+ const isPtrToArr =
400
+ baseType?.kind === "pointer" &&
401
+ isArrayType(baseType.base, this.mod.checker);
402
+
403
+ const elemGoType = isSlice
404
+ ? baseType.elem
405
+ : isPtrToArr
406
+ ? baseType.base.elem
407
+ : baseType.elem;
408
+ const arrInfo = this.mod.getArrayType(elemGoType);
409
+ const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
410
+
411
+ this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
412
+
413
+ this.emitExpr(r, arrInfo.elemWType);
414
+ this._emitCopyIfValueStruct(r, elemGoType, arrInfo.elemWType);
415
+
416
+ this.pushInstruction({
417
+ op: "array.set",
418
+ typeIndex: arrInfo.typeIndex,
419
+ });
420
+ return;
421
+ }
422
+ }
423
+
424
+ _emitTupleAssign(lhsNodes, rhsNode, op) {
425
+ const tupleTypes = rhsNode._type.types;
426
+ const dests = [];
427
+ for (let i = 0; i < lhsNodes.length; i++) {
428
+ const l = lhsNodes[i];
429
+ if (l.kind === "Ident" && l.name !== "_") {
430
+ let localInfo = this.resolveLocal(l.name);
431
+ if (!localInfo && op === ":=") {
432
+ const wType = this.toWasmType(tupleTypes[i]);
433
+ this.allocLocal(l.name, wType, tupleTypes[i]);
434
+ localInfo = this.locals.get(l.name);
435
+ }
436
+ dests.push(localInfo ?? this.mod.resolveGlobal(l.name) ?? null);
437
+ } else {
438
+ dests.push(null);
439
+ }
440
+ }
441
+
442
+ this.emitExpr(rhsNode);
443
+
444
+ for (let i = lhsNodes.length - 1; i >= 0; i--) {
445
+ const dest = dests[i];
446
+ if (dest && typeof dest.index === "number") {
447
+ if (dest.isBoxed) {
448
+ const valTmp = this.acquireTemp(dest.boxInfo.wType);
449
+ this.pushInstruction({ op: "local.set", index: valTmp });
450
+ this.pushInstruction({ op: "local.get", index: dest.index });
451
+ this.pushInstruction({ op: "local.get", index: valTmp });
452
+ this.pushInstruction({
453
+ op: "struct.set",
454
+ typeIndex: dest.boxInfo.typeIndex,
455
+ fieldIndex: 0,
456
+ });
457
+ this.releaseTemp(valTmp, dest.boxInfo.wType);
458
+ } else if (this.resolveLocal(lhsNodes[i].name)) {
459
+ this.pushInstruction({ op: "local.set", index: dest.index });
460
+ } else {
461
+ this.pushInstruction({ op: "global.set", index: dest.index });
462
+ }
463
+ } else {
464
+ this.pushInstruction("drop");
465
+ }
466
+ }
467
+ }
468
+
469
+ _emitCommaOkTypeAssert(valLhs, okLhs, assertExpr, isDefine) {
470
+ const targetGoType = assertExpr._type ?? assertExpr.type;
471
+ const targetWType = this.toWasmType(targetGoType);
472
+ const anyTmp = this.acquireTemp("anyref");
473
+ this.emitExpr(assertExpr.expr, "anyref");
474
+ this.pushInstruction({ op: "local.set", index: anyTmp });
475
+
476
+ const okTmp = this.acquireTemp("i32");
477
+ this.pushInstruction({ op: "local.get", index: anyTmp });
478
+ this._emitTypeTest(targetGoType);
479
+ this.pushInstruction({ op: "local.set", index: okTmp });
480
+
481
+ const valTmp = this.acquireTemp(targetWType);
482
+ this.pushInstruction({ op: "local.get", index: okTmp });
483
+ this.pushInstruction({ op: "if", blockType: targetWType });
484
+ this.pushInstruction({ op: "local.get", index: anyTmp });
485
+ this._emitTypeCast(targetGoType, targetWType);
486
+ this.pushInstruction("else");
487
+ this.emitZeroValue(targetGoType, targetWType);
488
+ this.pushInstruction("end");
489
+ this.pushInstruction({ op: "local.set", index: valTmp });
490
+
491
+ this.releaseTemp(anyTmp, "anyref");
492
+
493
+ this._emitStoreLhsFromTemp(
494
+ valLhs,
495
+ valTmp,
496
+ targetWType,
497
+ targetGoType,
498
+ isDefine,
499
+ );
500
+ this._emitStoreLhsFromTemp(
501
+ okLhs,
502
+ okTmp,
503
+ "i32",
504
+ { kind: "basic", name: "bool" },
505
+ isDefine,
506
+ );
507
+
508
+ this.releaseTemp(valTmp, targetWType);
509
+ this.releaseTemp(okTmp, "i32");
510
+ }
511
+
512
+ _emitStoreLhsFromTemp(lhsNode, tempIdx, wType, goType, isDefine) {
513
+ if (lhsNode.kind !== "Ident" || lhsNode.name === "_") return;
514
+ if (isDefine) {
515
+ const idx = this.allocLocal(lhsNode.name, wType, goType);
516
+ const local = this.locals.get(lhsNode.name);
517
+ this.pushInstruction({ op: "local.get", index: tempIdx });
518
+ if (local?.isBoxed) {
519
+ this.pushInstruction({
520
+ op: "struct.new",
521
+ typeIndex: local.boxInfo.typeIndex,
522
+ });
523
+ }
524
+ this.pushInstruction({ op: "local.set", index: idx });
525
+ return;
526
+ }
527
+ const local = this.resolveLocal(lhsNode.name);
528
+ if (local) {
529
+ if (local.isBoxed) {
530
+ this.pushInstruction({ op: "local.get", index: local.index });
531
+ this.pushInstruction({ op: "local.get", index: tempIdx });
532
+ this.pushInstruction({
533
+ op: "struct.set",
534
+ typeIndex: local.boxInfo.typeIndex,
535
+ fieldIndex: 0,
536
+ });
537
+ } else {
538
+ this.pushInstruction({ op: "local.get", index: tempIdx });
539
+ this.pushInstruction({ op: "local.set", index: local.index });
540
+ }
541
+ } else {
542
+ const global = this.mod.resolveGlobal(lhsNode.name);
543
+ this.pushInstruction({ op: "local.get", index: tempIdx });
544
+ this.pushInstruction({ op: "global.set", index: global.index });
545
+ }
546
+ }
547
+
548
+ _emitCommaOkMapIndex(valLhs, okLhs, indexExpr, isDefine) {
549
+ const baseNode = indexExpr.expr;
550
+ const baseType = baseNode._type ?? this._resolveExprGoType(baseNode);
551
+ const { keyType, valType } = getMapKeyValTypes(baseType, this.mod.checker);
552
+ const mapInfo = this.mod.getMapType(keyType, valType);
553
+
554
+ this.emitExpr(baseNode, mapInfo.wType);
555
+ this.emitExpr(indexExpr.index, mapInfo.keyWType);
556
+ this.pushInstruction({
557
+ op: "call",
558
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.getOkFuncName),
559
+ });
560
+
561
+ const okTmp = this.acquireTemp("i32");
562
+ const valTmp = this.acquireTemp(mapInfo.valWType);
563
+ this.pushInstruction({ op: "local.set", index: okTmp });
564
+ this.pushInstruction({ op: "local.set", index: valTmp });
565
+
566
+ this._emitStoreLhsFromTemp(
567
+ valLhs,
568
+ valTmp,
569
+ mapInfo.valWType,
570
+ valType,
571
+ isDefine,
572
+ );
573
+ this._emitStoreLhsFromTemp(
574
+ okLhs,
575
+ okTmp,
576
+ "i32",
577
+ { kind: "basic", name: "bool" },
578
+ isDefine,
579
+ );
580
+
581
+ this.releaseTemp(valTmp, mapInfo.valWType);
582
+ this.releaseTemp(okTmp, "i32");
583
+ }
584
+
585
+ _structNameOf(goType) {
586
+ if (!goType) return null;
587
+ if (goType.kind === "PointerType" || goType.kind === "pointer")
588
+ return this._structNameOf(goType.base);
589
+ if (goType.kind === "StarExpr")
590
+ return this._structNameOf(goType.expr ?? goType.operand);
591
+ return goType.name ?? null;
592
+ }
593
+
594
+ _emitTypeTest(targetGoType) {
595
+ if (!targetGoType) {
596
+ this.pushInstruction("drop");
597
+ this.pushInstruction({ op: "i32.const", value: 1 });
598
+ return;
599
+ }
600
+
601
+ if (targetGoType.name === "nil" || targetGoType.kind === "nil") {
602
+ this.pushInstruction("ref.is_null");
603
+ return;
604
+ }
605
+
606
+ if (targetGoType.name === "any" || targetGoType.name === "interface{}") {
607
+ this.pushInstruction("ref.is_null");
608
+ this.pushInstruction("i32.eqz");
609
+ return;
610
+ }
611
+
612
+ if (isNonEmptyInterface(targetGoType, this.mod.checker)) {
613
+ const candidates = this.mod.getTypesImplementingInterface(targetGoType);
614
+ if (candidates.length === 0) {
615
+ this.pushInstruction("drop");
616
+ this.pushInstruction({ op: "i32.const", value: 0 });
617
+ return;
618
+ }
619
+ const anyTmp = this.acquireTemp("anyref");
620
+ this.pushInstruction({ op: "local.set", index: anyTmp });
621
+
622
+ this.pushInstruction({ op: "local.get", index: anyTmp });
623
+ this.pushInstruction("ref.is_null");
624
+ this.pushInstruction("i32.eqz");
625
+
626
+ for (let i = 0; i < candidates.length; i++) {
627
+ this.pushInstruction({ op: "local.get", index: anyTmp });
628
+ this.pushInstruction({
629
+ op: "ref.test",
630
+ typeIndex: candidates[i].typeIndex,
631
+ });
632
+ if (i > 0) {
633
+ this.pushInstruction("i32.or");
634
+ }
635
+ }
636
+ this.pushInstruction("i32.and");
637
+ this.releaseTemp(anyTmp, "anyref");
638
+ return;
639
+ }
640
+
641
+ if (isStringType(targetGoType, this.mod.checker)) {
642
+ const isStrIdx = this.mod.getIsStringImportIndex();
643
+ this.pushInstruction({ op: "call", funcIndex: isStrIdx });
644
+ return;
645
+ }
646
+
647
+ if (isFuncType(targetGoType, this.mod.checker)) {
648
+ const closureInfo = this.mod.getClosureType(targetGoType);
649
+ this.pushInstruction({
650
+ op: "ref.test",
651
+ typeIndex: closureInfo.typeIndex,
652
+ });
653
+ return;
654
+ }
655
+
656
+ // `*T` lives in `any` as the bare struct ref; a `T` value as a boxed
657
+ // clone (see emitExpr). Fall through to the scalar box for values.
658
+ if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
659
+ const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
660
+ if (sInfo) {
661
+ this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
662
+ return;
663
+ }
664
+ }
665
+
666
+ if (isSliceType(targetGoType, this.mod.checker)) {
667
+ const elem = this._getSliceElemType(targetGoType);
668
+ const sInfo = this.mod.getSliceType(elem);
669
+ if (sInfo) {
670
+ this.pushInstruction({ op: "ref.test", typeIndex: sInfo.typeIndex });
671
+ return;
672
+ }
673
+ }
674
+
675
+ if (isArrayType(targetGoType, this.mod.checker)) {
676
+ const elem = this._getArrayElemType(targetGoType);
677
+ const aInfo = this.mod.getArrayType(elem);
678
+ if (aInfo) {
679
+ this.pushInstruction({ op: "ref.test", typeIndex: aInfo.typeIndex });
680
+ return;
681
+ }
682
+ }
683
+
684
+ // Scalar types
685
+ const box = this.mod.getIfaceBoxType(targetGoType);
686
+ if (box) {
687
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
688
+ return;
689
+ }
690
+
691
+ this.pushInstruction("drop");
692
+ this.pushInstruction({ op: "i32.const", value: 1 });
693
+ }
694
+
695
+ _emitTypeCast(targetGoType, targetWType) {
696
+ if (isInterfaceType(targetGoType, this.mod.checker)) {
697
+ return;
698
+ }
699
+
700
+ if (isStringType(targetGoType, this.mod.checker)) {
701
+ this.pushInstruction("extern.convert_any");
702
+ return;
703
+ }
704
+
705
+ if (isFuncType(targetGoType, this.mod.checker)) {
706
+ const closureInfo = this.mod.getClosureType(targetGoType);
707
+ this.pushInstruction({
708
+ op: "ref.cast_null",
709
+ typeIndex: closureInfo.typeIndex,
710
+ });
711
+ return;
712
+ }
713
+
714
+ if (isPointerToStruct(targetGoType, this.mod.checker, this.mod)) {
715
+ const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
716
+ if (sInfo) {
717
+ this.pushInstruction({
718
+ op: "ref.cast_null",
719
+ typeIndex: sInfo.typeIndex,
720
+ });
721
+ return;
722
+ }
723
+ }
724
+ if (isStructType(targetGoType, this.mod.checker, this.mod)) {
725
+ const sInfo = this.mod.getStructType(this._structNameOf(targetGoType));
726
+ const box = this.mod.getIfaceBoxType(targetGoType);
727
+ if (sInfo && box) {
728
+ this._emitUnbox(box);
729
+ this.emitCloneStruct(sInfo, targetWType);
730
+ return;
731
+ }
732
+ }
733
+
734
+ if (isSliceType(targetGoType, this.mod.checker)) {
735
+ const elem = this._getSliceElemType(targetGoType);
736
+ const sInfo = this.mod.getSliceType(elem);
737
+ if (sInfo) {
738
+ this.pushInstruction({
739
+ op: "ref.cast_null",
740
+ typeIndex: sInfo.typeIndex,
741
+ });
742
+ return;
743
+ }
744
+ }
745
+
746
+ if (isArrayType(targetGoType, this.mod.checker)) {
747
+ const elem = this._getArrayElemType(targetGoType);
748
+ const aInfo = this.mod.getArrayType(elem);
749
+ if (aInfo) {
750
+ this.pushInstruction({
751
+ op: "ref.cast_null",
752
+ typeIndex: aInfo.typeIndex,
753
+ });
754
+ return;
755
+ }
756
+ }
757
+
758
+ // Scalar types
759
+ const box = this.mod.getIfaceBoxType(targetGoType);
760
+ if (box) {
761
+ this._emitUnbox(box);
762
+ return;
763
+ }
764
+ }
765
+
766
+ _emitUnbox(box) {
767
+ this.pushInstruction({
768
+ op: "ref.cast_null",
769
+ typeIndex: box.typeIndex,
770
+ });
771
+ this.pushInstruction({
772
+ op: "struct.get",
773
+ typeIndex: box.typeIndex,
774
+ fieldIndex: 0,
775
+ });
776
+ }
777
+
778
+ _emitCompoundAssign(lhs, rhs, op) {
779
+ const baseOp = op.slice(0, -1);
780
+ const l = Array.isArray(lhs) ? lhs[0] : lhs;
781
+ const r = Array.isArray(rhs) ? rhs[0] : rhs;
782
+
783
+ if (l.kind === "SelectorExpr") {
784
+ const structInfo = this._resolveStructInfo(l.expr);
785
+ if (!structInfo) {
786
+ throw new Error(
787
+ `Cannot resolve struct for compound selector: ${l.field}`,
788
+ );
789
+ }
790
+ const path = this._resolveFieldPath(structInfo, l.field);
791
+ if (!path) {
792
+ throw new Error(
793
+ `Unknown field '${l.field}' on struct '${structInfo.name}'`,
794
+ );
795
+ }
796
+ const lastStep = path[path.length - 1];
797
+ const targetType = lastStep.field.wType;
798
+ const baseWType = this.toWasmType(l.expr._type);
799
+
800
+ const baseTmp = this.acquireTemp(baseWType);
801
+ this.emitExpr(l.expr, baseWType);
802
+ for (let i = 0; i < path.length - 1; i++) {
803
+ const step = path[i];
804
+ this.pushInstruction({
805
+ op: "struct.get",
806
+ typeIndex: step.structInfo.typeIndex,
807
+ fieldIndex: step.fieldIndex,
808
+ });
809
+ }
810
+ this.pushInstruction({ op: "local.set", index: baseTmp });
811
+
812
+ this.pushInstruction({ op: "local.get", index: baseTmp });
813
+
814
+ this.pushInstruction({ op: "local.get", index: baseTmp });
815
+ this.pushInstruction({
816
+ op: "struct.get",
817
+ typeIndex: lastStep.structInfo.typeIndex,
818
+ fieldIndex: lastStep.fieldIndex,
819
+ });
820
+ this.emitExpr(r, targetType);
821
+ this.emitBinaryOp(baseOp, targetType, l._type);
822
+
823
+ this.pushInstruction({
824
+ op: "struct.set",
825
+ typeIndex: lastStep.structInfo.typeIndex,
826
+ fieldIndex: lastStep.fieldIndex,
827
+ });
828
+ this.releaseTemp(baseTmp, baseWType);
829
+ return;
830
+ }
831
+
832
+ if (l.kind === "IndexExpr") {
833
+ const baseNode = l.expr;
834
+ const baseType = baseNode._type ?? this._resolveExprGoType(baseNode);
835
+ if (isMapType(baseType, this.mod.checker)) {
836
+ const { keyType, valType } = getMapKeyValTypes(
837
+ baseType,
838
+ this.mod.checker,
839
+ );
840
+ const mapInfo = this.mod.getMapType(keyType, valType);
841
+ const mTmp = this.acquireTemp(mapInfo.wType);
842
+ const kTmp = this.acquireTemp(mapInfo.keyWType);
843
+ this.emitExpr(baseNode, mapInfo.wType);
844
+ this.pushInstruction({ op: "local.set", index: mTmp });
845
+ this.emitExpr(l.index, mapInfo.keyWType);
846
+ this.pushInstruction({ op: "local.set", index: kTmp });
847
+
848
+ this.pushInstruction({ op: "local.get", index: mTmp });
849
+ this.pushInstruction({ op: "local.get", index: kTmp });
850
+
851
+ this.pushInstruction({ op: "local.get", index: mTmp });
852
+ this.pushInstruction({ op: "local.get", index: kTmp });
853
+ this.pushInstruction({
854
+ op: "call",
855
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.getFuncName),
856
+ });
857
+
858
+ this.emitExpr(r, mapInfo.valWType);
859
+ this.emitBinaryOp(baseOp, mapInfo.valWType, valType);
860
+
861
+ this.pushInstruction({
862
+ op: "call",
863
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
864
+ });
865
+
866
+ this.releaseTemp(kTmp, mapInfo.keyWType);
867
+ this.releaseTemp(mTmp, mapInfo.wType);
868
+ return;
869
+ }
870
+
871
+ const sharedInfo = this._sharedInfo(baseType);
872
+ if (sharedInfo) {
873
+ this.emitSharedIndexUpdate(l, sharedInfo, (elemW, elemGoType) => {
874
+ this.emitExpr(r, elemW);
875
+ this.emitBinaryOp(baseOp, elemW, elemGoType);
876
+ });
877
+ return;
878
+ }
879
+
880
+ const isSlice = isSliceType(baseType, this.mod.checker);
881
+ const isPtrToArr =
882
+ baseType?.kind === "pointer" &&
883
+ isArrayType(baseType.base, this.mod.checker);
884
+
885
+ const elemGoType = isSlice
886
+ ? baseType.elem
887
+ : isPtrToArr
888
+ ? baseType.base.elem
889
+ : baseType.elem;
890
+ const arrInfo = this.mod.getArrayType(elemGoType);
891
+ const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
892
+ const targetType = arrInfo.elemWType;
893
+ const arrWType = {
894
+ kind: "ref",
895
+ nullable: true,
896
+ typeIndex: arrInfo.typeIndex,
897
+ };
898
+
899
+ this._emitElemAddr(baseNode, l.index, sliceInfo, arrInfo);
900
+ const arrTmp = this.acquireTemp(arrWType);
901
+ const targetIdxTmp = this.acquireTemp("i32");
902
+ this.pushInstruction({ op: "local.set", index: targetIdxTmp });
903
+ this.pushInstruction({ op: "local.tee", index: arrTmp });
904
+ this.pushInstruction({ op: "local.get", index: targetIdxTmp });
905
+
906
+ this.pushInstruction({ op: "local.get", index: arrTmp });
907
+ this.pushInstruction({ op: "local.get", index: targetIdxTmp });
908
+ this.pushInstruction({
909
+ op: "array.get",
910
+ typeIndex: arrInfo.typeIndex,
911
+ });
912
+
913
+ this.emitExpr(r, targetType);
914
+ this.emitBinaryOp(baseOp, targetType, elemGoType);
915
+
916
+ this.pushInstruction({
917
+ op: "array.set",
918
+ typeIndex: arrInfo.typeIndex,
919
+ });
920
+
921
+ this.releaseTemp(targetIdxTmp, "i32");
922
+ this.releaseTemp(arrTmp, arrWType);
923
+ return;
924
+ }
925
+
926
+ if ((l.kind === "UnaryExpr" && l.op === "*") || l.kind === "StarExpr") {
927
+ const operand = l.operand ?? l.expr;
928
+ const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
929
+ if (box) {
930
+ const baseWType = {
931
+ kind: "ref",
932
+ nullable: true,
933
+ typeIndex: box.typeIndex,
934
+ };
935
+ const boxTmp = this.acquireTemp(baseWType);
936
+ this.emitExpr(operand, baseWType);
937
+ this.pushInstruction({ op: "local.set", index: boxTmp });
938
+ this.pushInstruction({ op: "local.get", index: boxTmp });
939
+ this.pushInstruction({ op: "local.get", index: boxTmp });
940
+ this.pushInstruction({
941
+ op: "struct.get",
942
+ typeIndex: box.typeIndex,
943
+ fieldIndex: 0,
944
+ });
945
+ const targetType = box.wType;
946
+ this.emitExpr(r, targetType);
947
+ this.emitBinaryOp(baseOp, targetType, l._type ?? operand._type?.base);
948
+ this.pushInstruction({
949
+ op: "struct.set",
950
+ typeIndex: box.typeIndex,
951
+ fieldIndex: 0,
952
+ });
953
+ this.releaseTemp(boxTmp, baseWType);
954
+ return;
955
+ }
956
+ }
957
+
958
+ if (l.kind !== "Ident") return;
959
+
960
+ const localInfo = this.resolveLocal(l.name);
961
+ const globalInfo = !localInfo ? this.mod.resolveGlobal(l.name) : null;
962
+ if (!localInfo && !globalInfo) return;
963
+
964
+ if (localInfo?.isBoxed) {
965
+ const boxInfo = localInfo.boxInfo;
966
+ const targetType = boxInfo.wType;
967
+ if (baseOp === "/" || baseOp === "%") {
968
+ const resTmp = this.acquireTemp(targetType);
969
+ this.emitDivRem(baseOp, l, r, targetType);
970
+ this.pushInstruction({ op: "local.set", index: resTmp });
971
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
972
+ this.pushInstruction({ op: "local.get", index: resTmp });
973
+ this.pushInstruction({
974
+ op: "struct.set",
975
+ typeIndex: boxInfo.typeIndex,
976
+ fieldIndex: 0,
977
+ });
978
+ this.releaseTemp(resTmp, targetType);
979
+ } else if (baseOp === "<<" || baseOp === ">>") {
980
+ const resTmp = this.acquireTemp(targetType);
981
+ this.emitShift(baseOp, l, r, targetType);
982
+ this.pushInstruction({ op: "local.set", index: resTmp });
983
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
984
+ this.pushInstruction({ op: "local.get", index: resTmp });
985
+ this.pushInstruction({
986
+ op: "struct.set",
987
+ typeIndex: boxInfo.typeIndex,
988
+ fieldIndex: 0,
989
+ });
990
+ this.releaseTemp(resTmp, targetType);
991
+ } else {
992
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
993
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
994
+ this.pushInstruction({
995
+ op: "struct.get",
996
+ typeIndex: boxInfo.typeIndex,
997
+ fieldIndex: 0,
998
+ });
999
+ this.emitExpr(r, targetType);
1000
+ this.emitBinaryOp(baseOp, targetType, l._type);
1001
+ this.pushInstruction({
1002
+ op: "struct.set",
1003
+ typeIndex: boxInfo.typeIndex,
1004
+ fieldIndex: 0,
1005
+ });
1006
+ }
1007
+ return;
1008
+ }
1009
+
1010
+ const targetType = localInfo ? localInfo.type : globalInfo.type;
1011
+
1012
+ if (baseOp === "/" || baseOp === "%") {
1013
+ this.emitDivRem(baseOp, l, r, targetType);
1014
+ } else if (baseOp === "<<" || baseOp === ">>") {
1015
+ this.emitShift(baseOp, l, r, targetType);
1016
+ } else {
1017
+ if (localInfo) {
1018
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
1019
+ } else {
1020
+ this.pushInstruction({ op: "global.get", index: globalInfo.index });
1021
+ }
1022
+ this.emitExpr(r, targetType);
1023
+ this.emitBinaryOp(baseOp, targetType, l._type);
1024
+ }
1025
+
1026
+ if (localInfo) {
1027
+ this.pushInstruction({ op: "local.set", index: localInfo.index });
1028
+ } else {
1029
+ this.pushInstruction({ op: "global.set", index: globalInfo.index });
1030
+ }
1031
+ }
1032
+
1033
+ emitIncDecStmt(stmt) {
1034
+ const { expr, op } = stmt;
1035
+
1036
+ if (
1037
+ (expr.kind === "UnaryExpr" && expr.op === "*") ||
1038
+ expr.kind === "StarExpr"
1039
+ ) {
1040
+ const operand = expr.operand ?? expr.expr;
1041
+ const structInfo = this._resolveStructInfo(operand);
1042
+ if (!structInfo) {
1043
+ const box = this.mod.getBoxType(operand._type?.base ?? operand._type);
1044
+ if (box) {
1045
+ const baseWType = {
1046
+ kind: "ref",
1047
+ nullable: true,
1048
+ typeIndex: box.typeIndex,
1049
+ };
1050
+ const boxTmp = this.acquireTemp(baseWType);
1051
+ this.emitExpr(operand, baseWType);
1052
+ this.pushInstruction({ op: "local.set", index: boxTmp });
1053
+ this.pushInstruction({ op: "local.get", index: boxTmp });
1054
+ this.pushInstruction({ op: "local.get", index: boxTmp });
1055
+ this.pushInstruction({
1056
+ op: "struct.get",
1057
+ typeIndex: box.typeIndex,
1058
+ fieldIndex: 0,
1059
+ });
1060
+ this._emitIncDecStep(
1061
+ op,
1062
+ box.wType,
1063
+ operand._type?.base ?? operand._type,
1064
+ );
1065
+ this.pushInstruction({
1066
+ op: "struct.set",
1067
+ typeIndex: box.typeIndex,
1068
+ fieldIndex: 0,
1069
+ });
1070
+ this.releaseTemp(boxTmp, baseWType);
1071
+ return;
1072
+ }
1073
+ }
1074
+ }
1075
+
1076
+ if (expr.kind === "SelectorExpr") {
1077
+ const structInfo = this._resolveStructInfo(expr.expr);
1078
+ if (!structInfo) return;
1079
+ const path = this._resolveFieldPath(structInfo, expr.field);
1080
+ if (!path) return;
1081
+ const lastStep = path[path.length - 1];
1082
+ const targetType = lastStep.field.wType;
1083
+ const baseWType = this.toWasmType(expr.expr._type);
1084
+
1085
+ const baseTmp = this.acquireTemp(baseWType);
1086
+ this.emitExpr(expr.expr, baseWType);
1087
+ for (let i = 0; i < path.length - 1; i++) {
1088
+ const step = path[i];
1089
+ this.pushInstruction({
1090
+ op: "struct.get",
1091
+ typeIndex: step.structInfo.typeIndex,
1092
+ fieldIndex: step.fieldIndex,
1093
+ });
1094
+ }
1095
+ this.pushInstruction({ op: "local.set", index: baseTmp });
1096
+
1097
+ this.pushInstruction({ op: "local.get", index: baseTmp });
1098
+
1099
+ this.pushInstruction({ op: "local.get", index: baseTmp });
1100
+ this.pushInstruction({
1101
+ op: "struct.get",
1102
+ typeIndex: lastStep.structInfo.typeIndex,
1103
+ fieldIndex: lastStep.fieldIndex,
1104
+ });
1105
+
1106
+ this._emitIncDecStep(op, targetType, expr._type);
1107
+
1108
+ this.pushInstruction({
1109
+ op: "struct.set",
1110
+ typeIndex: lastStep.structInfo.typeIndex,
1111
+ fieldIndex: lastStep.fieldIndex,
1112
+ });
1113
+ this.releaseTemp(baseTmp, baseWType);
1114
+ return;
1115
+ }
1116
+
1117
+ if (expr.kind === "IndexExpr") {
1118
+ const baseNode = expr.expr;
1119
+ const baseType = baseNode._type ?? this._resolveExprGoType(baseNode);
1120
+ if (isMapType(baseType, this.mod.checker)) {
1121
+ const { keyType, valType } = getMapKeyValTypes(
1122
+ baseType,
1123
+ this.mod.checker,
1124
+ );
1125
+ const mapInfo = this.mod.getMapType(keyType, valType);
1126
+ const mTmp = this.acquireTemp(mapInfo.wType);
1127
+ const kTmp = this.acquireTemp(mapInfo.keyWType);
1128
+ this.emitExpr(baseNode, mapInfo.wType);
1129
+ this.pushInstruction({ op: "local.set", index: mTmp });
1130
+ this.emitExpr(expr.index, mapInfo.keyWType);
1131
+ this.pushInstruction({ op: "local.set", index: kTmp });
1132
+
1133
+ this.pushInstruction({ op: "local.get", index: mTmp });
1134
+ this.pushInstruction({ op: "local.get", index: kTmp });
1135
+
1136
+ this.pushInstruction({ op: "local.get", index: mTmp });
1137
+ this.pushInstruction({ op: "local.get", index: kTmp });
1138
+ this.pushInstruction({
1139
+ op: "call",
1140
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.getFuncName),
1141
+ });
1142
+
1143
+ this._emitIncDecStep(op, mapInfo.valWType, valType);
1144
+
1145
+ this.pushInstruction({
1146
+ op: "call",
1147
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
1148
+ });
1149
+
1150
+ this.releaseTemp(kTmp, mapInfo.keyWType);
1151
+ this.releaseTemp(mTmp, mapInfo.wType);
1152
+ return;
1153
+ }
1154
+
1155
+ const sharedInfo = this._sharedInfo(baseType);
1156
+ if (sharedInfo) {
1157
+ this.emitSharedIndexUpdate(expr, sharedInfo, (elemW) => {
1158
+ if (elemW === "f32" || elemW === "f64") {
1159
+ this.pushInstruction({ op: `${elemW}.const`, value: 1.0 });
1160
+ } else {
1161
+ this.pushInstruction({ op: "i32.const", value: 1 });
1162
+ }
1163
+ this.pushInstruction(`${elemW}.${op === "++" ? "add" : "sub"}`);
1164
+ });
1165
+ return;
1166
+ }
1167
+
1168
+ const isSlice = isSliceType(baseType, this.mod.checker);
1169
+ const isPtrToArr =
1170
+ baseType?.kind === "pointer" &&
1171
+ isArrayType(baseType.base, this.mod.checker);
1172
+
1173
+ const elemGoType = isSlice
1174
+ ? baseType.elem
1175
+ : isPtrToArr
1176
+ ? baseType.base.elem
1177
+ : baseType.elem;
1178
+ const arrInfo = this.mod.getArrayType(elemGoType);
1179
+ const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
1180
+ const targetType = arrInfo.elemWType;
1181
+ const arrWType = {
1182
+ kind: "ref",
1183
+ nullable: true,
1184
+ typeIndex: arrInfo.typeIndex,
1185
+ };
1186
+
1187
+ this._emitElemAddr(baseNode, expr.index, sliceInfo, arrInfo);
1188
+ const arrTmp = this.acquireTemp(arrWType);
1189
+ const targetIdxTmp = this.acquireTemp("i32");
1190
+ this.pushInstruction({ op: "local.set", index: targetIdxTmp });
1191
+ this.pushInstruction({ op: "local.tee", index: arrTmp });
1192
+ this.pushInstruction({ op: "local.get", index: targetIdxTmp });
1193
+
1194
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1195
+ this.pushInstruction({ op: "local.get", index: targetIdxTmp });
1196
+ this.pushInstruction({
1197
+ op: "array.get",
1198
+ typeIndex: arrInfo.typeIndex,
1199
+ });
1200
+
1201
+ this._emitIncDecStep(op, targetType, elemGoType);
1202
+
1203
+ this.pushInstruction({
1204
+ op: "array.set",
1205
+ typeIndex: arrInfo.typeIndex,
1206
+ });
1207
+
1208
+ this.releaseTemp(targetIdxTmp, "i32");
1209
+ this.releaseTemp(arrTmp, arrWType);
1210
+ return;
1211
+ }
1212
+
1213
+ if (expr.kind === "Ident") {
1214
+ const localInfo = this.resolveLocal(expr.name);
1215
+ const globalInfo = !localInfo ? this.mod.resolveGlobal(expr.name) : null;
1216
+ if (!localInfo && !globalInfo) return;
1217
+
1218
+ if (localInfo?.isBoxed) {
1219
+ const boxInfo = localInfo.boxInfo;
1220
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
1221
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
1222
+ this.pushInstruction({
1223
+ op: "struct.get",
1224
+ typeIndex: boxInfo.typeIndex,
1225
+ fieldIndex: 0,
1226
+ });
1227
+ this._emitIncDecStep(op, boxInfo.wType, localInfo.goType);
1228
+ this.pushInstruction({
1229
+ op: "struct.set",
1230
+ typeIndex: boxInfo.typeIndex,
1231
+ fieldIndex: 0,
1232
+ });
1233
+ return;
1234
+ }
1235
+
1236
+ const targetType = localInfo ? localInfo.type : globalInfo.type;
1237
+
1238
+ if (localInfo) {
1239
+ this.pushInstruction({ op: "local.get", index: localInfo.index });
1240
+ } else {
1241
+ this.pushInstruction({ op: "global.get", index: globalInfo.index });
1242
+ }
1243
+
1244
+ this._emitIncDecStep(op, targetType, expr._type);
1245
+
1246
+ if (localInfo) {
1247
+ this.pushInstruction({ op: "local.set", index: localInfo.index });
1248
+ } else {
1249
+ this.pushInstruction({ op: "global.set", index: globalInfo.index });
1250
+ }
1251
+ }
1252
+ }
1253
+
1254
+ _emitIncDecStep(op, targetType, goType) {
1255
+ const isAdd = op === "++";
1256
+ if (targetType === "i64") {
1257
+ this.pushInstruction({ op: "i64.const", value: 1n });
1258
+ this.pushInstruction(isAdd ? "i64.add" : "i64.sub");
1259
+ } else if (targetType === "f32" || targetType === "f64") {
1260
+ this.pushInstruction({ op: `${targetType}.const`, value: 1.0 });
1261
+ this.pushInstruction(`${targetType}.${isAdd ? "add" : "sub"}`);
1262
+ } else {
1263
+ this.pushInstruction({ op: "i32.const", value: 1 });
1264
+ this.pushInstruction(isAdd ? "i32.add" : "i32.sub");
1265
+ this.emitNarrowIntWrap(goType);
1266
+ }
1267
+ }
1268
+
1269
+ emitIfStmt(stmt) {
1270
+ if (stmt.init) this.emitStmt(stmt.init);
1271
+
1272
+ this.emitExpr(stmt.cond, "i32");
1273
+ this.pushInstruction({ op: "if", blockType: "void" });
1274
+ this.pushControl("if");
1275
+
1276
+ const thenBlock = stmt.body ?? stmt.then;
1277
+ if (thenBlock) this.emitBlock(thenBlock);
1278
+ const elseBlock = stmt.elseBody ?? stmt.else;
1279
+ if (elseBlock) {
1280
+ this.pushInstruction("else");
1281
+ if (elseBlock.kind === "Block") {
1282
+ this.emitBlock(elseBlock);
1283
+ } else {
1284
+ this.emitStmt(elseBlock);
1285
+ }
1286
+ }
1287
+ this.pushInstruction("end");
1288
+ }
1289
+
1290
+ emitRangeForStmt(stmt) {
1291
+ const rangeExpr =
1292
+ stmt.cond?.kind === "RangeExpr" ? stmt.cond : stmt.init.rhs[0];
1293
+ const isAssign = stmt.init?.kind === "AssignStmt";
1294
+ const lhs = stmt.init?.lhs ?? [];
1295
+ const iterExpr = rangeExpr.expr;
1296
+ const iterType = iterExpr._type;
1297
+
1298
+ // `for i := range` shadows any outer `i`; restore the outer binding afterwards.
1299
+ const shadowed = isAssign
1300
+ ? []
1301
+ : lhs
1302
+ .filter((l) => l && l.name !== "_")
1303
+ .map((l) => [l.name, this.locals.get(l.name)]);
1304
+ const restoreShadowed = () => {
1305
+ for (const [name, prev] of shadowed) {
1306
+ if (prev) this.locals.set(name, prev);
1307
+ else this.locals.delete(name);
1308
+ }
1309
+ };
1310
+
1311
+ if (isIntRangeType(iterType)) {
1312
+ const limitTmp = this.acquireTemp("i32");
1313
+ this._emitIndexExprToI32(iterExpr, limitTmp);
1314
+
1315
+ const idxTmp = this.acquireTemp("i32");
1316
+ this.pushInstruction({ op: "i32.const", value: 0 });
1317
+ this.pushInstruction({ op: "local.set", index: idxTmp });
1318
+
1319
+ let idxLocalInfo = null;
1320
+ if (lhs[0] && lhs[0].name !== "_") {
1321
+ if (!isAssign) {
1322
+ const bodyBlock = stmt.body ?? stmt.block;
1323
+ const isNonEscaping =
1324
+ !this.mutatedCaptures?.has(lhs[0].name) &&
1325
+ !this.capturedNames?.includes(lhs[0].name) &&
1326
+ !this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
1327
+ const wType = isNonEscaping ? "i32" : "i64";
1328
+ const lIdx = this.allocLocal(lhs[0].name, wType, {
1329
+ kind: "basic",
1330
+ name: "int",
1331
+ });
1332
+ idxLocalInfo = { index: lIdx, type: wType };
1333
+ } else {
1334
+ idxLocalInfo =
1335
+ this.resolveLocal(lhs[0].name) ??
1336
+ this.mod.resolveGlobal(lhs[0].name);
1337
+ }
1338
+ }
1339
+
1340
+ // Outer block for break
1341
+ this.pushInstruction({ op: "block", blockType: "void" });
1342
+ this.pushControl("break", stmt.label);
1343
+
1344
+ // Middle loop
1345
+ this.pushInstruction({ op: "loop", blockType: "void" });
1346
+ this.pushControl("loop", stmt.label);
1347
+
1348
+ // Condition: idx >= limit -> break
1349
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1350
+ this.pushInstruction({ op: "local.get", index: limitTmp });
1351
+ this.pushInstruction("i32.ge_s");
1352
+ const breakDepth = this.resolveBranchDepth(stmt.label, false);
1353
+ this.pushInstruction({ op: "br_if", depth: breakDepth });
1354
+
1355
+ // Inner block for continue
1356
+ this.pushInstruction({ op: "block", blockType: "void" });
1357
+ this.pushControl("continue", stmt.label);
1358
+
1359
+ // Assign loop var if present
1360
+ if (idxLocalInfo) {
1361
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1362
+ if (idxLocalInfo.type === "i64") {
1363
+ this.pushInstruction("i64.extend_i32_s");
1364
+ }
1365
+ if (this.resolveLocal(lhs[0].name)) {
1366
+ this.pushInstruction({
1367
+ op: "local.set",
1368
+ index: idxLocalInfo.index,
1369
+ });
1370
+ } else {
1371
+ this.pushInstruction({
1372
+ op: "global.set",
1373
+ index: idxLocalInfo.index,
1374
+ });
1375
+ }
1376
+ }
1377
+
1378
+ // Emit body
1379
+ const bodyBlock = stmt.body ?? stmt.block;
1380
+ if (bodyBlock) {
1381
+ this.emitBlock(bodyBlock);
1382
+ }
1383
+
1384
+ // End continue
1385
+ this.pushInstruction("end");
1386
+
1387
+ // Post: idx++
1388
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1389
+ this.pushInstruction({ op: "i32.const", value: 1 });
1390
+ this.pushInstruction("i32.add");
1391
+ this.pushInstruction({ op: "local.set", index: idxTmp });
1392
+
1393
+ // Loop back
1394
+ this.pushInstruction({ op: "br", depth: 0 });
1395
+
1396
+ // End loop
1397
+ this.pushInstruction("end");
1398
+
1399
+ // End break
1400
+ this.pushInstruction("end");
1401
+
1402
+ this.releaseTemp(idxTmp, "i32");
1403
+ this.releaseTemp(limitTmp, "i32");
1404
+ restoreShadowed();
1405
+ return;
1406
+ }
1407
+
1408
+ if (isStringType(iterType, this.mod.checker)) {
1409
+ const strTmp = this.acquireTemp("externref");
1410
+ this.emitExpr(iterExpr, "externref");
1411
+ this.pushInstruction({ op: "local.set", index: strTmp });
1412
+
1413
+ const lenTmp = this.acquireTemp("i32");
1414
+ this.pushInstruction({ op: "local.get", index: strTmp });
1415
+ this.pushInstruction({
1416
+ op: "call",
1417
+ funcIndex: this.mod.getStringLenImportIndex(),
1418
+ });
1419
+ this.pushInstruction({ op: "local.set", index: lenTmp });
1420
+
1421
+ const idxTmp = this.acquireTemp("i32");
1422
+ this.pushInstruction({ op: "i32.const", value: 0 });
1423
+ this.pushInstruction({ op: "local.set", index: idxTmp });
1424
+
1425
+ let idxLocalInfo = null;
1426
+ if (lhs[0] && lhs[0].name !== "_") {
1427
+ if (!isAssign) {
1428
+ const lIdx = this.allocLocal(lhs[0].name, "i64", {
1429
+ kind: "basic",
1430
+ name: "int",
1431
+ });
1432
+ idxLocalInfo = { index: lIdx, type: "i64" };
1433
+ } else {
1434
+ idxLocalInfo =
1435
+ this.resolveLocal(lhs[0].name) ??
1436
+ this.mod.resolveGlobal(lhs[0].name);
1437
+ }
1438
+ }
1439
+
1440
+ let valLocalInfo = null;
1441
+ if (lhs[1] && lhs[1].name !== "_") {
1442
+ if (!isAssign) {
1443
+ const lIdx = this.allocLocal(lhs[1].name, "i32", {
1444
+ kind: "basic",
1445
+ name: "rune",
1446
+ });
1447
+ valLocalInfo = { index: lIdx, type: "i32" };
1448
+ } else {
1449
+ valLocalInfo =
1450
+ this.resolveLocal(lhs[1].name) ??
1451
+ this.mod.resolveGlobal(lhs[1].name);
1452
+ }
1453
+ }
1454
+
1455
+ // Outer block for break
1456
+ this.pushInstruction({ op: "block", blockType: "void" });
1457
+ this.pushControl("break", stmt.label);
1458
+
1459
+ // Middle loop
1460
+ this.pushInstruction({ op: "loop", blockType: "void" });
1461
+ this.pushControl("loop", stmt.label);
1462
+
1463
+ // Condition: idx >= len -> break
1464
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1465
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1466
+ this.pushInstruction("i32.ge_s");
1467
+ const breakDepth = this.resolveBranchDepth(stmt.label, false);
1468
+ this.pushInstruction({ op: "br_if", depth: breakDepth });
1469
+
1470
+ // Inner block for continue
1471
+ this.pushInstruction({ op: "block", blockType: "void" });
1472
+ this.pushControl("continue", stmt.label);
1473
+
1474
+ const runeTmp = this.acquireTemp("i32");
1475
+ this.pushInstruction({ op: "local.get", index: strTmp });
1476
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1477
+ this.pushInstruction({
1478
+ op: "call",
1479
+ funcIndex: this.mod.getStringCodePointAtImportIndex(),
1480
+ });
1481
+ this.pushInstruction({ op: "local.set", index: runeTmp });
1482
+
1483
+ // Assign index variable
1484
+ if (idxLocalInfo) {
1485
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1486
+ this.pushInstruction("i64.extend_i32_s");
1487
+ if (this.resolveLocal(lhs[0].name)) {
1488
+ this.pushInstruction({
1489
+ op: "local.set",
1490
+ index: idxLocalInfo.index,
1491
+ });
1492
+ } else {
1493
+ this.pushInstruction({
1494
+ op: "global.set",
1495
+ index: idxLocalInfo.index,
1496
+ });
1497
+ }
1498
+ }
1499
+
1500
+ // Assign rune variable
1501
+ if (valLocalInfo) {
1502
+ this.pushInstruction({ op: "local.get", index: runeTmp });
1503
+ if (valLocalInfo.type === "i64") {
1504
+ this.pushInstruction("i64.extend_i32_u");
1505
+ }
1506
+ if (this.resolveLocal(lhs[1].name)) {
1507
+ this.pushInstruction({
1508
+ op: "local.set",
1509
+ index: valLocalInfo.index,
1510
+ });
1511
+ } else {
1512
+ this.pushInstruction({
1513
+ op: "global.set",
1514
+ index: valLocalInfo.index,
1515
+ });
1516
+ }
1517
+ }
1518
+
1519
+ // Body
1520
+ const bodyBlock = stmt.body ?? stmt.block;
1521
+ if (bodyBlock) {
1522
+ this.emitBlock(bodyBlock);
1523
+ }
1524
+
1525
+ // End continue block
1526
+ this.pushInstruction("end");
1527
+
1528
+ // Advance idxTmp: idx += (rune > 0xffff ? 2 : 1)
1529
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1530
+ this.pushInstruction({ op: "local.get", index: runeTmp });
1531
+ this.pushInstruction({ op: "i32.const", value: 65535 });
1532
+ this.pushInstruction("i32.gt_u");
1533
+ this.pushInstruction({ op: "if", blockType: "i32" });
1534
+ this.pushInstruction({ op: "i32.const", value: 2 });
1535
+ this.pushInstruction("else");
1536
+ this.pushInstruction({ op: "i32.const", value: 1 });
1537
+ this.pushInstruction("end");
1538
+ this.pushInstruction("i32.add");
1539
+ this.pushInstruction({ op: "local.set", index: idxTmp });
1540
+
1541
+ // Repeat loop
1542
+ this.pushInstruction({ op: "br", depth: 0 });
1543
+
1544
+ // End loop
1545
+ this.pushInstruction("end");
1546
+
1547
+ // End break block
1548
+ this.pushInstruction("end");
1549
+
1550
+ this.releaseTemp(runeTmp, "i32");
1551
+ this.releaseTemp(idxTmp, "i32");
1552
+ this.releaseTemp(lenTmp, "i32");
1553
+ this.releaseTemp(strTmp, "externref");
1554
+ restoreShadowed();
1555
+ return;
1556
+ }
1557
+
1558
+ if (isMapType(iterType, this.mod.checker)) {
1559
+ const { keyType, valType } = getMapKeyValTypes(
1560
+ iterType,
1561
+ this.mod.checker,
1562
+ );
1563
+ const mapInfo = this.mod.getMapType(keyType, valType);
1564
+
1565
+ const mapTmp = this.acquireTemp(mapInfo.wType);
1566
+ this.emitExpr(iterExpr, mapInfo.wType);
1567
+ this.pushInstruction({ op: "local.set", index: mapTmp });
1568
+
1569
+ // Outer block for break
1570
+ this.pushInstruction({ op: "block", blockType: "void" });
1571
+ this.pushControl("break", stmt.label);
1572
+
1573
+ // If map is nil: break immediately
1574
+ this.pushInstruction({ op: "local.get", index: mapTmp });
1575
+ this.pushInstruction("ref.is_null");
1576
+ const earlyBreakDepth = this.resolveBranchDepth(stmt.label, false);
1577
+ this.pushInstruction({ op: "br_if", depth: earlyBreakDepth });
1578
+
1579
+ const currIdxTmp = this.acquireTemp("i32");
1580
+ const nextIdxTmp = this.acquireTemp("i32");
1581
+ const entryTmp = this.acquireTemp({
1582
+ kind: "ref",
1583
+ nullable: true,
1584
+ typeIndex: mapInfo.entryTypeIndex,
1585
+ });
1586
+
1587
+ // currIdx = map.head (field 5)
1588
+ this.pushInstruction({ op: "local.get", index: mapTmp });
1589
+ this.pushInstruction({
1590
+ op: "struct.get",
1591
+ typeIndex: mapInfo.typeIndex,
1592
+ fieldIndex: 5,
1593
+ });
1594
+ this.pushInstruction({ op: "local.set", index: currIdxTmp });
1595
+
1596
+ let keyLocalInfo = null;
1597
+ if (lhs[0] && lhs[0].name !== "_") {
1598
+ if (!isAssign) {
1599
+ const lIdx = this.allocLocal(lhs[0].name, mapInfo.keyWType, keyType);
1600
+ keyLocalInfo = { index: lIdx, type: mapInfo.keyWType };
1601
+ } else {
1602
+ keyLocalInfo =
1603
+ this.resolveLocal(lhs[0].name) ??
1604
+ this.mod.resolveGlobal(lhs[0].name);
1605
+ }
1606
+ }
1607
+
1608
+ let valLocalInfo = null;
1609
+ if (lhs[1] && lhs[1].name !== "_") {
1610
+ if (!isAssign) {
1611
+ const lIdx = this.allocLocal(lhs[1].name, mapInfo.valWType, valType);
1612
+ valLocalInfo = { index: lIdx, type: mapInfo.valWType };
1613
+ } else {
1614
+ valLocalInfo =
1615
+ this.resolveLocal(lhs[1].name) ??
1616
+ this.mod.resolveGlobal(lhs[1].name);
1617
+ }
1618
+ }
1619
+
1620
+ // Loop block
1621
+ this.pushInstruction({ op: "loop", blockType: "void" });
1622
+ this.pushControl("loop", stmt.label);
1623
+
1624
+ // Condition: currIdx == -1 -> break
1625
+ this.pushInstruction({ op: "local.get", index: currIdxTmp });
1626
+ this.pushInstruction({ op: "i32.const", value: -1 });
1627
+ this.pushInstruction("i32.eq");
1628
+ const breakDepth = this.resolveBranchDepth(stmt.label, false);
1629
+ this.pushInstruction({ op: "br_if", depth: breakDepth });
1630
+
1631
+ // entry = map.entries[currIdx]
1632
+ this.pushInstruction({ op: "local.get", index: mapTmp });
1633
+ this.pushInstruction({
1634
+ op: "struct.get",
1635
+ typeIndex: mapInfo.typeIndex,
1636
+ fieldIndex: 1,
1637
+ });
1638
+ this.pushInstruction({ op: "local.get", index: currIdxTmp });
1639
+ this.pushInstruction({
1640
+ op: "array.get",
1641
+ typeIndex: mapInfo.entriesTypeIndex,
1642
+ });
1643
+ this.pushInstruction({ op: "local.set", index: entryTmp });
1644
+
1645
+ // nextIdx = entry.order_next (field 4)
1646
+ this.pushInstruction({ op: "local.get", index: entryTmp });
1647
+ this.pushInstruction({
1648
+ op: "struct.get",
1649
+ typeIndex: mapInfo.entryTypeIndex,
1650
+ fieldIndex: 4,
1651
+ });
1652
+ this.pushInstruction({ op: "local.set", index: nextIdxTmp });
1653
+
1654
+ // If entry.active == 0 (field 5): currIdx = nextIdx; br 0 (continue next iteration)
1655
+ this.pushInstruction({ op: "local.get", index: entryTmp });
1656
+ this.pushInstruction({
1657
+ op: "struct.get",
1658
+ typeIndex: mapInfo.entryTypeIndex,
1659
+ fieldIndex: 5,
1660
+ });
1661
+ this.pushInstruction({ op: "i32.const", value: 0 });
1662
+ this.pushInstruction("i32.eq");
1663
+ this.pushInstruction({ op: "if", blockType: "void" });
1664
+ this.pushInstruction({ op: "local.get", index: nextIdxTmp });
1665
+ this.pushInstruction({ op: "local.set", index: currIdxTmp });
1666
+ this.pushInstruction({ op: "br", depth: 1 });
1667
+ this.pushInstruction("end");
1668
+
1669
+ // Inner block for continue
1670
+ this.pushInstruction({ op: "block", blockType: "void" });
1671
+ this.pushControl("continue", stmt.label);
1672
+
1673
+ // Assign key variable
1674
+ if (keyLocalInfo) {
1675
+ this.pushInstruction({ op: "local.get", index: entryTmp });
1676
+ this.pushInstruction({
1677
+ op: "struct.get",
1678
+ typeIndex: mapInfo.entryTypeIndex,
1679
+ fieldIndex: 0,
1680
+ });
1681
+ if (this.resolveLocal(lhs[0].name)) {
1682
+ this.pushInstruction({
1683
+ op: "local.set",
1684
+ index: keyLocalInfo.index,
1685
+ });
1686
+ } else {
1687
+ this.pushInstruction({
1688
+ op: "global.set",
1689
+ index: keyLocalInfo.index,
1690
+ });
1691
+ }
1692
+ }
1693
+
1694
+ // Assign val variable
1695
+ if (valLocalInfo) {
1696
+ this.pushInstruction({ op: "local.get", index: entryTmp });
1697
+ this.pushInstruction({
1698
+ op: "struct.get",
1699
+ typeIndex: mapInfo.entryTypeIndex,
1700
+ fieldIndex: 1,
1701
+ });
1702
+ const isValStruct =
1703
+ isStructType(valType, this.mod.checker, this.mod) &&
1704
+ !isPointerToStruct(valType, this.mod.checker, this.mod);
1705
+ if (isValStruct) {
1706
+ const sInfo = this._resolveStructInfo({ _type: valType });
1707
+ if (sInfo) {
1708
+ this.emitCloneStruct(sInfo, mapInfo.valWType);
1709
+ }
1710
+ }
1711
+ if (this.resolveLocal(lhs[1].name)) {
1712
+ this.pushInstruction({
1713
+ op: "local.set",
1714
+ index: valLocalInfo.index,
1715
+ });
1716
+ } else {
1717
+ this.pushInstruction({
1718
+ op: "global.set",
1719
+ index: valLocalInfo.index,
1720
+ });
1721
+ }
1722
+ }
1723
+
1724
+ // Body
1725
+ const bodyBlock = stmt.body ?? stmt.block;
1726
+ if (bodyBlock) {
1727
+ this.emitBlock(bodyBlock);
1728
+ }
1729
+
1730
+ // End continue block
1731
+ this.pushInstruction("end");
1732
+
1733
+ // Advance: currIdx = nextIdx
1734
+ this.pushInstruction({ op: "local.get", index: nextIdxTmp });
1735
+ this.pushInstruction({ op: "local.set", index: currIdxTmp });
1736
+
1737
+ // Loop back
1738
+ this.pushInstruction({ op: "br", depth: 0 });
1739
+
1740
+ // End loop
1741
+ this.pushInstruction("end");
1742
+
1743
+ // End break
1744
+ this.pushInstruction("end");
1745
+
1746
+ this.releaseTemp(entryTmp, {
1747
+ kind: "ref",
1748
+ nullable: true,
1749
+ typeIndex: mapInfo.entryTypeIndex,
1750
+ });
1751
+ this.releaseTemp(nextIdxTmp, "i32");
1752
+ this.releaseTemp(currIdxTmp, "i32");
1753
+ this.releaseTemp(mapTmp, mapInfo.wType);
1754
+ restoreShadowed();
1755
+ return;
1756
+ }
1757
+
1758
+ const sharedInfo = this._sharedInfo(iterType);
1759
+ if (sharedInfo) {
1760
+ this._emitSharedRange(stmt, iterExpr, sharedInfo, lhs, isAssign);
1761
+ restoreShadowed();
1762
+ return;
1763
+ }
1764
+
1765
+ // Slice or Array range
1766
+ const isSlice = isSliceType(iterType, this.mod.checker);
1767
+ const isPtrToArr =
1768
+ iterType?.kind === "pointer" &&
1769
+ isArrayType(iterType.base, this.mod.checker);
1770
+
1771
+ const elemGoType = isSlice
1772
+ ? iterType.elem
1773
+ : isPtrToArr
1774
+ ? iterType.base.elem
1775
+ : iterType.elem;
1776
+ const arrInfo = this.mod.getArrayType(elemGoType);
1777
+ const sliceInfo = isSlice ? this.mod.getSliceType(elemGoType) : null;
1778
+
1779
+ const arrWType = {
1780
+ kind: "ref",
1781
+ nullable: true,
1782
+ typeIndex: arrInfo.typeIndex,
1783
+ };
1784
+ const hasVal = Boolean(lhs[1] && lhs[1].name !== "_");
1785
+ const arrTmp = hasVal ? this.acquireTemp(arrWType) : null;
1786
+ const offTmp = hasVal ? this.acquireTemp("i32") : null;
1787
+ const lenTmp = this.acquireTemp("i32");
1788
+
1789
+ if (isSlice) {
1790
+ const sliceWType = {
1791
+ kind: "ref",
1792
+ nullable: true,
1793
+ typeIndex: sliceInfo.typeIndex,
1794
+ };
1795
+ const sliceTmp = this.acquireTemp(sliceWType);
1796
+ this.emitExpr(iterExpr, sliceWType);
1797
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
1798
+
1799
+ if (hasVal) {
1800
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
1801
+ arr: arrTmp,
1802
+ off: offTmp,
1803
+ len: lenTmp,
1804
+ });
1805
+ } else {
1806
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
1807
+ len: lenTmp,
1808
+ });
1809
+ }
1810
+ this.releaseTemp(sliceTmp, sliceWType);
1811
+ } else {
1812
+ this.emitExpr(iterExpr, arrWType);
1813
+ if (hasVal) {
1814
+ this.pushInstruction({ op: "local.set", index: arrTmp });
1815
+ this.pushInstruction({ op: "i32.const", value: 0 });
1816
+ this.pushInstruction({ op: "local.set", index: offTmp });
1817
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1818
+ }
1819
+ this.pushInstruction("array.len");
1820
+ this.pushInstruction({ op: "local.set", index: lenTmp });
1821
+ }
1822
+
1823
+ const idxTmp = this.acquireTemp("i32");
1824
+ this.pushInstruction({ op: "i32.const", value: 0 });
1825
+ this.pushInstruction({ op: "local.set", index: idxTmp });
1826
+
1827
+ let idxLocalInfo = null;
1828
+ if (lhs[0] && lhs[0].name !== "_") {
1829
+ if (!isAssign) {
1830
+ const bodyBlock = stmt.body ?? stmt.block;
1831
+ const isNonEscaping =
1832
+ !this.mutatedCaptures?.has(lhs[0].name) &&
1833
+ !this.capturedNames?.includes(lhs[0].name) &&
1834
+ !this._varMutatesOrEscapes(bodyBlock, lhs[0].name);
1835
+ const wType = isNonEscaping ? "i32" : "i64";
1836
+ const lIdx = this.allocLocal(lhs[0].name, wType, {
1837
+ kind: "basic",
1838
+ name: "int",
1839
+ });
1840
+ idxLocalInfo = { index: lIdx, type: wType };
1841
+ } else {
1842
+ idxLocalInfo =
1843
+ this.resolveLocal(lhs[0].name) ?? this.mod.resolveGlobal(lhs[0].name);
1844
+ }
1845
+ }
1846
+
1847
+ let valLocalInfo = null;
1848
+ if (lhs[1] && lhs[1].name !== "_") {
1849
+ if (!isAssign) {
1850
+ const lIdx = this.allocLocal(
1851
+ lhs[1].name,
1852
+ arrInfo.elemWType,
1853
+ elemGoType,
1854
+ );
1855
+ valLocalInfo = { index: lIdx, type: arrInfo.elemWType };
1856
+ } else {
1857
+ valLocalInfo =
1858
+ this.resolveLocal(lhs[1].name) ?? this.mod.resolveGlobal(lhs[1].name);
1859
+ }
1860
+ }
1861
+
1862
+ // Outer block for break
1863
+ this.pushInstruction({ op: "block", blockType: "void" });
1864
+ this.pushControl("break", stmt.label);
1865
+
1866
+ // Middle loop
1867
+ this.pushInstruction({ op: "loop", blockType: "void" });
1868
+ this.pushControl("loop", stmt.label);
1869
+
1870
+ // Condition: idx >= len -> break
1871
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1872
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1873
+ this.pushInstruction("i32.ge_s");
1874
+ const breakDepth = this.resolveBranchDepth(stmt.label, false);
1875
+ this.pushInstruction({ op: "br_if", depth: breakDepth });
1876
+
1877
+ // Inner block for continue
1878
+ this.pushInstruction({ op: "block", blockType: "void" });
1879
+ this.pushControl("continue", stmt.label);
1880
+
1881
+ // Assign idx
1882
+ if (idxLocalInfo) {
1883
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1884
+ if (idxLocalInfo.type === "i64") {
1885
+ this.pushInstruction("i64.extend_i32_s");
1886
+ }
1887
+ if (this.resolveLocal(lhs[0].name)) {
1888
+ this.pushInstruction({ op: "local.set", index: idxLocalInfo.index });
1889
+ } else {
1890
+ this.pushInstruction({
1891
+ op: "global.set",
1892
+ index: idxLocalInfo.index,
1893
+ });
1894
+ }
1895
+ }
1896
+
1897
+ // Assign val
1898
+ if (valLocalInfo) {
1899
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1900
+ this.pushInstruction({ op: "local.get", index: offTmp });
1901
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1902
+ this.pushInstruction("i32.add");
1903
+ this.pushInstruction({
1904
+ op: "array.get",
1905
+ typeIndex: arrInfo.typeIndex,
1906
+ });
1907
+ if (
1908
+ isStructType(elemGoType, this.mod.checker, this.mod) &&
1909
+ !isPointerToStruct(elemGoType, this.mod.checker, this.mod)
1910
+ ) {
1911
+ const sInfo = this._resolveStructInfo({ _type: elemGoType });
1912
+ if (sInfo) {
1913
+ this.emitCloneStruct(sInfo, arrInfo.elemWType);
1914
+ }
1915
+ }
1916
+ if (this.resolveLocal(lhs[1].name)) {
1917
+ this.pushInstruction({ op: "local.set", index: valLocalInfo.index });
1918
+ } else {
1919
+ this.pushInstruction({
1920
+ op: "global.set",
1921
+ index: valLocalInfo.index,
1922
+ });
1923
+ }
1924
+ }
1925
+
1926
+ // Body
1927
+ const bodyBlock = stmt.body ?? stmt.block;
1928
+ if (bodyBlock) {
1929
+ this.emitBlock(bodyBlock);
1930
+ }
1931
+
1932
+ // End continue
1933
+ this.pushInstruction("end");
1934
+
1935
+ // Post: idx++
1936
+ this.pushInstruction({ op: "local.get", index: idxTmp });
1937
+ this.pushInstruction({ op: "i32.const", value: 1 });
1938
+ this.pushInstruction("i32.add");
1939
+ this.pushInstruction({ op: "local.set", index: idxTmp });
1940
+
1941
+ // Loop back
1942
+ this.pushInstruction({ op: "br", depth: 0 });
1943
+
1944
+ // End loop
1945
+ this.pushInstruction("end");
1946
+
1947
+ // End break
1948
+ this.pushInstruction("end");
1949
+
1950
+ this.releaseTemp(idxTmp, "i32");
1951
+ this.releaseTemp(lenTmp, "i32");
1952
+ if (offTmp !== null) this.releaseTemp(offTmp, "i32");
1953
+ if (arrTmp !== null) this.releaseTemp(arrTmp, arrWType);
1954
+
1955
+ restoreShadowed();
1956
+ }
1957
+
1958
+ emitForStmt(stmt) {
1959
+ if (isRangeFor(stmt) || stmt.cond?.kind === "RangeExpr") {
1960
+ this.emitRangeForStmt(stmt);
1961
+ return;
1962
+ }
1963
+
1964
+ const iv = this._detectInductionVar(stmt);
1965
+ let prevLocal = null;
1966
+ let ivLocalInfo = null;
1967
+
1968
+ if (iv) {
1969
+ prevLocal = this.locals.get(iv.name);
1970
+ const lIdx = this.allocLocal(
1971
+ iv.name,
1972
+ "i32",
1973
+ { kind: "basic", name: "int" },
1974
+ false,
1975
+ );
1976
+ ivLocalInfo = this.locals.get(iv.name);
1977
+ ivLocalInfo.isInductionVar = true;
1978
+ this.emitExpr(iv.initExpr, "i32");
1979
+ this.pushInstruction({ op: "local.set", index: lIdx });
1980
+ } else if (stmt.init) {
1981
+ this.emitStmt(stmt.init);
1982
+ }
1983
+
1984
+ // Outer block for break
1985
+ this.pushInstruction({ op: "block", blockType: "void" });
1986
+ this.pushControl("break", stmt.label);
1987
+
1988
+ // Middle loop for repeating iterations
1989
+ this.pushInstruction({ op: "loop", blockType: "void" });
1990
+ this.pushControl("loop", stmt.label);
1991
+
1992
+ if (stmt.cond) {
1993
+ this.emitExpr(stmt.cond, "i32");
1994
+ this.pushInstruction("i32.eqz");
1995
+ const breakDepth = this.resolveBranchDepth(stmt.label, false);
1996
+ this.pushInstruction({ op: "br_if", depth: breakDepth });
1997
+ }
1998
+
1999
+ // Inner block for continue
2000
+ this.pushInstruction({ op: "block", blockType: "void" });
2001
+ this.pushControl("continue", stmt.label);
2002
+
2003
+ const bodyBlock = stmt.body ?? stmt.block;
2004
+ if (bodyBlock) {
2005
+ this.emitBlock(bodyBlock);
2006
+ }
2007
+
2008
+ // End inner continue block
2009
+ this.pushInstruction("end");
2010
+
2011
+ if (iv) {
2012
+ this.pushInstruction({ op: "local.get", index: ivLocalInfo.index });
2013
+ this.pushInstruction({ op: "i32.const", value: iv.step });
2014
+ this.pushInstruction("i32.add");
2015
+ this.pushInstruction({ op: "local.set", index: ivLocalInfo.index });
2016
+ } else if (stmt.post) {
2017
+ this.emitStmt(stmt.post);
2018
+ }
2019
+
2020
+ // Repeat loop: branch back to loop block
2021
+ this.pushInstruction({ op: "br", depth: 0 });
2022
+
2023
+ // End loop
2024
+ this.pushInstruction("end");
2025
+
2026
+ // End outer break block
2027
+ this.pushInstruction("end");
2028
+
2029
+ if (iv) {
2030
+ if (prevLocal) {
2031
+ this.locals.set(iv.name, prevLocal);
2032
+ } else {
2033
+ this.locals.delete(iv.name);
2034
+ }
2035
+ }
2036
+ }
2037
+
2038
+ _detectInductionVar(stmt) {
2039
+ if (stmt.init?.kind !== "DefineStmt") return null;
2040
+ const lhs = stmt.init.lhs ?? [];
2041
+ if (lhs.length !== 1 || lhs[0].kind !== "Ident") return null;
2042
+ const varName = lhs[0].name;
2043
+ if (!varName || varName === "_") return null;
2044
+
2045
+ const rhs = stmt.init.rhs ?? [];
2046
+ if (rhs.length !== 1) return null;
2047
+ const initRhs = rhs[0];
2048
+
2049
+ // Initial value must be a non-negative integer literal < 2^31 or i32 expr
2050
+ let initOk = false;
2051
+ if (initRhs.kind === "BasicLit" && initRhs.litKind === "INT") {
2052
+ const val = BigInt(initRhs.value);
2053
+ if (val >= 0n && val < 2147483648n) initOk = true;
2054
+ } else if (this.toWasmType(initRhs._type) === "i32") {
2055
+ initOk = true;
2056
+ }
2057
+ if (!initOk) return null;
2058
+
2059
+ // Post statement must increment varName by a constant > 0
2060
+ let step = null;
2061
+ if (stmt.post) {
2062
+ if (
2063
+ stmt.post.kind === "IncDecStmt" &&
2064
+ stmt.post.expr?.kind === "Ident" &&
2065
+ stmt.post.expr.name === varName &&
2066
+ stmt.post.op === "++"
2067
+ ) {
2068
+ step = 1;
2069
+ } else if (
2070
+ stmt.post.kind === "CompoundAssignStmt" &&
2071
+ stmt.post.lhs?.length === 1 &&
2072
+ stmt.post.lhs[0].kind === "Ident" &&
2073
+ stmt.post.lhs[0].name === varName &&
2074
+ stmt.post.op === "+=" &&
2075
+ stmt.post.rhs?.length === 1 &&
2076
+ stmt.post.rhs[0].kind === "BasicLit" &&
2077
+ stmt.post.rhs[0].litKind === "INT"
2078
+ ) {
2079
+ const sVal = BigInt(stmt.post.rhs[0].value);
2080
+ if (sVal > 0n && sVal < 2147483648n) step = Number(sVal);
2081
+ }
2082
+ }
2083
+ if (step === null) return null;
2084
+
2085
+ // Condition must bound varName (< bound or <= bound) where bound < 2^31
2086
+ const boundNode = this._findInductionBound(stmt.cond, varName);
2087
+ if (!boundNode) return null;
2088
+ // The last `i += step` must not wrap i32: i can reach bound + step - 1.
2089
+ if (
2090
+ boundNode.kind === "BasicLit" &&
2091
+ BigInt(boundNode.value) + BigInt(step) > 2147483647n
2092
+ ) {
2093
+ return null;
2094
+ }
2095
+
2096
+ // Check escaping and mutation in body
2097
+ if (this.mutatedCaptures?.has(varName)) return null;
2098
+ if (this.capturedNames?.includes(varName)) return null;
2099
+
2100
+ const bodyBlock = stmt.body ?? stmt.block;
2101
+ if (bodyBlock && this._varMutatesOrEscapes(bodyBlock, varName)) {
2102
+ return null;
2103
+ }
2104
+
2105
+ return { name: varName, initExpr: initRhs, step };
2106
+ }
2107
+
2108
+ _findInductionBound(cond, varName) {
2109
+ if (!cond) return null;
2110
+ if (cond.kind === "BinaryExpr") {
2111
+ if (
2112
+ (cond.op === "<" || cond.op === "<=") &&
2113
+ cond.left?.kind === "Ident" &&
2114
+ cond.left.name === varName
2115
+ ) {
2116
+ const right = cond.right;
2117
+ if (
2118
+ right?.kind === "CallExpr" &&
2119
+ right.func?.kind === "Ident" &&
2120
+ (right.func.name === "len" || right.func.name === "cap")
2121
+ ) {
2122
+ return right;
2123
+ }
2124
+ if (right?.kind === "BasicLit" && right.litKind === "INT") {
2125
+ const val = BigInt(right.value);
2126
+ if (val >= 0n && val < 2147483648n) return right;
2127
+ return null;
2128
+ }
2129
+ if (this.toWasmType(right?._type) === "i32") {
2130
+ return right;
2131
+ }
2132
+ return null;
2133
+ }
2134
+ if (cond.op === "&&") {
2135
+ return (
2136
+ this._findInductionBound(cond.left, varName) ??
2137
+ this._findInductionBound(cond.right, varName)
2138
+ );
2139
+ }
2140
+ }
2141
+ return null;
2142
+ }
2143
+
2144
+ _varMutatesOrEscapes(node, varName) {
2145
+ if (!node || typeof node !== "object") return false;
2146
+ if (Array.isArray(node)) {
2147
+ return node.some((item) => this._varMutatesOrEscapes(item, varName));
2148
+ }
2149
+ if (
2150
+ node.kind === "UnaryExpr" &&
2151
+ node.op === "&" &&
2152
+ node.operand?.kind === "Ident" &&
2153
+ node.operand.name === varName
2154
+ ) {
2155
+ return true;
2156
+ }
2157
+ if (node.kind === "FuncLit") {
2158
+ if (this._findIdentInAST(node, varName)) return true;
2159
+ }
2160
+ if (node.kind === "AssignStmt" || node.kind === "CompoundAssignStmt") {
2161
+ const lhs = node.lhs ?? [];
2162
+ for (const l of lhs) {
2163
+ if (l.kind === "Ident" && l.name === varName) return true;
2164
+ }
2165
+ }
2166
+ if (node.kind === "IncDecStmt") {
2167
+ if (node.expr?.kind === "Ident" && node.expr.name === varName)
2168
+ return true;
2169
+ }
2170
+ if (node.kind === "DefineStmt") {
2171
+ const lhs = node.lhs ?? [];
2172
+ for (const l of lhs) {
2173
+ if (l.kind === "Ident" && l.name === varName) return true;
2174
+ }
2175
+ }
2176
+ for (const key of Object.keys(node)) {
2177
+ if (key.startsWith("_")) continue;
2178
+ if (this._varMutatesOrEscapes(node[key], varName)) return true;
2179
+ }
2180
+ return false;
2181
+ }
2182
+
2183
+ _findIdentInAST(node, name) {
2184
+ if (!node || typeof node !== "object") return false;
2185
+ if (Array.isArray(node)) {
2186
+ return node.some((item) => this._findIdentInAST(item, name));
2187
+ }
2188
+ if (node.kind === "Ident" && node.name === name) return true;
2189
+ for (const key of Object.keys(node)) {
2190
+ if (key.startsWith("_")) continue;
2191
+ if (this._findIdentInAST(node[key], name)) return true;
2192
+ }
2193
+ return false;
2194
+ }
2195
+
2196
+ emitSwitchStmt(stmt) {
2197
+ this.pushInstruction({ op: "block", blockType: "void" });
2198
+ this.pushControl("break", stmt.label);
2199
+
2200
+ if (stmt.init) this.emitStmt(stmt.init);
2201
+
2202
+ const getCaseVals = (c) => c.list ?? c.values ?? [];
2203
+ const nonDefaultCases = (stmt.cases ?? []).filter(
2204
+ (c) => getCaseVals(c).length > 0,
2205
+ );
2206
+ const defaultCase = (stmt.cases ?? []).find(
2207
+ (c) => getCaseVals(c).length === 0,
2208
+ );
2209
+
2210
+ let tagGoType = null;
2211
+ let tagWType = null;
2212
+ let tagTmp = -1;
2213
+ if (stmt.tag) {
2214
+ tagGoType = stmt.tag._type ?? this._resolveExprGoType(stmt.tag);
2215
+ tagWType = this.toWasmType(tagGoType);
2216
+ tagTmp = this.acquireTemp(tagWType);
2217
+ this.emitExpr(stmt.tag, tagWType);
2218
+ this.pushInstruction({ op: "local.set", index: tagTmp });
2219
+ }
2220
+
2221
+ let ifCount = 0;
2222
+ for (const c of nonDefaultCases) {
2223
+ const vals = getCaseVals(c);
2224
+ for (let i = 0; i < vals.length; i++) {
2225
+ if (stmt.tag) {
2226
+ this.pushInstruction({ op: "local.get", index: tagTmp });
2227
+ this.emitExpr(vals[i], tagWType);
2228
+ this.emitBinaryOp("==", tagWType, tagGoType);
2229
+ } else {
2230
+ this.emitExpr(vals[i], "i32");
2231
+ }
2232
+ if (i > 0) this.pushInstruction("i32.or");
2233
+ }
2234
+ this.pushInstruction({ op: "if", blockType: "void" });
2235
+ ifCount++;
2236
+
2237
+ for (const s of c.stmts ?? []) this.emitStmt(s);
2238
+ this.pushInstruction("else");
2239
+ }
2240
+
2241
+ if (defaultCase) {
2242
+ for (const s of defaultCase.stmts ?? []) this.emitStmt(s);
2243
+ }
2244
+
2245
+ for (let i = 0; i < ifCount; i++) {
2246
+ this.pushInstruction("end");
2247
+ }
2248
+ if (stmt.tag) {
2249
+ this.releaseTemp(tagTmp, tagWType);
2250
+ }
2251
+
2252
+ this.pushInstruction("end");
2253
+ }
2254
+
2255
+ emitTypeSwitchStmt(stmt) {
2256
+ this.pushInstruction({ op: "block", blockType: "void" });
2257
+ this.pushControl("break", stmt.label);
2258
+
2259
+ const prevAssignLocal = stmt.assign ? this.locals.get(stmt.assign) : null;
2260
+
2261
+ const tswTmp = this.acquireTemp("anyref");
2262
+ this.emitExpr(stmt.expr, "anyref");
2263
+ this.pushInstruction({ op: "local.set", index: tswTmp });
2264
+
2265
+ const getCaseTypes = (c) => c.types ?? c.list ?? [];
2266
+
2267
+ const nonDefaultCases = (stmt.cases ?? []).filter(
2268
+ (c) => getCaseTypes(c).length > 0,
2269
+ );
2270
+ const defaultCase = (stmt.cases ?? []).find(
2271
+ (c) => getCaseTypes(c).length === 0,
2272
+ );
2273
+
2274
+ let ifCount = 0;
2275
+ for (const c of nonDefaultCases) {
2276
+ const types = getCaseTypes(c);
2277
+ for (let i = 0; i < types.length; i++) {
2278
+ this.pushInstruction({ op: "local.get", index: tswTmp });
2279
+ this._emitTypeTest(types[i]._type ?? types[i]);
2280
+ if (i > 0) {
2281
+ this.pushInstruction("i32.or");
2282
+ }
2283
+ }
2284
+ this.pushInstruction({ op: "if", blockType: "void" });
2285
+ ifCount++;
2286
+
2287
+ if (stmt.assign) {
2288
+ const targetGoType =
2289
+ types.length === 1
2290
+ ? (types[0]._type ?? types[0])
2291
+ : { kind: "basic", name: "any" };
2292
+ const targetWType = this.toWasmType(targetGoType);
2293
+ const assignLocal = this.allocLocal(
2294
+ stmt.assign,
2295
+ targetWType,
2296
+ targetGoType,
2297
+ );
2298
+ if (types.length === 1) {
2299
+ this.pushInstruction({ op: "local.get", index: tswTmp });
2300
+ this._emitTypeCast(targetGoType, targetWType);
2301
+ } else {
2302
+ this.pushInstruction({ op: "local.get", index: tswTmp });
2303
+ }
2304
+ this.pushInstruction({ op: "local.set", index: assignLocal });
2305
+ }
2306
+
2307
+ for (const s of c.stmts ?? []) {
2308
+ this.emitStmt(s);
2309
+ }
2310
+
2311
+ this.pushInstruction("else");
2312
+ }
2313
+
2314
+ if (defaultCase) {
2315
+ if (stmt.assign) {
2316
+ const assignLocal = this.allocLocal(stmt.assign, "anyref", {
2317
+ kind: "basic",
2318
+ name: "any",
2319
+ });
2320
+ this.pushInstruction({ op: "local.get", index: tswTmp });
2321
+ this.pushInstruction({ op: "local.set", index: assignLocal });
2322
+ }
2323
+ for (const s of defaultCase.stmts ?? []) {
2324
+ this.emitStmt(s);
2325
+ }
2326
+ }
2327
+
2328
+ for (let i = 0; i < ifCount; i++) {
2329
+ this.pushInstruction("end");
2330
+ }
2331
+
2332
+ this.releaseTemp(tswTmp, "anyref");
2333
+
2334
+ if (prevAssignLocal) {
2335
+ this.locals.set(stmt.assign, prevAssignLocal);
2336
+ } else if (stmt.assign) {
2337
+ this.locals.delete(stmt.assign);
2338
+ }
2339
+
2340
+ this.pushInstruction("end");
2341
+ }
2342
+
2343
+ emitBranchStmt(stmt) {
2344
+ const isContinue = stmt.keyword === "continue";
2345
+ const depth = this.resolveBranchDepth(stmt.label, isContinue);
2346
+ this.pushInstruction({ op: "br", depth });
2347
+ }
2348
+
2349
+ emitReturnStmt(stmt) {
2350
+ const values = stmt.values ?? [];
2351
+ if (this.hasDefer) {
2352
+ if (this.namedReturnVars?.length > 0) {
2353
+ if (values.length === 0) {
2354
+ // Naked return: named return vars already hold their values
2355
+ } else if (values.length === 1 && this.namedReturnVars.length > 1) {
2356
+ // Multi-value call: leaves results on stack
2357
+ this.emitExpr(values[0]);
2358
+ for (let i = this.namedReturnVars.length - 1; i >= 0; i--) {
2359
+ this._assignToNamedReturnVar(this.namedReturnVars[i]);
2360
+ }
2361
+ } else {
2362
+ // Evaluate all values into temporary locals first
2363
+ const temps = [];
2364
+ for (let i = 0; i < values.length; i++) {
2365
+ const val = values[i];
2366
+ const targetWType = this.returnTypes[i] ?? null;
2367
+ this.emitExpr(val, targetWType);
2368
+ this._emitCopyIfValueStruct(val, val._type, targetWType);
2369
+ const tmp = this.acquireTemp(targetWType);
2370
+ this.pushInstruction({ op: "local.set", index: tmp });
2371
+ temps.push({ tmp, type: targetWType });
2372
+ }
2373
+ for (let i = 0; i < temps.length; i++) {
2374
+ this.pushInstruction({ op: "local.get", index: temps[i].tmp });
2375
+ this._assignToNamedReturnVar(this.namedReturnVars[i]);
2376
+ this.releaseTemp(temps[i].tmp, temps[i].type);
2377
+ }
2378
+ }
2379
+ } else if (this.returnTempLocals?.length > 0) {
2380
+ if (values.length === 1 && this.returnTempLocals.length > 1) {
2381
+ this.emitExpr(values[0]);
2382
+ for (let i = this.returnTempLocals.length - 1; i >= 0; i--) {
2383
+ this.pushInstruction({
2384
+ op: "local.set",
2385
+ index: this.returnTempLocals[i],
2386
+ });
2387
+ }
2388
+ } else {
2389
+ for (let i = 0; i < values.length; i++) {
2390
+ const val = values[i];
2391
+ const targetWType = this.returnTypes[i] ?? null;
2392
+ this.emitExpr(val, targetWType);
2393
+ this._emitCopyIfValueStruct(val, val._type, targetWType);
2394
+ this.pushInstruction({
2395
+ op: "local.set",
2396
+ index: this.returnTempLocals[i],
2397
+ });
2398
+ }
2399
+ }
2400
+ }
2401
+ const depth = this.resolveBranchDepthToRole("runDefers");
2402
+ this.pushInstruction({ op: "br", depth });
2403
+ return;
2404
+ }
2405
+
2406
+ if (values.length === 0 && this.namedReturnVars?.length > 0) {
2407
+ for (const nr of this.namedReturnVars) {
2408
+ const loc = this.locals.get(nr.name);
2409
+ if (loc.isBoxed) {
2410
+ this.pushInstruction({ op: "local.get", index: loc.index });
2411
+ this.pushInstruction({
2412
+ op: "struct.get",
2413
+ typeIndex: loc.boxInfo.typeIndex,
2414
+ fieldIndex: 0,
2415
+ });
2416
+ } else {
2417
+ this.pushInstruction({ op: "local.get", index: loc.index });
2418
+ }
2419
+ }
2420
+ this.pushInstruction("return");
2421
+ return;
2422
+ }
2423
+
2424
+ for (let i = 0; i < values.length; i++) {
2425
+ const val = values[i];
2426
+ const targetWType = this.returnTypes[i] ?? null;
2427
+ this.emitExpr(val, targetWType);
2428
+ this._emitCopyIfValueStruct(val, val._type, targetWType);
2429
+ }
2430
+ this.pushInstruction("return");
2431
+ }
2432
+
2433
+ _assignToNamedReturnVar(nr) {
2434
+ const loc = this.locals.get(nr.name);
2435
+ if (loc.isBoxed) {
2436
+ const valTmp = this.acquireTemp(loc.boxInfo.wType);
2437
+ this.pushInstruction({ op: "local.set", index: valTmp });
2438
+ this.pushInstruction({ op: "local.get", index: loc.index });
2439
+ this.pushInstruction({ op: "local.get", index: valTmp });
2440
+ this.pushInstruction({
2441
+ op: "struct.set",
2442
+ typeIndex: loc.boxInfo.typeIndex,
2443
+ fieldIndex: 0,
2444
+ });
2445
+ this.releaseTemp(valTmp, loc.boxInfo.wType);
2446
+ } else {
2447
+ this.pushInstruction({ op: "local.set", index: loc.index });
2448
+ }
2449
+ }
2450
+
2451
+ emitExprStmt(stmt) {
2452
+ this.emitExpr(stmt.expr);
2453
+ const goType = stmt.expr._type;
2454
+ if (!goType || goType.name === "void") return;
2455
+ if (goType.kind === "tuple" || goType.kind === "TupleType") {
2456
+ for (const _ of goType.types ?? []) {
2457
+ this.pushInstruction("drop");
2458
+ }
2459
+ return;
2460
+ }
2461
+ const wType = this.toWasmType(goType);
2462
+ if (wType) {
2463
+ this.pushInstruction("drop");
2464
+ }
2465
+ }
2466
+ }