gofront 1.4.0 → 1.6.0

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