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,3006 @@
1
+ // src/backend/wasm/emit-stdlib.js
2
+ // FunctionEmitter mixin: stdlib packages implemented natively (math, math/bits,
3
+ // maps, slices, strings, strconv, fmt, errors, sort, unicode/utf8, testing)
4
+ // plus the runtime helper bodies they rely on.
5
+
6
+ import {
7
+ ERROR_STRUCT,
8
+ getMapKeyValTypes,
9
+ isErrorType,
10
+ toWasmType,
11
+ } from "./types.js";
12
+
13
+ // `unicode/utf8` package constants.
14
+ const UTF8_CONSTS = {
15
+ RuneError: 0xfffd,
16
+ MaxRune: 0x10ffff,
17
+ UTFMax: 4,
18
+ RuneSelf: 0x80,
19
+ };
20
+
21
+ // Go `math` package constants; BigInt entries are integer-typed. Float
22
+ // literals are the shortest decimal that round-trips to Go's float64 value.
23
+ const MATH_CONSTS = {
24
+ E: Math.E,
25
+ Pi: Math.PI,
26
+ Phi: 1.618033988749895,
27
+ Sqrt2: Math.SQRT2,
28
+ SqrtE: 1.6487212707001282,
29
+ SqrtPi: 1.7724538509055159,
30
+ SqrtPhi: 1.272019649514069,
31
+ Ln2: Math.LN2,
32
+ Log2E: Math.LOG2E,
33
+ Ln10: Math.LN10,
34
+ Log10E: Math.LOG10E,
35
+ MaxFloat32: 3.4028234663852886e38,
36
+ SmallestNonzeroFloat32: 1.401298464324817e-45,
37
+ MaxFloat64: Number.MAX_VALUE,
38
+ SmallestNonzeroFloat64: Number.MIN_VALUE,
39
+ MaxInt: 9223372036854775807n,
40
+ MinInt: -9223372036854775808n,
41
+ MaxInt8: 127n,
42
+ MinInt8: -128n,
43
+ MaxInt16: 32767n,
44
+ MinInt16: -32768n,
45
+ MaxInt32: 2147483647n,
46
+ MinInt32: -2147483648n,
47
+ MaxInt64: 9223372036854775807n,
48
+ MinInt64: -9223372036854775808n,
49
+ MaxUint8: 255n,
50
+ MaxUint16: 65535n,
51
+ MaxUint32: 4294967295n,
52
+ MaxUint: 18446744073709551615n,
53
+ MaxUint64: 18446744073709551615n,
54
+ };
55
+
56
+ // `math` functions that map 1:1 onto a single-operand f64 instruction.
57
+ const MATH_F64_UNARY = {
58
+ Sqrt: "f64.sqrt",
59
+ Floor: "f64.floor",
60
+ Ceil: "f64.ceil",
61
+ Trunc: "f64.trunc",
62
+ Abs: "f64.abs",
63
+ };
64
+
65
+ // Imported `Math.*` functions taking two f64 operands.
66
+ export const BINARY_MATH_IMPORTS = new Set(["atan2", "pow", "mod", "hypot"]);
67
+
68
+ export class StdlibEmitter {
69
+ _isMathConst(expr) {
70
+ return (
71
+ expr.expr?.kind === "Ident" &&
72
+ expr.expr.name === "math" &&
73
+ !this.resolveLocal("math") &&
74
+ !this.mod.resolveGlobal("math") &&
75
+ MATH_CONSTS[expr.field] !== undefined
76
+ );
77
+ }
78
+
79
+ _isUtf8Const(expr) {
80
+ return (
81
+ expr.expr?.kind === "Ident" &&
82
+ expr.expr.name === "utf8" &&
83
+ !this.resolveLocal("utf8") &&
84
+ !this.mod.resolveGlobal("utf8") &&
85
+ UTF8_CONSTS[expr.field] !== undefined
86
+ );
87
+ }
88
+
89
+ emitUtf8Const(name, targetWasmType) {
90
+ const v = UTF8_CONSTS[name];
91
+ if ((targetWasmType ?? "i32") === "i64") {
92
+ this.pushInstruction({ op: "i64.const", value: BigInt(v) });
93
+ } else {
94
+ this.pushInstruction({ op: "i32.const", value: v });
95
+ }
96
+ }
97
+
98
+ emitMathConst(name, targetWasmType) {
99
+ const v = MATH_CONSTS[name];
100
+ const wType = targetWasmType ?? (typeof v === "bigint" ? "i64" : "f64");
101
+ switch (wType) {
102
+ case "i32":
103
+ this.pushInstruction({ op: "i32.const", value: Number(v) | 0 });
104
+ return;
105
+ case "i64":
106
+ this.pushInstruction({
107
+ op: "i64.const",
108
+ value: BigInt.asIntN(
109
+ 64,
110
+ typeof v === "bigint" ? v : BigInt(Math.trunc(v)),
111
+ ),
112
+ });
113
+ return;
114
+ case "f32":
115
+ this.pushInstruction({
116
+ op: "f32.const",
117
+ value: Math.fround(Number(v)),
118
+ });
119
+ return;
120
+ default:
121
+ this.pushInstruction({ op: "f64.const", value: Number(v) });
122
+ }
123
+ }
124
+
125
+ emitMathCall(name, args) {
126
+ // Native WASM instructions
127
+ const unaryOp = MATH_F64_UNARY[name];
128
+ if (unaryOp) {
129
+ this.emitExpr(args[0], "f64");
130
+ this.pushInstruction(unaryOp);
131
+ return;
132
+ }
133
+ switch (name) {
134
+ case "Min":
135
+ this.emitExpr(args[0], "f64");
136
+ this.emitExpr(args[1], "f64");
137
+ this.pushInstruction("f64.min");
138
+ return;
139
+ case "Max":
140
+ this.emitExpr(args[0], "f64");
141
+ this.emitExpr(args[1], "f64");
142
+ this.pushInstruction("f64.max");
143
+ return;
144
+ case "Copysign":
145
+ this.emitExpr(args[0], "f64");
146
+ this.emitExpr(args[1], "f64");
147
+ this.pushInstruction("f64.copysign");
148
+ return;
149
+ case "Dim":
150
+ // max(x-y, 0)
151
+ this.emitExpr(args[0], "f64");
152
+ this.emitExpr(args[1], "f64");
153
+ this.pushInstruction("f64.sub");
154
+ this.pushInstruction({ op: "f64.const", value: 0 });
155
+ this.pushInstruction("f64.max");
156
+ return;
157
+ case "Signbit":
158
+ this.emitExpr(args[0], "f64");
159
+ this.pushInstruction("i64.reinterpret_f64");
160
+ this.pushInstruction({ op: "i64.const", value: 0n });
161
+ this.pushInstruction("i64.lt_s");
162
+ return;
163
+ case "Exp2":
164
+ this.pushInstruction({ op: "f64.const", value: 2 });
165
+ this.emitExpr(args[0], "f64");
166
+ this.pushInstruction({
167
+ op: "call",
168
+ funcIndex: this.mod.getMathImportIndex("pow", true),
169
+ });
170
+ return;
171
+ case "Inf":
172
+ // Go: Inf(sign) is +Inf when sign >= 0; copysign with the converted
173
+ // sign gives exactly that (0 converts to +0.0).
174
+ this.pushInstruction({ op: "f64.const", value: Infinity });
175
+ this.emitExpr(args[0], "i64");
176
+ this.pushInstruction("f64.convert_i64_s");
177
+ this.pushInstruction("f64.copysign");
178
+ return;
179
+ case "NaN":
180
+ this.pushInstruction({ op: "f64.const", value: NaN });
181
+ return;
182
+ case "IsNaN": {
183
+ const tmp = this.acquireTemp("f64");
184
+ this.emitExpr(args[0], "f64");
185
+ this.pushInstruction({ op: "local.tee", index: tmp });
186
+ this.pushInstruction({ op: "local.get", index: tmp });
187
+ this.pushInstruction("f64.ne");
188
+ this.releaseTemp(tmp, "f64");
189
+ return;
190
+ }
191
+ case "IsInf": {
192
+ // IsInf(f, sign): sign > 0 → f == +Inf, sign < 0 → f == -Inf, else |f| == Inf.
193
+ const f = this.acquireTemp("f64");
194
+ const s = this.acquireTemp("i64");
195
+ this.emitExpr(args[0], "f64");
196
+ this.pushInstruction({ op: "local.set", index: f });
197
+ this.emitExpr(args[1], "i64");
198
+ this.pushInstruction({ op: "local.set", index: s });
199
+ // (s >= 0 && f == +Inf) || (s <= 0 && f == -Inf)
200
+ this.pushInstruction({ op: "local.get", index: s });
201
+ this.pushInstruction({ op: "i64.const", value: 0n });
202
+ this.pushInstruction("i64.ge_s");
203
+ this.pushInstruction({ op: "local.get", index: f });
204
+ this.pushInstruction({ op: "f64.const", value: Infinity });
205
+ this.pushInstruction("f64.eq");
206
+ this.pushInstruction("i32.and");
207
+ this.pushInstruction({ op: "local.get", index: s });
208
+ this.pushInstruction({ op: "i64.const", value: 0n });
209
+ this.pushInstruction("i64.le_s");
210
+ this.pushInstruction({ op: "local.get", index: f });
211
+ this.pushInstruction({ op: "f64.const", value: -Infinity });
212
+ this.pushInstruction("f64.eq");
213
+ this.pushInstruction("i32.and");
214
+ this.pushInstruction("i32.or");
215
+ this.releaseTemp(s, "i64");
216
+ this.releaseTemp(f, "f64");
217
+ return;
218
+ }
219
+ }
220
+
221
+ // Imported JS Math functions
222
+ const jsMathMap = {
223
+ Sin: "sin",
224
+ Cos: "cos",
225
+ Tan: "tan",
226
+ Asin: "asin",
227
+ Acos: "acos",
228
+ Atan: "atan",
229
+ Atan2: "atan2",
230
+ Pow: "pow",
231
+ Mod: "mod",
232
+ Hypot: "hypot",
233
+ Cbrt: "cbrt",
234
+ Exp: "exp",
235
+ Log: "log",
236
+ Log2: "log2",
237
+ Log10: "log10",
238
+ Round: "round",
239
+ };
240
+
241
+ if (jsMathMap[name]) {
242
+ const jsName = jsMathMap[name];
243
+ const isBinary = BINARY_MATH_IMPORTS.has(jsName);
244
+ this.emitExpr(args[0], "f64");
245
+ if (isBinary) this.emitExpr(args[1], "f64");
246
+ const funcIdx = this.mod.getMathImportIndex(jsName, isBinary);
247
+ this.pushInstruction({ op: "call", funcIndex: funcIdx });
248
+ return;
249
+ }
250
+
251
+ throw new Error(`Unsupported math function: math.${name}`);
252
+ }
253
+
254
+ // `t.Method(args...)` on a `*testing.T`: the receiver is a JS object held
255
+ // as externref. Arguments are pushed to a JS-side buffer one at a time
256
+ // (typed imports), then `testing_call(t, nameIdx)` dispatches by name.
257
+ emitTestingCall(func, args) {
258
+ const method = func.field;
259
+ const nameIdx = this.mod.internString(method);
260
+ if (method === "Run") {
261
+ this.emitExpr(func.expr, "externref");
262
+ this.emitExpr(args[0], "externref");
263
+ this.emitExpr(args[1], "anyref");
264
+ this.pushInstruction({
265
+ op: "call",
266
+ funcIndex: this.mod.getStdlibImportIndex("testing_run"),
267
+ });
268
+ return;
269
+ }
270
+ if (method === "Name") {
271
+ this.emitExpr(func.expr, "externref");
272
+ this.pushInstruction({
273
+ op: "call",
274
+ funcIndex: this.mod.getTestingNameImportIndex(),
275
+ });
276
+ return;
277
+ }
278
+ if (method === "Failed" || method === "Skipped") {
279
+ this.emitExpr(func.expr, "externref");
280
+ this.pushInstruction({ op: "i32.const", value: nameIdx });
281
+ this.pushInstruction({
282
+ op: "call",
283
+ funcIndex: this.mod.getTestingFlagImportIndex(),
284
+ });
285
+ return;
286
+ }
287
+ const temps = this._emitArgBuffer(args);
288
+ this.emitExpr(func.expr, "externref");
289
+ this.pushInstruction({ op: "i32.const", value: nameIdx });
290
+ this.pushInstruction({
291
+ op: "call",
292
+ funcIndex: this.mod.getTestingCallImportIndex(),
293
+ });
294
+ this._releaseArgBuffer(temps);
295
+ }
296
+
297
+ // Evaluates every argument into a temp first, then pushes them in order via
298
+ // the typed `testing_arg_*` imports. Anything that may run user code (the
299
+ // argument itself, or `Error()` on an `error` operand) happens in the first
300
+ // phase: such code can call Sprintf and reset the JS-side argument buffer,
301
+ // so nothing may be pushed until every operand is fully evaluated.
302
+ // Returns the temps so the caller can still read an argument (fmt.Errorf's
303
+ // %w cause keeps the original error value in `errTmp`).
304
+ _emitArgBuffer(args) {
305
+ const temps = [];
306
+ for (const arg of args) {
307
+ const goType = arg._type ?? this._resolveExprGoType(arg);
308
+ const isBool =
309
+ goType?.name === "bool" ||
310
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL");
311
+ const isErr = isErrorType(goType) && this.mod.hasErrorType;
312
+ const valW = toWasmType(goType, this.mod.checker);
313
+ if (isErr) {
314
+ const errTmp = this.acquireTemp("anyref");
315
+ const tmp = this.acquireTemp("externref");
316
+ this.emitExpr(arg, "anyref");
317
+ this.pushInstruction({ op: "local.tee", index: errTmp });
318
+ this.pushInstruction({
319
+ op: "call",
320
+ funcIndex: this.mod.resolveFuncIndex("__error_str"),
321
+ });
322
+ this.pushInstruction({ op: "local.set", index: tmp });
323
+ temps.push({ tmp, wType: "externref", isBool: false, errTmp });
324
+ continue;
325
+ }
326
+ const tmp = this.acquireTemp(valW);
327
+ this.emitExpr(arg, valW);
328
+ this.pushInstruction({ op: "local.set", index: tmp });
329
+ temps.push({ tmp, wType: valW, isBool, errTmp: null });
330
+ }
331
+ for (const { tmp, wType, isBool } of temps) {
332
+ this.pushInstruction({ op: "local.get", index: tmp });
333
+ this.pushInstruction({
334
+ op: "call",
335
+ funcIndex: this.mod.getTestingArgImportIndex(wType, isBool),
336
+ });
337
+ }
338
+ return temps;
339
+ }
340
+
341
+ _releaseArgBuffer(temps) {
342
+ for (let i = temps.length - 1; i >= 0; i--) {
343
+ this.releaseTemp(temps[i].tmp, temps[i].wType);
344
+ if (temps[i].errTmp !== null) this.releaseTemp(temps[i].errTmp, "anyref");
345
+ }
346
+ }
347
+
348
+ emitBitsCall(name, args) {
349
+ switch (name) {
350
+ case "LeadingZeros32":
351
+ this.emitExpr(args[0], "i32");
352
+ this.pushInstruction("i32.clz");
353
+ this.pushInstruction("i64.extend_i32_u");
354
+ return;
355
+ case "TrailingZeros32":
356
+ this.emitExpr(args[0], "i32");
357
+ this.pushInstruction("i32.ctz");
358
+ this.pushInstruction("i64.extend_i32_u");
359
+ return;
360
+ case "OnesCount32":
361
+ this.emitExpr(args[0], "i32");
362
+ this.pushInstruction("i32.popcnt");
363
+ this.pushInstruction("i64.extend_i32_u");
364
+ return;
365
+ case "RotateLeft32": {
366
+ this.emitExpr(args[0], "i32");
367
+ const arg1WType = toWasmType(args[1]?._type, this.mod.checker);
368
+ if (arg1WType === "i64") {
369
+ this.emitExpr(args[1], "i64");
370
+ this.pushInstruction("i32.wrap_i64");
371
+ } else {
372
+ this.emitExpr(args[1], "i32");
373
+ }
374
+ this.pushInstruction("i32.rotl");
375
+ return;
376
+ }
377
+
378
+ case "LeadingZeros64":
379
+ this.emitExpr(args[0], "i64");
380
+ this.pushInstruction("i64.clz");
381
+ return;
382
+ case "TrailingZeros64":
383
+ this.emitExpr(args[0], "i64");
384
+ this.pushInstruction("i64.ctz");
385
+ return;
386
+ case "OnesCount64":
387
+ this.emitExpr(args[0], "i64");
388
+ this.pushInstruction("i64.popcnt");
389
+ return;
390
+ case "RotateLeft64": {
391
+ this.emitExpr(args[0], "i64");
392
+ const arg1WType = toWasmType(args[1]?._type, this.mod.checker);
393
+ if (arg1WType === "i32") {
394
+ this.emitExpr(args[1], "i32");
395
+ this.pushInstruction("i64.extend_i32_s");
396
+ } else {
397
+ this.emitExpr(args[1], "i64");
398
+ }
399
+ this.pushInstruction("i64.rotl");
400
+ return;
401
+ }
402
+
403
+ default:
404
+ throw new Error(`Unsupported bits function: bits.${name}`);
405
+ }
406
+ }
407
+
408
+ emitSliceClear(arg, argType) {
409
+ const elemGoType = this._getSliceElemType(argType);
410
+ const sliceInfo = this.mod.getSliceType(elemGoType);
411
+ const arrInfo = sliceInfo.arrInfo;
412
+
413
+ const sliceTmp = this.acquireTemp({
414
+ kind: "ref",
415
+ nullable: true,
416
+ typeIndex: sliceInfo.typeIndex,
417
+ });
418
+ this.emitExpr(arg, {
419
+ kind: "ref",
420
+ nullable: true,
421
+ typeIndex: sliceInfo.typeIndex,
422
+ });
423
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
424
+
425
+ this.pushInstruction({ op: "block", blockType: "void" });
426
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
427
+ this.pushInstruction("ref.is_null");
428
+ this.pushInstruction({ op: "br_if", depth: 0 });
429
+
430
+ const arrTmp = this.acquireTemp({
431
+ kind: "ref",
432
+ nullable: true,
433
+ typeIndex: arrInfo.typeIndex,
434
+ });
435
+ const offTmp = this.acquireTemp("i32");
436
+ const lenTmp = this.acquireTemp("i32");
437
+ const iLoc = this.acquireTemp("i32");
438
+
439
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
440
+ arr: arrTmp,
441
+ off: offTmp,
442
+ len: lenTmp,
443
+ });
444
+
445
+ this.pushInstruction({ op: "i32.const", value: 0 });
446
+ this.pushInstruction({ op: "local.set", index: iLoc });
447
+
448
+ this.pushInstruction({ op: "block", blockType: "void" });
449
+ this.pushInstruction({ op: "loop", blockType: "void" });
450
+ this.pushInstruction({ op: "local.get", index: iLoc });
451
+ this.pushInstruction({ op: "local.get", index: lenTmp });
452
+ this.pushInstruction("i32.ge_s");
453
+ this.pushInstruction({ op: "br_if", depth: 1 });
454
+
455
+ this.pushInstruction({ op: "local.get", index: arrTmp });
456
+ this.pushInstruction({ op: "local.get", index: offTmp });
457
+ this.pushInstruction({ op: "local.get", index: iLoc });
458
+ this.pushInstruction("i32.add");
459
+ this.emitZeroValue(elemGoType, arrInfo.elemWType);
460
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
461
+
462
+ this.pushInstruction({ op: "local.get", index: iLoc });
463
+ this.pushInstruction({ op: "i32.const", value: 1 });
464
+ this.pushInstruction("i32.add");
465
+ this.pushInstruction({ op: "local.set", index: iLoc });
466
+ this.pushInstruction({ op: "br", depth: 0 });
467
+ this.pushInstruction("end");
468
+ this.pushInstruction("end");
469
+
470
+ this.releaseTemp(iLoc, "i32");
471
+ this.releaseTemp(lenTmp, "i32");
472
+ this.releaseTemp(offTmp, "i32");
473
+ this.releaseTemp(arrTmp, {
474
+ kind: "ref",
475
+ nullable: true,
476
+ typeIndex: arrInfo.typeIndex,
477
+ });
478
+
479
+ this.pushInstruction("end");
480
+ this.releaseTemp(sliceTmp, {
481
+ kind: "ref",
482
+ nullable: true,
483
+ typeIndex: sliceInfo.typeIndex,
484
+ });
485
+ }
486
+
487
+ emitMapsCall(name, args, _targetWasmType) {
488
+ switch (name) {
489
+ case "Keys": {
490
+ const mArg = args[0];
491
+ const mType = mArg._type ?? this._resolveExprGoType(mArg);
492
+ const { keyType, valType } = getMapKeyValTypes(mType, this.mod.checker);
493
+ const mapInfo = this.mod.getMapType(keyType, valType);
494
+ this.emitExpr(mArg, mapInfo.wType);
495
+ this.pushInstruction({
496
+ op: "call",
497
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.keysFuncName),
498
+ });
499
+ return;
500
+ }
501
+ case "Values": {
502
+ const mArg = args[0];
503
+ const mType = mArg._type ?? this._resolveExprGoType(mArg);
504
+ const { keyType, valType } = getMapKeyValTypes(mType, this.mod.checker);
505
+ const mapInfo = this.mod.getMapType(keyType, valType);
506
+ this.emitExpr(mArg, mapInfo.wType);
507
+ this.pushInstruction({
508
+ op: "call",
509
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.valuesFuncName),
510
+ });
511
+ return;
512
+ }
513
+ case "Clone": {
514
+ const mArg = args[0];
515
+ const mType = mArg._type ?? this._resolveExprGoType(mArg);
516
+ const { keyType, valType } = getMapKeyValTypes(mType, this.mod.checker);
517
+ const mapInfo = this.mod.getMapType(keyType, valType);
518
+ this.emitExpr(mArg, mapInfo.wType);
519
+ this.pushInstruction({
520
+ op: "call",
521
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.cloneFuncName),
522
+ });
523
+ return;
524
+ }
525
+ case "Copy":
526
+ this.emitMapsCopy(args);
527
+ return;
528
+ case "Equal":
529
+ this.emitMapsEqual(args, false);
530
+ return;
531
+ case "EqualFunc":
532
+ this.emitMapsEqual(args, true);
533
+ return;
534
+ case "Delete": {
535
+ const mArg = args[0];
536
+ const kArg = args[1];
537
+ const mType = mArg._type ?? this._resolveExprGoType(mArg);
538
+ const { keyType, valType } = getMapKeyValTypes(mType, this.mod.checker);
539
+ const mapInfo = this.mod.getMapType(keyType, valType);
540
+ this.emitExpr(mArg, mapInfo.wType);
541
+ this.emitExpr(kArg, mapInfo.keyWType);
542
+ this.pushInstruction({
543
+ op: "call",
544
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.deleteFuncName),
545
+ });
546
+ return;
547
+ }
548
+ case "DeleteFunc":
549
+ this.emitMapsDeleteFunc(args);
550
+ return;
551
+ default:
552
+ throw new Error(`Unsupported maps function: maps.${name}`);
553
+ }
554
+ }
555
+
556
+ emitMapsCopy(args) {
557
+ const dstNode = args[0];
558
+ const srcNode = args[1];
559
+ const dstType = dstNode._type ?? this._resolveExprGoType(dstNode);
560
+ const { keyType, valType } = getMapKeyValTypes(dstType, this.mod.checker);
561
+ const mapInfo = this.mod.getMapType(keyType, valType);
562
+
563
+ const dstTmp = this.acquireTemp(mapInfo.wType);
564
+ const srcTmp = this.acquireTemp(mapInfo.wType);
565
+ this.emitExpr(dstNode, mapInfo.wType);
566
+ this.pushInstruction({ op: "local.set", index: dstTmp });
567
+ this.emitExpr(srcNode, mapInfo.wType);
568
+ this.pushInstruction({ op: "local.set", index: srcTmp });
569
+
570
+ this.pushInstruction({ op: "block", blockType: "void" });
571
+ // if src == null -> return
572
+ this.pushInstruction({ op: "local.get", index: srcTmp });
573
+ this.pushInstruction("ref.is_null");
574
+ this.pushInstruction({ op: "br_if", depth: 0 });
575
+
576
+ // if dst == null -> panic
577
+ this.pushInstruction({ op: "local.get", index: dstTmp });
578
+ this.pushInstruction("ref.is_null");
579
+ this.pushInstruction({ op: "if", blockType: "void" });
580
+ this.emitPanic("assignment to entry in nil map");
581
+ this.pushInstruction("end");
582
+
583
+ const idxTmp = this.acquireTemp("i32");
584
+ const entryTmp = this.acquireTemp({
585
+ kind: "ref",
586
+ nullable: true,
587
+ typeIndex: mapInfo.entryTypeIndex,
588
+ });
589
+
590
+ // idx = src.head
591
+ this.pushInstruction({ op: "local.get", index: srcTmp });
592
+ this.pushInstruction({
593
+ op: "struct.get",
594
+ typeIndex: mapInfo.typeIndex,
595
+ fieldIndex: 5,
596
+ });
597
+ this.pushInstruction({ op: "local.set", index: idxTmp });
598
+
599
+ this.pushInstruction({ op: "block", blockType: "void" });
600
+ this.pushInstruction({ op: "loop", blockType: "void" });
601
+ this.pushInstruction({ op: "local.get", index: idxTmp });
602
+ this.pushInstruction({ op: "i32.const", value: -1 });
603
+ this.pushInstruction("i32.eq");
604
+ this.pushInstruction({ op: "br_if", depth: 1 });
605
+
606
+ this.pushInstruction({ op: "local.get", index: srcTmp });
607
+ this.pushInstruction({
608
+ op: "struct.get",
609
+ typeIndex: mapInfo.typeIndex,
610
+ fieldIndex: 1,
611
+ });
612
+ this.pushInstruction({ op: "local.get", index: idxTmp });
613
+ this.pushInstruction({
614
+ op: "array.get",
615
+ typeIndex: mapInfo.entriesTypeIndex,
616
+ });
617
+ this.pushInstruction({ op: "local.set", index: entryTmp });
618
+
619
+ this.pushInstruction({ op: "local.get", index: entryTmp });
620
+ this.pushInstruction({
621
+ op: "struct.get",
622
+ typeIndex: mapInfo.entryTypeIndex,
623
+ fieldIndex: 5,
624
+ });
625
+ this.pushInstruction({ op: "i32.const", value: 1 });
626
+ this.pushInstruction("i32.eq");
627
+ this.pushInstruction({ op: "if", blockType: "void" });
628
+
629
+ // dst[k] = v
630
+ this.pushInstruction({ op: "local.get", index: dstTmp });
631
+ this.pushInstruction({ op: "local.get", index: entryTmp });
632
+ this.pushInstruction({
633
+ op: "struct.get",
634
+ typeIndex: mapInfo.entryTypeIndex,
635
+ fieldIndex: 0,
636
+ });
637
+ this.pushInstruction({ op: "local.get", index: entryTmp });
638
+ this.pushInstruction({
639
+ op: "struct.get",
640
+ typeIndex: mapInfo.entryTypeIndex,
641
+ fieldIndex: 1,
642
+ });
643
+ this.pushInstruction({
644
+ op: "call",
645
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.setFuncName),
646
+ });
647
+
648
+ this.pushInstruction("end");
649
+
650
+ // idx = entry.order_next
651
+ this.pushInstruction({ op: "local.get", index: entryTmp });
652
+ this.pushInstruction({
653
+ op: "struct.get",
654
+ typeIndex: mapInfo.entryTypeIndex,
655
+ fieldIndex: 4,
656
+ });
657
+ this.pushInstruction({ op: "local.set", index: idxTmp });
658
+ this.pushInstruction({ op: "br", depth: 0 });
659
+ this.pushInstruction("end");
660
+ this.pushInstruction("end");
661
+
662
+ this.releaseTemp(entryTmp, {
663
+ kind: "ref",
664
+ nullable: true,
665
+ typeIndex: mapInfo.entryTypeIndex,
666
+ });
667
+ this.releaseTemp(idxTmp, "i32");
668
+
669
+ this.pushInstruction("end");
670
+ this.releaseTemp(srcTmp, mapInfo.wType);
671
+ this.releaseTemp(dstTmp, mapInfo.wType);
672
+ }
673
+
674
+ emitMapsEqual(args, isFunc = false) {
675
+ const m1Node = args[0];
676
+ const m2Node = args[1];
677
+ const eqFn = isFunc ? args[2] : null;
678
+
679
+ const m1Type = m1Node._type ?? this._resolveExprGoType(m1Node);
680
+ const { keyType, valType } = getMapKeyValTypes(m1Type, this.mod.checker);
681
+ const mapInfo = this.mod.getMapType(keyType, valType);
682
+
683
+ const m1Tmp = this.acquireTemp(mapInfo.wType);
684
+ const m2Tmp = this.acquireTemp(mapInfo.wType);
685
+ this.emitExpr(m1Node, mapInfo.wType);
686
+ this.pushInstruction({ op: "local.set", index: m1Tmp });
687
+ this.emitExpr(m2Node, mapInfo.wType);
688
+ this.pushInstruction({ op: "local.set", index: m2Tmp });
689
+
690
+ const resTmp = this.acquireTemp("i32");
691
+ this.pushInstruction({ op: "i32.const", value: 0 });
692
+ this.pushInstruction({ op: "local.set", index: resTmp });
693
+
694
+ this.pushInstruction({ op: "block", blockType: "void" }); // exit block 0
695
+
696
+ // if m1 == m2 -> res = 1; br 0
697
+ this.pushInstruction({ op: "local.get", index: m1Tmp });
698
+ this.pushInstruction({ op: "local.get", index: m2Tmp });
699
+ this.pushInstruction("ref.eq");
700
+ this.pushInstruction({ op: "if", blockType: "void" });
701
+ this.pushInstruction({ op: "i32.const", value: 1 });
702
+ this.pushInstruction({ op: "local.set", index: resTmp });
703
+ this.pushInstruction({ op: "br", depth: 1 });
704
+ this.pushInstruction("end");
705
+
706
+ // if m1 == null || m2 == null -> br 0
707
+ this.pushInstruction({ op: "local.get", index: m1Tmp });
708
+ this.pushInstruction("ref.is_null");
709
+ this.pushInstruction({ op: "local.get", index: m2Tmp });
710
+ this.pushInstruction("ref.is_null");
711
+ this.pushInstruction("i32.or");
712
+ this.pushInstruction({ op: "br_if", depth: 0 });
713
+
714
+ // if m1.len != m2.len -> br 0
715
+ this.pushInstruction({ op: "local.get", index: m1Tmp });
716
+ this.pushInstruction({
717
+ op: "struct.get",
718
+ typeIndex: mapInfo.typeIndex,
719
+ fieldIndex: 2,
720
+ });
721
+ this.pushInstruction({ op: "local.get", index: m2Tmp });
722
+ this.pushInstruction({
723
+ op: "struct.get",
724
+ typeIndex: mapInfo.typeIndex,
725
+ fieldIndex: 2,
726
+ });
727
+ this.pushInstruction("i32.ne");
728
+ this.pushInstruction({ op: "br_if", depth: 0 });
729
+
730
+ // Loop m1 entries
731
+ const idxTmp = this.acquireTemp("i32");
732
+ const entryTmp = this.acquireTemp({
733
+ kind: "ref",
734
+ nullable: true,
735
+ typeIndex: mapInfo.entryTypeIndex,
736
+ });
737
+ const val1Tmp = this.acquireTemp(mapInfo.valWType);
738
+ const val2Tmp = this.acquireTemp(mapInfo.valWType);
739
+ const okTmp = this.acquireTemp("i32");
740
+
741
+ this.pushInstruction({ op: "local.get", index: m1Tmp });
742
+ this.pushInstruction({
743
+ op: "struct.get",
744
+ typeIndex: mapInfo.typeIndex,
745
+ fieldIndex: 5,
746
+ });
747
+ this.pushInstruction({ op: "local.set", index: idxTmp });
748
+
749
+ this.pushInstruction({ op: "block", blockType: "void" });
750
+ this.pushInstruction({ op: "loop", blockType: "void" });
751
+ this.pushInstruction({ op: "local.get", index: idxTmp });
752
+ this.pushInstruction({ op: "i32.const", value: -1 });
753
+ this.pushInstruction("i32.eq");
754
+ this.pushInstruction({ op: "br_if", depth: 1 });
755
+
756
+ this.pushInstruction({ op: "local.get", index: m1Tmp });
757
+ this.pushInstruction({
758
+ op: "struct.get",
759
+ typeIndex: mapInfo.typeIndex,
760
+ fieldIndex: 1,
761
+ });
762
+ this.pushInstruction({ op: "local.get", index: idxTmp });
763
+ this.pushInstruction({
764
+ op: "array.get",
765
+ typeIndex: mapInfo.entriesTypeIndex,
766
+ });
767
+ this.pushInstruction({ op: "local.set", index: entryTmp });
768
+
769
+ this.pushInstruction({ op: "local.get", index: entryTmp });
770
+ this.pushInstruction({
771
+ op: "struct.get",
772
+ typeIndex: mapInfo.entryTypeIndex,
773
+ fieldIndex: 5,
774
+ });
775
+ this.pushInstruction({ op: "i32.const", value: 1 });
776
+ this.pushInstruction("i32.eq");
777
+ this.pushInstruction({ op: "if", blockType: "void" });
778
+
779
+ // val2, ok = getOk(m2, entry.key)
780
+ this.pushInstruction({ op: "local.get", index: m2Tmp });
781
+ this.pushInstruction({ op: "local.get", index: entryTmp });
782
+ this.pushInstruction({
783
+ op: "struct.get",
784
+ typeIndex: mapInfo.entryTypeIndex,
785
+ fieldIndex: 0,
786
+ });
787
+ this.pushInstruction({
788
+ op: "call",
789
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.getOkFuncName),
790
+ });
791
+ this.pushInstruction({ op: "local.set", index: okTmp });
792
+ this.pushInstruction({ op: "local.set", index: val2Tmp });
793
+
794
+ // if !ok -> br 3 (exit outer block with res=0)
795
+ this.pushInstruction({ op: "local.get", index: okTmp });
796
+ this.pushInstruction("i32.eqz");
797
+ this.pushInstruction({ op: "br_if", depth: 3 });
798
+
799
+ // check entry.val == val2
800
+ this.pushInstruction({ op: "local.get", index: entryTmp });
801
+ this.pushInstruction({
802
+ op: "struct.get",
803
+ typeIndex: mapInfo.entryTypeIndex,
804
+ fieldIndex: 1,
805
+ });
806
+ this.pushInstruction({ op: "local.set", index: val1Tmp });
807
+
808
+ if (isFunc) {
809
+ const eqClosureType = this._resolveExprGoType(eqFn);
810
+ const closureInfo = this.mod.getClosureType(eqClosureType);
811
+ const closureTmp = this.acquireTemp({
812
+ kind: "ref",
813
+ nullable: true,
814
+ typeIndex: closureInfo.typeIndex,
815
+ });
816
+ this.emitExpr(eqFn, {
817
+ kind: "ref",
818
+ nullable: true,
819
+ typeIndex: closureInfo.typeIndex,
820
+ });
821
+ this.pushInstruction({ op: "local.set", index: closureTmp });
822
+
823
+ // env
824
+ this.pushInstruction({ op: "local.get", index: closureTmp });
825
+ this.pushInstruction({
826
+ op: "struct.get",
827
+ typeIndex: closureInfo.typeIndex,
828
+ fieldIndex: 1,
829
+ });
830
+ // arg 0: val1
831
+ this.pushInstruction({ op: "local.get", index: val1Tmp });
832
+ // arg 1: val2
833
+ this.pushInstruction({ op: "local.get", index: val2Tmp });
834
+ // funcref
835
+ this.pushInstruction({ op: "local.get", index: closureTmp });
836
+ this.pushInstruction({
837
+ op: "struct.get",
838
+ typeIndex: closureInfo.typeIndex,
839
+ fieldIndex: 0,
840
+ });
841
+ this.pushInstruction({
842
+ op: "call_ref",
843
+ typeIndex: closureInfo.funcTypeIndex,
844
+ });
845
+ this.releaseTemp(closureTmp, {
846
+ kind: "ref",
847
+ nullable: true,
848
+ typeIndex: closureInfo.typeIndex,
849
+ });
850
+
851
+ this.pushInstruction("i32.eqz");
852
+ this.pushInstruction({ op: "br_if", depth: 3 });
853
+ } else {
854
+ this.emitKeyEq(val1Tmp, val2Tmp, valType);
855
+ this.pushInstruction("i32.eqz");
856
+ this.pushInstruction({ op: "br_if", depth: 3 });
857
+ }
858
+
859
+ this.pushInstruction("end"); // end if active
860
+
861
+ // idx = entry.order_next
862
+ this.pushInstruction({ op: "local.get", index: entryTmp });
863
+ this.pushInstruction({
864
+ op: "struct.get",
865
+ typeIndex: mapInfo.entryTypeIndex,
866
+ fieldIndex: 4,
867
+ });
868
+ this.pushInstruction({ op: "local.set", index: idxTmp });
869
+ this.pushInstruction({ op: "br", depth: 0 });
870
+ this.pushInstruction("end");
871
+ this.pushInstruction("end");
872
+
873
+ // All matched: res = 1
874
+ this.pushInstruction({ op: "i32.const", value: 1 });
875
+ this.pushInstruction({ op: "local.set", index: resTmp });
876
+
877
+ this.releaseTemp(okTmp, "i32");
878
+ this.releaseTemp(val2Tmp, mapInfo.valWType);
879
+ this.releaseTemp(val1Tmp, mapInfo.valWType);
880
+ this.releaseTemp(entryTmp, {
881
+ kind: "ref",
882
+ nullable: true,
883
+ typeIndex: mapInfo.entryTypeIndex,
884
+ });
885
+ this.releaseTemp(idxTmp, "i32");
886
+
887
+ this.pushInstruction("end"); // end outer block 0
888
+
889
+ this.pushInstruction({ op: "local.get", index: resTmp });
890
+ this.releaseTemp(resTmp, "i32");
891
+ this.releaseTemp(m2Tmp, mapInfo.wType);
892
+ this.releaseTemp(m1Tmp, mapInfo.wType);
893
+ }
894
+
895
+ emitMapsDeleteFunc(args) {
896
+ const mNode = args[0];
897
+ const delFn = args[1];
898
+ const mType = mNode._type ?? this._resolveExprGoType(mNode);
899
+ const { keyType, valType } = getMapKeyValTypes(mType, this.mod.checker);
900
+ const mapInfo = this.mod.getMapType(keyType, valType);
901
+
902
+ const mTmp = this.acquireTemp(mapInfo.wType);
903
+ this.emitExpr(mNode, mapInfo.wType);
904
+ this.pushInstruction({ op: "local.set", index: mTmp });
905
+
906
+ this.pushInstruction({ op: "block", blockType: "void" });
907
+ this.pushInstruction({ op: "local.get", index: mTmp });
908
+ this.pushInstruction("ref.is_null");
909
+ this.pushInstruction({ op: "br_if", depth: 0 });
910
+
911
+ const idxTmp = this.acquireTemp("i32");
912
+ const nextTmp = this.acquireTemp("i32");
913
+ const entryTmp = this.acquireTemp({
914
+ kind: "ref",
915
+ nullable: true,
916
+ typeIndex: mapInfo.entryTypeIndex,
917
+ });
918
+ const keyTmp = this.acquireTemp(mapInfo.keyWType);
919
+ const valTmp = this.acquireTemp(mapInfo.valWType);
920
+
921
+ const delClosureType = this._resolveExprGoType(delFn);
922
+ const closureInfo = this.mod.getClosureType(delClosureType);
923
+ const closureTmp = this.acquireTemp({
924
+ kind: "ref",
925
+ nullable: true,
926
+ typeIndex: closureInfo.typeIndex,
927
+ });
928
+ this.emitExpr(delFn, {
929
+ kind: "ref",
930
+ nullable: true,
931
+ typeIndex: closureInfo.typeIndex,
932
+ });
933
+ this.pushInstruction({ op: "local.set", index: closureTmp });
934
+
935
+ this.pushInstruction({ op: "local.get", index: mTmp });
936
+ this.pushInstruction({
937
+ op: "struct.get",
938
+ typeIndex: mapInfo.typeIndex,
939
+ fieldIndex: 5,
940
+ });
941
+ this.pushInstruction({ op: "local.set", index: idxTmp });
942
+
943
+ this.pushInstruction({ op: "block", blockType: "void" });
944
+ this.pushInstruction({ op: "loop", blockType: "void" });
945
+ this.pushInstruction({ op: "local.get", index: idxTmp });
946
+ this.pushInstruction({ op: "i32.const", value: -1 });
947
+ this.pushInstruction("i32.eq");
948
+ this.pushInstruction({ op: "br_if", depth: 1 });
949
+
950
+ this.pushInstruction({ op: "local.get", index: mTmp });
951
+ this.pushInstruction({
952
+ op: "struct.get",
953
+ typeIndex: mapInfo.typeIndex,
954
+ fieldIndex: 1,
955
+ });
956
+ this.pushInstruction({ op: "local.get", index: idxTmp });
957
+ this.pushInstruction({
958
+ op: "array.get",
959
+ typeIndex: mapInfo.entriesTypeIndex,
960
+ });
961
+ this.pushInstruction({ op: "local.set", index: entryTmp });
962
+
963
+ this.pushInstruction({ op: "local.get", index: entryTmp });
964
+ this.pushInstruction({
965
+ op: "struct.get",
966
+ typeIndex: mapInfo.entryTypeIndex,
967
+ fieldIndex: 4,
968
+ });
969
+ this.pushInstruction({ op: "local.set", index: nextTmp });
970
+
971
+ this.pushInstruction({ op: "local.get", index: entryTmp });
972
+ this.pushInstruction({
973
+ op: "struct.get",
974
+ typeIndex: mapInfo.entryTypeIndex,
975
+ fieldIndex: 5,
976
+ });
977
+ this.pushInstruction({ op: "i32.const", value: 1 });
978
+ this.pushInstruction("i32.eq");
979
+ this.pushInstruction({ op: "if", blockType: "void" });
980
+
981
+ this.pushInstruction({ op: "local.get", index: entryTmp });
982
+ this.pushInstruction({
983
+ op: "struct.get",
984
+ typeIndex: mapInfo.entryTypeIndex,
985
+ fieldIndex: 0,
986
+ });
987
+ this.pushInstruction({ op: "local.set", index: keyTmp });
988
+
989
+ this.pushInstruction({ op: "local.get", index: entryTmp });
990
+ this.pushInstruction({
991
+ op: "struct.get",
992
+ typeIndex: mapInfo.entryTypeIndex,
993
+ fieldIndex: 1,
994
+ });
995
+ this.pushInstruction({ op: "local.set", index: valTmp });
996
+
997
+ // call delFn(key, val)
998
+ this.pushInstruction({ op: "local.get", index: closureTmp });
999
+ this.pushInstruction({
1000
+ op: "struct.get",
1001
+ typeIndex: closureInfo.typeIndex,
1002
+ fieldIndex: 1,
1003
+ });
1004
+ this.pushInstruction({ op: "local.get", index: keyTmp });
1005
+ this.pushInstruction({ op: "local.get", index: valTmp });
1006
+ this.pushInstruction({ op: "local.get", index: closureTmp });
1007
+ this.pushInstruction({
1008
+ op: "struct.get",
1009
+ typeIndex: closureInfo.typeIndex,
1010
+ fieldIndex: 0,
1011
+ });
1012
+ this.pushInstruction({
1013
+ op: "call_ref",
1014
+ typeIndex: closureInfo.funcTypeIndex,
1015
+ });
1016
+
1017
+ // if delFn returned true -> delete(m, key)
1018
+ this.pushInstruction({ op: "if", blockType: "void" });
1019
+ this.pushInstruction({ op: "local.get", index: mTmp });
1020
+ this.pushInstruction({ op: "local.get", index: keyTmp });
1021
+ this.pushInstruction({
1022
+ op: "call",
1023
+ funcIndex: this.mod.resolveFuncIndex(mapInfo.deleteFuncName),
1024
+ });
1025
+ this.pushInstruction("end");
1026
+
1027
+ this.pushInstruction("end"); // end if active
1028
+
1029
+ this.pushInstruction({ op: "local.get", index: nextTmp });
1030
+ this.pushInstruction({ op: "local.set", index: idxTmp });
1031
+ this.pushInstruction({ op: "br", depth: 0 });
1032
+ this.pushInstruction("end");
1033
+ this.pushInstruction("end");
1034
+
1035
+ this.releaseTemp(closureTmp, {
1036
+ kind: "ref",
1037
+ nullable: true,
1038
+ typeIndex: closureInfo.typeIndex,
1039
+ });
1040
+ this.releaseTemp(valTmp, mapInfo.valWType);
1041
+ this.releaseTemp(keyTmp, mapInfo.keyWType);
1042
+ this.releaseTemp(entryTmp, {
1043
+ kind: "ref",
1044
+ nullable: true,
1045
+ typeIndex: mapInfo.entryTypeIndex,
1046
+ });
1047
+ this.releaseTemp(nextTmp, "i32");
1048
+ this.releaseTemp(idxTmp, "i32");
1049
+
1050
+ this.pushInstruction("end");
1051
+ this.releaseTemp(mTmp, mapInfo.wType);
1052
+ }
1053
+
1054
+ emitSlicesCall(name, args, targetWasmType) {
1055
+ switch (name) {
1056
+ case "Sort":
1057
+ this.emitSlicesSort(args);
1058
+ return;
1059
+ case "Reverse":
1060
+ this.emitSlicesReverse(args);
1061
+ return;
1062
+ case "Contains":
1063
+ this.emitSlicesContains(args);
1064
+ return;
1065
+ case "Index":
1066
+ this.emitSlicesIndex(args, targetWasmType);
1067
+ return;
1068
+ case "Equal":
1069
+ this.emitSlicesEqual(args);
1070
+ return;
1071
+ case "Clone":
1072
+ this.emitSlicesClone(args);
1073
+ return;
1074
+ default:
1075
+ throw new Error(`Unsupported slices function: slices.${name}`);
1076
+ }
1077
+ }
1078
+
1079
+ emitSlicesSort(args) {
1080
+ const sNode = args[0];
1081
+ const sType = sNode._type ?? this._resolveExprGoType(sNode);
1082
+ const elemGoType = this._getSliceElemType(sType);
1083
+ const sliceInfo = this.mod.getSliceType(elemGoType);
1084
+ const arrInfo = sliceInfo.arrInfo;
1085
+
1086
+ const sliceTmp = this.acquireTemp({
1087
+ kind: "ref",
1088
+ nullable: true,
1089
+ typeIndex: sliceInfo.typeIndex,
1090
+ });
1091
+ this.emitExpr(sNode, {
1092
+ kind: "ref",
1093
+ nullable: true,
1094
+ typeIndex: sliceInfo.typeIndex,
1095
+ });
1096
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
1097
+
1098
+ this.pushInstruction({ op: "block", blockType: "void" });
1099
+
1100
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1101
+ this.pushInstruction("ref.is_null");
1102
+ this.pushInstruction({ op: "br_if", depth: 0 });
1103
+
1104
+ const arrTmp = this.acquireTemp({
1105
+ kind: "ref",
1106
+ nullable: true,
1107
+ typeIndex: arrInfo.typeIndex,
1108
+ });
1109
+ const offTmp = this.acquireTemp("i32");
1110
+ const lenTmp = this.acquireTemp("i32");
1111
+
1112
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
1113
+ arr: arrTmp,
1114
+ off: offTmp,
1115
+ len: lenTmp,
1116
+ });
1117
+
1118
+ // if len <= 1 -> br 0
1119
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1120
+ this.pushInstruction({ op: "i32.const", value: 1 });
1121
+ this.pushInstruction("i32.le_s");
1122
+ this.pushInstruction({ op: "br_if", depth: 0 });
1123
+
1124
+ const iLoc = this.acquireTemp("i32");
1125
+ const jLoc = this.acquireTemp("i32");
1126
+ const keyLoc = this.acquireTemp(arrInfo.elemWType);
1127
+ const testLoc = this.acquireTemp(arrInfo.elemWType);
1128
+
1129
+ // i = 1
1130
+ this.pushInstruction({ op: "i32.const", value: 1 });
1131
+ this.pushInstruction({ op: "local.set", index: iLoc });
1132
+
1133
+ // Outer loop
1134
+ this.pushInstruction({ op: "loop", blockType: "void" });
1135
+
1136
+ // key = arr[off + i]
1137
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1138
+ this.pushInstruction({ op: "local.get", index: offTmp });
1139
+ this.pushInstruction({ op: "local.get", index: iLoc });
1140
+ this.pushInstruction("i32.add");
1141
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1142
+ this.pushInstruction({ op: "local.set", index: keyLoc });
1143
+
1144
+ // j = i - 1
1145
+ this.pushInstruction({ op: "local.get", index: iLoc });
1146
+ this.pushInstruction({ op: "i32.const", value: 1 });
1147
+ this.pushInstruction("i32.sub");
1148
+ this.pushInstruction({ op: "local.set", index: jLoc });
1149
+
1150
+ // Inner loop
1151
+ this.pushInstruction({ op: "block", blockType: "void" });
1152
+ this.pushInstruction({ op: "loop", blockType: "void" });
1153
+
1154
+ // if j < 0 -> break inner loop
1155
+ this.pushInstruction({ op: "local.get", index: jLoc });
1156
+ this.pushInstruction({ op: "i32.const", value: 0 });
1157
+ this.pushInstruction("i32.lt_s");
1158
+ this.pushInstruction({ op: "br_if", depth: 1 });
1159
+
1160
+ // test = arr[off + j]
1161
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1162
+ this.pushInstruction({ op: "local.get", index: offTmp });
1163
+ this.pushInstruction({ op: "local.get", index: jLoc });
1164
+ this.pushInstruction("i32.add");
1165
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1166
+ this.pushInstruction({ op: "local.set", index: testLoc });
1167
+
1168
+ // if !(key < test) -> break inner loop
1169
+ this.emitElemLt(keyLoc, testLoc, elemGoType);
1170
+ this.pushInstruction("i32.eqz");
1171
+ this.pushInstruction({ op: "br_if", depth: 1 });
1172
+
1173
+ // arr[off + j + 1] = test
1174
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1175
+ this.pushInstruction({ op: "local.get", index: offTmp });
1176
+ this.pushInstruction({ op: "local.get", index: jLoc });
1177
+ this.pushInstruction("i32.add");
1178
+ this.pushInstruction({ op: "i32.const", value: 1 });
1179
+ this.pushInstruction("i32.add");
1180
+ this.pushInstruction({ op: "local.get", index: testLoc });
1181
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1182
+
1183
+ // j--
1184
+ this.pushInstruction({ op: "local.get", index: jLoc });
1185
+ this.pushInstruction({ op: "i32.const", value: 1 });
1186
+ this.pushInstruction("i32.sub");
1187
+ this.pushInstruction({ op: "local.set", index: jLoc });
1188
+
1189
+ this.pushInstruction({ op: "br", depth: 0 });
1190
+ this.pushInstruction("end");
1191
+ this.pushInstruction("end");
1192
+
1193
+ // arr[off + j + 1] = key
1194
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1195
+ this.pushInstruction({ op: "local.get", index: offTmp });
1196
+ this.pushInstruction({ op: "local.get", index: jLoc });
1197
+ this.pushInstruction("i32.add");
1198
+ this.pushInstruction({ op: "i32.const", value: 1 });
1199
+ this.pushInstruction("i32.add");
1200
+ this.pushInstruction({ op: "local.get", index: keyLoc });
1201
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1202
+
1203
+ // i++
1204
+ this.pushInstruction({ op: "local.get", index: iLoc });
1205
+ this.pushInstruction({ op: "i32.const", value: 1 });
1206
+ this.pushInstruction("i32.add");
1207
+ this.pushInstruction({ op: "local.tee", index: iLoc });
1208
+
1209
+ // if i < len -> continue outer loop
1210
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1211
+ this.pushInstruction("i32.lt_s");
1212
+ this.pushInstruction({ op: "br_if", depth: 0 });
1213
+
1214
+ this.pushInstruction("end"); // end outer loop
1215
+
1216
+ this.releaseTemp(testLoc, arrInfo.elemWType);
1217
+ this.releaseTemp(keyLoc, arrInfo.elemWType);
1218
+ this.releaseTemp(jLoc, "i32");
1219
+ this.releaseTemp(iLoc, "i32");
1220
+ this.releaseTemp(lenTmp, "i32");
1221
+ this.releaseTemp(offTmp, "i32");
1222
+ this.releaseTemp(arrTmp, {
1223
+ kind: "ref",
1224
+ nullable: true,
1225
+ typeIndex: arrInfo.typeIndex,
1226
+ });
1227
+
1228
+ this.pushInstruction("end"); // end exit block
1229
+ this.releaseTemp(sliceTmp, {
1230
+ kind: "ref",
1231
+ nullable: true,
1232
+ typeIndex: sliceInfo.typeIndex,
1233
+ });
1234
+ }
1235
+
1236
+ emitSlicesReverse(args) {
1237
+ const sNode = args[0];
1238
+ const sType = sNode._type ?? this._resolveExprGoType(sNode);
1239
+ const elemGoType = this._getSliceElemType(sType);
1240
+ const sliceInfo = this.mod.getSliceType(elemGoType);
1241
+ const arrInfo = sliceInfo.arrInfo;
1242
+
1243
+ const sliceTmp = this.acquireTemp({
1244
+ kind: "ref",
1245
+ nullable: true,
1246
+ typeIndex: sliceInfo.typeIndex,
1247
+ });
1248
+ this.emitExpr(sNode, {
1249
+ kind: "ref",
1250
+ nullable: true,
1251
+ typeIndex: sliceInfo.typeIndex,
1252
+ });
1253
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
1254
+
1255
+ this.pushInstruction({ op: "block", blockType: "void" });
1256
+
1257
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1258
+ this.pushInstruction("ref.is_null");
1259
+ this.pushInstruction({ op: "br_if", depth: 0 });
1260
+
1261
+ const arrTmp = this.acquireTemp({
1262
+ kind: "ref",
1263
+ nullable: true,
1264
+ typeIndex: arrInfo.typeIndex,
1265
+ });
1266
+ const offTmp = this.acquireTemp("i32");
1267
+ const lenTmp = this.acquireTemp("i32");
1268
+
1269
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
1270
+ arr: arrTmp,
1271
+ off: offTmp,
1272
+ len: lenTmp,
1273
+ });
1274
+
1275
+ const iLoc = this.acquireTemp("i32");
1276
+ const jLoc = this.acquireTemp("i32");
1277
+ const tmp1 = this.acquireTemp(arrInfo.elemWType);
1278
+ const tmp2 = this.acquireTemp(arrInfo.elemWType);
1279
+
1280
+ // i = 0, j = len - 1
1281
+ this.pushInstruction({ op: "i32.const", value: 0 });
1282
+ this.pushInstruction({ op: "local.set", index: iLoc });
1283
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1284
+ this.pushInstruction({ op: "i32.const", value: 1 });
1285
+ this.pushInstruction("i32.sub");
1286
+ this.pushInstruction({ op: "local.set", index: jLoc });
1287
+
1288
+ this.pushInstruction({ op: "block", blockType: "void" });
1289
+ this.pushInstruction({ op: "loop", blockType: "void" });
1290
+
1291
+ // if i >= j -> break
1292
+ this.pushInstruction({ op: "local.get", index: iLoc });
1293
+ this.pushInstruction({ op: "local.get", index: jLoc });
1294
+ this.pushInstruction("i32.ge_s");
1295
+ this.pushInstruction({ op: "br_if", depth: 1 });
1296
+
1297
+ // tmp1 = arr[off + i]
1298
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1299
+ this.pushInstruction({ op: "local.get", index: offTmp });
1300
+ this.pushInstruction({ op: "local.get", index: iLoc });
1301
+ this.pushInstruction("i32.add");
1302
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1303
+ this.pushInstruction({ op: "local.set", index: tmp1 });
1304
+
1305
+ // tmp2 = arr[off + j]
1306
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1307
+ this.pushInstruction({ op: "local.get", index: offTmp });
1308
+ this.pushInstruction({ op: "local.get", index: jLoc });
1309
+ this.pushInstruction("i32.add");
1310
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1311
+ this.pushInstruction({ op: "local.set", index: tmp2 });
1312
+
1313
+ // arr[off + i] = tmp2
1314
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1315
+ this.pushInstruction({ op: "local.get", index: offTmp });
1316
+ this.pushInstruction({ op: "local.get", index: iLoc });
1317
+ this.pushInstruction("i32.add");
1318
+ this.pushInstruction({ op: "local.get", index: tmp2 });
1319
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1320
+
1321
+ // arr[off + j] = tmp1
1322
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1323
+ this.pushInstruction({ op: "local.get", index: offTmp });
1324
+ this.pushInstruction({ op: "local.get", index: jLoc });
1325
+ this.pushInstruction("i32.add");
1326
+ this.pushInstruction({ op: "local.get", index: tmp1 });
1327
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
1328
+
1329
+ // i++, j--
1330
+ this.pushInstruction({ op: "local.get", index: iLoc });
1331
+ this.pushInstruction({ op: "i32.const", value: 1 });
1332
+ this.pushInstruction("i32.add");
1333
+ this.pushInstruction({ op: "local.set", index: iLoc });
1334
+
1335
+ this.pushInstruction({ op: "local.get", index: jLoc });
1336
+ this.pushInstruction({ op: "i32.const", value: 1 });
1337
+ this.pushInstruction("i32.sub");
1338
+ this.pushInstruction({ op: "local.set", index: jLoc });
1339
+
1340
+ this.pushInstruction({ op: "br", depth: 0 });
1341
+ this.pushInstruction("end");
1342
+ this.pushInstruction("end");
1343
+
1344
+ this.releaseTemp(tmp2, arrInfo.elemWType);
1345
+ this.releaseTemp(tmp1, arrInfo.elemWType);
1346
+ this.releaseTemp(jLoc, "i32");
1347
+ this.releaseTemp(iLoc, "i32");
1348
+ this.releaseTemp(lenTmp, "i32");
1349
+ this.releaseTemp(offTmp, "i32");
1350
+ this.releaseTemp(arrTmp, {
1351
+ kind: "ref",
1352
+ nullable: true,
1353
+ typeIndex: arrInfo.typeIndex,
1354
+ });
1355
+
1356
+ this.pushInstruction("end");
1357
+ this.releaseTemp(sliceTmp, {
1358
+ kind: "ref",
1359
+ nullable: true,
1360
+ typeIndex: sliceInfo.typeIndex,
1361
+ });
1362
+ }
1363
+
1364
+ emitSlicesContains(args) {
1365
+ const sNode = args[0];
1366
+ const vNode = args[1];
1367
+ const sType = sNode._type ?? this._resolveExprGoType(sNode);
1368
+ const elemGoType = this._getSliceElemType(sType);
1369
+ const sliceInfo = this.mod.getSliceType(elemGoType);
1370
+ const arrInfo = sliceInfo.arrInfo;
1371
+
1372
+ const sliceTmp = this.acquireTemp({
1373
+ kind: "ref",
1374
+ nullable: true,
1375
+ typeIndex: sliceInfo.typeIndex,
1376
+ });
1377
+ const vTmp = this.acquireTemp(arrInfo.elemWType);
1378
+ this.emitExpr(sNode, {
1379
+ kind: "ref",
1380
+ nullable: true,
1381
+ typeIndex: sliceInfo.typeIndex,
1382
+ });
1383
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
1384
+ this.emitExpr(vNode, arrInfo.elemWType);
1385
+ this.pushInstruction({ op: "local.set", index: vTmp });
1386
+
1387
+ const resTmp = this.acquireTemp("i32");
1388
+ this.pushInstruction({ op: "i32.const", value: 0 });
1389
+ this.pushInstruction({ op: "local.set", index: resTmp });
1390
+
1391
+ this.pushInstruction({ op: "block", blockType: "void" });
1392
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1393
+ this.pushInstruction("ref.is_null");
1394
+ this.pushInstruction({ op: "br_if", depth: 0 });
1395
+
1396
+ const arrTmp = this.acquireTemp({
1397
+ kind: "ref",
1398
+ nullable: true,
1399
+ typeIndex: arrInfo.typeIndex,
1400
+ });
1401
+ const offTmp = this.acquireTemp("i32");
1402
+ const lenTmp = this.acquireTemp("i32");
1403
+ const iLoc = this.acquireTemp("i32");
1404
+ const itemTmp = this.acquireTemp(arrInfo.elemWType);
1405
+
1406
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
1407
+ arr: arrTmp,
1408
+ off: offTmp,
1409
+ len: lenTmp,
1410
+ });
1411
+
1412
+ this.pushInstruction({ op: "i32.const", value: 0 });
1413
+ this.pushInstruction({ op: "local.set", index: iLoc });
1414
+
1415
+ this.pushInstruction({ op: "block", blockType: "void" });
1416
+ this.pushInstruction({ op: "loop", blockType: "void" });
1417
+ this.pushInstruction({ op: "local.get", index: iLoc });
1418
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1419
+ this.pushInstruction("i32.ge_s");
1420
+ this.pushInstruction({ op: "br_if", depth: 1 });
1421
+
1422
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1423
+ this.pushInstruction({ op: "local.get", index: offTmp });
1424
+ this.pushInstruction({ op: "local.get", index: iLoc });
1425
+ this.pushInstruction("i32.add");
1426
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1427
+ this.pushInstruction({ op: "local.set", index: itemTmp });
1428
+
1429
+ this.emitKeyEq(itemTmp, vTmp, elemGoType);
1430
+ this.pushInstruction({ op: "if", blockType: "void" });
1431
+ this.pushInstruction({ op: "i32.const", value: 1 });
1432
+ this.pushInstruction({ op: "local.set", index: resTmp });
1433
+ this.pushInstruction({ op: "br", depth: 2 });
1434
+ this.pushInstruction("end");
1435
+
1436
+ this.pushInstruction({ op: "local.get", index: iLoc });
1437
+ this.pushInstruction({ op: "i32.const", value: 1 });
1438
+ this.pushInstruction("i32.add");
1439
+ this.pushInstruction({ op: "local.set", index: iLoc });
1440
+ this.pushInstruction({ op: "br", depth: 0 });
1441
+ this.pushInstruction("end");
1442
+ this.pushInstruction("end");
1443
+
1444
+ this.releaseTemp(itemTmp, arrInfo.elemWType);
1445
+ this.releaseTemp(iLoc, "i32");
1446
+ this.releaseTemp(lenTmp, "i32");
1447
+ this.releaseTemp(offTmp, "i32");
1448
+ this.releaseTemp(arrTmp, {
1449
+ kind: "ref",
1450
+ nullable: true,
1451
+ typeIndex: arrInfo.typeIndex,
1452
+ });
1453
+
1454
+ this.pushInstruction("end"); // end exit block
1455
+ this.pushInstruction({ op: "local.get", index: resTmp });
1456
+ this.releaseTemp(resTmp, "i32");
1457
+ this.releaseTemp(vTmp, arrInfo.elemWType);
1458
+ this.releaseTemp(sliceTmp, {
1459
+ kind: "ref",
1460
+ nullable: true,
1461
+ typeIndex: sliceInfo.typeIndex,
1462
+ });
1463
+ }
1464
+
1465
+ emitSlicesIndex(args, targetWasmType) {
1466
+ const sNode = args[0];
1467
+ const vNode = args[1];
1468
+ const sType = sNode._type ?? this._resolveExprGoType(sNode);
1469
+ const elemGoType = this._getSliceElemType(sType);
1470
+ const sliceInfo = this.mod.getSliceType(elemGoType);
1471
+ const arrInfo = sliceInfo.arrInfo;
1472
+
1473
+ const sliceTmp = this.acquireTemp({
1474
+ kind: "ref",
1475
+ nullable: true,
1476
+ typeIndex: sliceInfo.typeIndex,
1477
+ });
1478
+ const vTmp = this.acquireTemp(arrInfo.elemWType);
1479
+ this.emitExpr(sNode, {
1480
+ kind: "ref",
1481
+ nullable: true,
1482
+ typeIndex: sliceInfo.typeIndex,
1483
+ });
1484
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
1485
+ this.emitExpr(vNode, arrInfo.elemWType);
1486
+ this.pushInstruction({ op: "local.set", index: vTmp });
1487
+
1488
+ const resLoc = this.acquireTemp("i32");
1489
+ this.pushInstruction({ op: "i32.const", value: -1 });
1490
+ this.pushInstruction({ op: "local.set", index: resLoc });
1491
+
1492
+ this.pushInstruction({ op: "block", blockType: "void" });
1493
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1494
+ this.pushInstruction("ref.is_null");
1495
+ this.pushInstruction({ op: "br_if", depth: 0 });
1496
+
1497
+ const arrTmp = this.acquireTemp({
1498
+ kind: "ref",
1499
+ nullable: true,
1500
+ typeIndex: arrInfo.typeIndex,
1501
+ });
1502
+ const offTmp = this.acquireTemp("i32");
1503
+ const lenTmp = this.acquireTemp("i32");
1504
+ const iLoc = this.acquireTemp("i32");
1505
+ const itemTmp = this.acquireTemp(arrInfo.elemWType);
1506
+
1507
+ this._emitSliceUnpack(sliceTmp, sliceInfo, {
1508
+ arr: arrTmp,
1509
+ off: offTmp,
1510
+ len: lenTmp,
1511
+ });
1512
+
1513
+ this.pushInstruction({ op: "i32.const", value: 0 });
1514
+ this.pushInstruction({ op: "local.set", index: iLoc });
1515
+
1516
+ this.pushInstruction({ op: "block", blockType: "void" });
1517
+ this.pushInstruction({ op: "loop", blockType: "void" });
1518
+ this.pushInstruction({ op: "local.get", index: iLoc });
1519
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1520
+ this.pushInstruction("i32.ge_s");
1521
+ this.pushInstruction({ op: "br_if", depth: 1 });
1522
+
1523
+ this.pushInstruction({ op: "local.get", index: arrTmp });
1524
+ this.pushInstruction({ op: "local.get", index: offTmp });
1525
+ this.pushInstruction({ op: "local.get", index: iLoc });
1526
+ this.pushInstruction("i32.add");
1527
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1528
+ this.pushInstruction({ op: "local.set", index: itemTmp });
1529
+
1530
+ this.emitKeyEq(itemTmp, vTmp, elemGoType);
1531
+ this.pushInstruction({ op: "if", blockType: "void" });
1532
+ this.pushInstruction({ op: "local.get", index: iLoc });
1533
+ this.pushInstruction({ op: "local.set", index: resLoc });
1534
+ this.pushInstruction({ op: "br", depth: 2 });
1535
+ this.pushInstruction("end");
1536
+
1537
+ this.pushInstruction({ op: "local.get", index: iLoc });
1538
+ this.pushInstruction({ op: "i32.const", value: 1 });
1539
+ this.pushInstruction("i32.add");
1540
+ this.pushInstruction({ op: "local.set", index: iLoc });
1541
+ this.pushInstruction({ op: "br", depth: 0 });
1542
+ this.pushInstruction("end");
1543
+ this.pushInstruction("end");
1544
+
1545
+ this.releaseTemp(itemTmp, arrInfo.elemWType);
1546
+ this.releaseTemp(iLoc, "i32");
1547
+ this.releaseTemp(lenTmp, "i32");
1548
+ this.releaseTemp(offTmp, "i32");
1549
+ this.releaseTemp(arrTmp, {
1550
+ kind: "ref",
1551
+ nullable: true,
1552
+ typeIndex: arrInfo.typeIndex,
1553
+ });
1554
+
1555
+ this.pushInstruction("end"); // end exit block
1556
+ this.pushInstruction({ op: "local.get", index: resLoc });
1557
+ this.releaseTemp(resLoc, "i32");
1558
+ this.releaseTemp(vTmp, arrInfo.elemWType);
1559
+ this.releaseTemp(sliceTmp, {
1560
+ kind: "ref",
1561
+ nullable: true,
1562
+ typeIndex: sliceInfo.typeIndex,
1563
+ });
1564
+
1565
+ if ((targetWasmType ?? "i64") === "i64") {
1566
+ this.pushInstruction("i64.extend_i32_s");
1567
+ }
1568
+ }
1569
+
1570
+ emitSlicesEqual(args) {
1571
+ const s1Node = args[0];
1572
+ const s2Node = args[1];
1573
+ const sType = s1Node._type ?? this._resolveExprGoType(s1Node);
1574
+ const elemGoType = this._getSliceElemType(sType);
1575
+ const sliceInfo = this.mod.getSliceType(elemGoType);
1576
+ const arrInfo = sliceInfo.arrInfo;
1577
+
1578
+ const s1Tmp = this.acquireTemp({
1579
+ kind: "ref",
1580
+ nullable: true,
1581
+ typeIndex: sliceInfo.typeIndex,
1582
+ });
1583
+ const s2Tmp = this.acquireTemp({
1584
+ kind: "ref",
1585
+ nullable: true,
1586
+ typeIndex: sliceInfo.typeIndex,
1587
+ });
1588
+ this.emitExpr(s1Node, {
1589
+ kind: "ref",
1590
+ nullable: true,
1591
+ typeIndex: sliceInfo.typeIndex,
1592
+ });
1593
+ this.pushInstruction({ op: "local.set", index: s1Tmp });
1594
+ this.emitExpr(s2Node, {
1595
+ kind: "ref",
1596
+ nullable: true,
1597
+ typeIndex: sliceInfo.typeIndex,
1598
+ });
1599
+ this.pushInstruction({ op: "local.set", index: s2Tmp });
1600
+
1601
+ const resLoc = this.acquireTemp("i32");
1602
+ this.pushInstruction({ op: "i32.const", value: 0 });
1603
+ this.pushInstruction({ op: "local.set", index: resLoc });
1604
+
1605
+ this.pushInstruction({ op: "block", blockType: "void" });
1606
+
1607
+ // if s1 == s2 -> 1
1608
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1609
+ this.pushInstruction({ op: "local.get", index: s2Tmp });
1610
+ this.pushInstruction("ref.eq");
1611
+ this.pushInstruction({ op: "if", blockType: "void" });
1612
+ this.pushInstruction({ op: "i32.const", value: 1 });
1613
+ this.pushInstruction({ op: "local.set", index: resLoc });
1614
+ this.pushInstruction({ op: "br", depth: 1 });
1615
+ this.pushInstruction("end");
1616
+
1617
+ // if s1 == null || s2 == null -> 0 (br 0)
1618
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1619
+ this.pushInstruction("ref.is_null");
1620
+ this.pushInstruction({ op: "local.get", index: s2Tmp });
1621
+ this.pushInstruction("ref.is_null");
1622
+ this.pushInstruction("i32.or");
1623
+ this.pushInstruction({ op: "br_if", depth: 0 });
1624
+
1625
+ // if s1.len != s2.len -> 0 (br 0)
1626
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1627
+ this.pushInstruction({
1628
+ op: "struct.get",
1629
+ typeIndex: sliceInfo.typeIndex,
1630
+ fieldIndex: 2,
1631
+ });
1632
+ this.pushInstruction({ op: "local.get", index: s2Tmp });
1633
+ this.pushInstruction({
1634
+ op: "struct.get",
1635
+ typeIndex: sliceInfo.typeIndex,
1636
+ fieldIndex: 2,
1637
+ });
1638
+ this.pushInstruction("i32.ne");
1639
+ this.pushInstruction({ op: "br_if", depth: 0 });
1640
+
1641
+ const lenLoc = this.acquireTemp("i32");
1642
+ const iLoc = this.acquireTemp("i32");
1643
+ const item1 = this.acquireTemp(arrInfo.elemWType);
1644
+ const item2 = this.acquireTemp(arrInfo.elemWType);
1645
+
1646
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1647
+ this.pushInstruction({
1648
+ op: "struct.get",
1649
+ typeIndex: sliceInfo.typeIndex,
1650
+ fieldIndex: 2,
1651
+ });
1652
+ this.pushInstruction({ op: "local.set", index: lenLoc });
1653
+
1654
+ this.pushInstruction({ op: "i32.const", value: 0 });
1655
+ this.pushInstruction({ op: "local.set", index: iLoc });
1656
+
1657
+ this.pushInstruction({ op: "block", blockType: "void" });
1658
+ this.pushInstruction({ op: "loop", blockType: "void" });
1659
+ this.pushInstruction({ op: "local.get", index: iLoc });
1660
+ this.pushInstruction({ op: "local.get", index: lenLoc });
1661
+ this.pushInstruction("i32.ge_s");
1662
+ this.pushInstruction({ op: "br_if", depth: 1 });
1663
+
1664
+ // item1 = s1.arr[s1.off + i]
1665
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1666
+ this.pushInstruction({
1667
+ op: "struct.get",
1668
+ typeIndex: sliceInfo.typeIndex,
1669
+ fieldIndex: 0,
1670
+ });
1671
+ this.pushInstruction({ op: "local.get", index: s1Tmp });
1672
+ this.pushInstruction({
1673
+ op: "struct.get",
1674
+ typeIndex: sliceInfo.typeIndex,
1675
+ fieldIndex: 1,
1676
+ });
1677
+ this.pushInstruction({ op: "local.get", index: iLoc });
1678
+ this.pushInstruction("i32.add");
1679
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1680
+ this.pushInstruction({ op: "local.set", index: item1 });
1681
+
1682
+ // item2 = s2.arr[s2.off + i]
1683
+ this.pushInstruction({ op: "local.get", index: s2Tmp });
1684
+ this.pushInstruction({
1685
+ op: "struct.get",
1686
+ typeIndex: sliceInfo.typeIndex,
1687
+ fieldIndex: 0,
1688
+ });
1689
+ this.pushInstruction({ op: "local.get", index: s2Tmp });
1690
+ this.pushInstruction({
1691
+ op: "struct.get",
1692
+ typeIndex: sliceInfo.typeIndex,
1693
+ fieldIndex: 1,
1694
+ });
1695
+ this.pushInstruction({ op: "local.get", index: iLoc });
1696
+ this.pushInstruction("i32.add");
1697
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
1698
+ this.pushInstruction({ op: "local.set", index: item2 });
1699
+
1700
+ this.emitKeyEq(item1, item2, elemGoType);
1701
+ this.pushInstruction("i32.eqz");
1702
+ this.pushInstruction({ op: "br_if", depth: 2 }); // mismatch -> res 0
1703
+
1704
+ this.pushInstruction({ op: "local.get", index: iLoc });
1705
+ this.pushInstruction({ op: "i32.const", value: 1 });
1706
+ this.pushInstruction("i32.add");
1707
+ this.pushInstruction({ op: "local.set", index: iLoc });
1708
+ this.pushInstruction({ op: "br", depth: 0 });
1709
+ this.pushInstruction("end");
1710
+ this.pushInstruction("end");
1711
+
1712
+ this.pushInstruction({ op: "i32.const", value: 1 });
1713
+ this.pushInstruction({ op: "local.set", index: resLoc });
1714
+
1715
+ this.releaseTemp(item2, arrInfo.elemWType);
1716
+ this.releaseTemp(item1, arrInfo.elemWType);
1717
+ this.releaseTemp(iLoc, "i32");
1718
+ this.releaseTemp(lenLoc, "i32");
1719
+
1720
+ this.pushInstruction("end");
1721
+
1722
+ this.pushInstruction({ op: "local.get", index: resLoc });
1723
+ this.releaseTemp(resLoc, "i32");
1724
+ this.releaseTemp(s2Tmp, {
1725
+ kind: "ref",
1726
+ nullable: true,
1727
+ typeIndex: sliceInfo.typeIndex,
1728
+ });
1729
+ this.releaseTemp(s1Tmp, {
1730
+ kind: "ref",
1731
+ nullable: true,
1732
+ typeIndex: sliceInfo.typeIndex,
1733
+ });
1734
+ }
1735
+
1736
+ emitSlicesClone(args) {
1737
+ const sNode = args[0];
1738
+ const sType = sNode._type ?? this._resolveExprGoType(sNode);
1739
+ const elemGoType = this._getSliceElemType(sType);
1740
+ const sliceInfo = this.mod.getSliceType(elemGoType);
1741
+ const arrInfo = sliceInfo.arrInfo;
1742
+
1743
+ const sliceTmp = this.acquireTemp({
1744
+ kind: "ref",
1745
+ nullable: true,
1746
+ typeIndex: sliceInfo.typeIndex,
1747
+ });
1748
+ this.emitExpr(sNode, {
1749
+ kind: "ref",
1750
+ nullable: true,
1751
+ typeIndex: sliceInfo.typeIndex,
1752
+ });
1753
+ this.pushInstruction({ op: "local.set", index: sliceTmp });
1754
+
1755
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1756
+ this.pushInstruction("ref.is_null");
1757
+ this.pushInstruction({
1758
+ op: "if",
1759
+ blockType: {
1760
+ kind: "ref",
1761
+ nullable: true,
1762
+ typeIndex: sliceInfo.typeIndex,
1763
+ },
1764
+ });
1765
+ this.pushInstruction({ op: "ref.null", heapType: sliceInfo.typeIndex });
1766
+ this.pushInstruction("else");
1767
+
1768
+ const lenTmp = this.acquireTemp("i32");
1769
+ const newArrTmp = this.acquireTemp({
1770
+ kind: "ref",
1771
+ nullable: true,
1772
+ typeIndex: arrInfo.typeIndex,
1773
+ });
1774
+
1775
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1776
+ this.pushInstruction({
1777
+ op: "struct.get",
1778
+ typeIndex: sliceInfo.typeIndex,
1779
+ fieldIndex: 2,
1780
+ });
1781
+ this.pushInstruction({ op: "local.tee", index: lenTmp });
1782
+ this.pushInstruction({
1783
+ op: "array.new_default",
1784
+ typeIndex: arrInfo.typeIndex,
1785
+ });
1786
+ this.pushInstruction({ op: "local.set", index: newArrTmp });
1787
+
1788
+ // array.copy (newArr, 0, srcArr, srcOff, len)
1789
+ this.pushInstruction({ op: "local.get", index: newArrTmp });
1790
+ this.pushInstruction({ op: "i32.const", value: 0 });
1791
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1792
+ this.pushInstruction({
1793
+ op: "struct.get",
1794
+ typeIndex: sliceInfo.typeIndex,
1795
+ fieldIndex: 0,
1796
+ });
1797
+ this.pushInstruction({ op: "local.get", index: sliceTmp });
1798
+ this.pushInstruction({
1799
+ op: "struct.get",
1800
+ typeIndex: sliceInfo.typeIndex,
1801
+ fieldIndex: 1,
1802
+ });
1803
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1804
+ this._emitArrayCopy(arrInfo, elemGoType);
1805
+
1806
+ // struct.new slice (newArr, 0, len, len)
1807
+ this.pushInstruction({ op: "local.get", index: newArrTmp });
1808
+ this.pushInstruction({ op: "i32.const", value: 0 });
1809
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1810
+ this.pushInstruction({ op: "local.get", index: lenTmp });
1811
+ this.pushInstruction({
1812
+ op: "struct.new",
1813
+ typeIndex: sliceInfo.typeIndex,
1814
+ });
1815
+
1816
+ this.releaseTemp(newArrTmp, {
1817
+ kind: "ref",
1818
+ nullable: true,
1819
+ typeIndex: arrInfo.typeIndex,
1820
+ });
1821
+ this.releaseTemp(lenTmp, "i32");
1822
+ this.pushInstruction("end"); // end if
1823
+
1824
+ this.releaseTemp(sliceTmp, {
1825
+ kind: "ref",
1826
+ nullable: true,
1827
+ typeIndex: sliceInfo.typeIndex,
1828
+ });
1829
+ }
1830
+
1831
+ emitStringsCall(name, args, targetWasmType) {
1832
+ switch (name) {
1833
+ case "ToUpper": {
1834
+ this.emitExpr(args[0], "externref");
1835
+ this.pushInstruction({
1836
+ op: "call",
1837
+ funcIndex: this.mod.getStringToUpperImportIndex(),
1838
+ });
1839
+ return;
1840
+ }
1841
+ case "ToLower": {
1842
+ this.emitExpr(args[0], "externref");
1843
+ this.pushInstruction({
1844
+ op: "call",
1845
+ funcIndex: this.mod.getStringToLowerImportIndex(),
1846
+ });
1847
+ return;
1848
+ }
1849
+ case "TrimSpace": {
1850
+ this.emitExpr(args[0], "externref");
1851
+ this.pushInstruction({
1852
+ op: "call",
1853
+ funcIndex: this.mod.getStringTrimSpaceImportIndex(),
1854
+ });
1855
+ return;
1856
+ }
1857
+ case "Contains": {
1858
+ this.emitExpr(args[0], "externref");
1859
+ this.emitExpr(args[1], "externref");
1860
+ this.pushInstruction({
1861
+ op: "call",
1862
+ funcIndex: this.mod.getStringContainsImportIndex(),
1863
+ });
1864
+ return;
1865
+ }
1866
+ case "HasPrefix": {
1867
+ this.emitExpr(args[0], "externref");
1868
+ this.emitExpr(args[1], "externref");
1869
+ this.pushInstruction({
1870
+ op: "call",
1871
+ funcIndex: this.mod.getStringHasPrefixImportIndex(),
1872
+ });
1873
+ return;
1874
+ }
1875
+ case "HasSuffix": {
1876
+ this.emitExpr(args[0], "externref");
1877
+ this.emitExpr(args[1], "externref");
1878
+ this.pushInstruction({
1879
+ op: "call",
1880
+ funcIndex: this.mod.getStringHasSuffixImportIndex(),
1881
+ });
1882
+ return;
1883
+ }
1884
+ case "Index": {
1885
+ this.emitExpr(args[0], "externref");
1886
+ this.emitExpr(args[1], "externref");
1887
+ this.pushInstruction({
1888
+ op: "call",
1889
+ funcIndex: this.mod.getStringIndexImportIndex(),
1890
+ });
1891
+ if ((targetWasmType ?? "i64") === "i64") {
1892
+ this.pushInstruction("i64.extend_i32_s");
1893
+ }
1894
+ return;
1895
+ }
1896
+ case "LastIndex": {
1897
+ this.emitExpr(args[0], "externref");
1898
+ this.emitExpr(args[1], "externref");
1899
+ this.pushInstruction({
1900
+ op: "call",
1901
+ funcIndex: this.mod.getStringLastIndexImportIndex(),
1902
+ });
1903
+ if ((targetWasmType ?? "i64") === "i64") {
1904
+ this.pushInstruction("i64.extend_i32_s");
1905
+ }
1906
+ return;
1907
+ }
1908
+ case "Repeat": {
1909
+ this.emitExpr(args[0], "externref");
1910
+ const cntWType = toWasmType(args[1]?._type, this.mod.checker);
1911
+ if (cntWType === "i64") {
1912
+ this.emitExpr(args[1], "i64");
1913
+ this.pushInstruction("i32.wrap_i64");
1914
+ } else {
1915
+ this.emitExpr(args[1], "i32");
1916
+ }
1917
+ this.pushInstruction({
1918
+ op: "call",
1919
+ funcIndex: this.mod.getStringRepeatImportIndex(),
1920
+ });
1921
+ return;
1922
+ }
1923
+ case "ReplaceAll": {
1924
+ this.emitExpr(args[0], "externref");
1925
+ this.emitExpr(args[1], "externref");
1926
+ this.emitExpr(args[2], "externref");
1927
+ this.pushInstruction({
1928
+ op: "call",
1929
+ funcIndex: this.mod.getStringReplaceAllImportIndex(),
1930
+ });
1931
+ return;
1932
+ }
1933
+ case "EqualFold": {
1934
+ this.emitExpr(args[0], "externref");
1935
+ this.emitExpr(args[1], "externref");
1936
+ this.pushInstruction({
1937
+ op: "call",
1938
+ funcIndex: this.mod.getStringEqualFoldImportIndex(),
1939
+ });
1940
+ return;
1941
+ }
1942
+ case "Count": {
1943
+ this.emitExpr(args[0], "externref");
1944
+ this.emitExpr(args[1], "externref");
1945
+ this.pushInstruction({
1946
+ op: "call",
1947
+ funcIndex: this.mod.getStringCountImportIndex(),
1948
+ });
1949
+ if ((targetWasmType ?? "i64") === "i64") {
1950
+ this.pushInstruction("i64.extend_i32_s");
1951
+ }
1952
+ return;
1953
+ }
1954
+ case "Trim":
1955
+ case "TrimLeft":
1956
+ case "TrimRight":
1957
+ case "TrimPrefix":
1958
+ case "TrimSuffix":
1959
+ case "ContainsAny":
1960
+ case "IndexAny": {
1961
+ const imp = {
1962
+ Trim: "str_trim",
1963
+ TrimLeft: "str_trim_left",
1964
+ TrimRight: "str_trim_right",
1965
+ TrimPrefix: "str_trim_prefix",
1966
+ TrimSuffix: "str_trim_suffix",
1967
+ ContainsAny: "str_contains_any",
1968
+ IndexAny: "str_index_any",
1969
+ }[name];
1970
+ this.emitExpr(args[0], "externref");
1971
+ this.emitExpr(args[1], "externref");
1972
+ this._callStdlib(imp);
1973
+ if (name === "IndexAny" && (targetWasmType ?? "i64") === "i64") {
1974
+ this.pushInstruction("i64.extend_i32_s");
1975
+ }
1976
+ return;
1977
+ }
1978
+ case "ContainsRune":
1979
+ case "IndexByte":
1980
+ case "IndexRune":
1981
+ case "LastIndexByte": {
1982
+ const imp = {
1983
+ ContainsRune: "str_contains_rune",
1984
+ IndexByte: "str_index_byte",
1985
+ IndexRune: "str_index_rune",
1986
+ LastIndexByte: "str_last_index_byte",
1987
+ }[name];
1988
+ this.emitExpr(args[0], "externref");
1989
+ this._emitI32Arg(args[1]);
1990
+ this._callStdlib(imp);
1991
+ if (name !== "ContainsRune" && (targetWasmType ?? "i64") === "i64") {
1992
+ this.pushInstruction("i64.extend_i32_s");
1993
+ }
1994
+ return;
1995
+ }
1996
+ case "Title":
1997
+ case "ToTitle": {
1998
+ this.emitExpr(args[0], "externref");
1999
+ this._callStdlib(name === "Title" ? "str_title" : "str_to_upper");
2000
+ return;
2001
+ }
2002
+ case "Replace": {
2003
+ this.emitExpr(args[0], "externref");
2004
+ this.emitExpr(args[1], "externref");
2005
+ this.emitExpr(args[2], "externref");
2006
+ this._emitI32Arg(args[3]);
2007
+ this._callStdlib("str_replace");
2008
+ return;
2009
+ }
2010
+ case "Split":
2011
+ case "Fields": {
2012
+ // JS splits and parks the parts; wasm pulls them into a []string.
2013
+ this.emitExpr(args[0], "externref");
2014
+ if (name === "Split") {
2015
+ this.emitExpr(args[1], "externref");
2016
+ this._callStdlib("str_split");
2017
+ } else {
2018
+ this._callStdlib("str_fields");
2019
+ }
2020
+ this._emitCollectStringParts();
2021
+ return;
2022
+ }
2023
+ case "Join":
2024
+ this.emitStringsJoin(args);
2025
+ return;
2026
+ default:
2027
+ throw new Error(`Unsupported strings function: strings.${name}`);
2028
+ }
2029
+ }
2030
+
2031
+ _callStdlib(name) {
2032
+ this.pushInstruction({
2033
+ op: "call",
2034
+ funcIndex: this.mod.getStdlibImportIndex(name),
2035
+ });
2036
+ }
2037
+
2038
+ // Pushes an integer-ish argument as i32 (Go ints are i64 in this backend).
2039
+ _emitI32Arg(arg) {
2040
+ const wType = toWasmType(arg?._type, this.mod.checker);
2041
+ if (wType === "i64") {
2042
+ this.emitExpr(arg, "i64");
2043
+ this.pushInstruction("i32.wrap_i64");
2044
+ } else {
2045
+ this.emitExpr(arg, "i32");
2046
+ }
2047
+ }
2048
+
2049
+ // Consumes the part count (i32) left by str_split/str_fields and builds a
2050
+ // `[]string` by calling `str_part(i)` for each index.
2051
+ _emitCollectStringParts() {
2052
+ const strGo = { kind: "basic", name: "string" };
2053
+ const sliceInfo = this.mod.getSliceType(strGo);
2054
+ const arrInfo = sliceInfo.arrInfo;
2055
+ const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
2056
+ const n = this.acquireTemp("i32");
2057
+ const i = this.acquireTemp("i32");
2058
+ const arr = this.acquireTemp(arrW);
2059
+
2060
+ this.pushInstruction({ op: "local.tee", index: n });
2061
+ this.pushInstruction({
2062
+ op: "array.new_default",
2063
+ typeIndex: arrInfo.typeIndex,
2064
+ });
2065
+ this.pushInstruction({ op: "local.set", index: arr });
2066
+ this.pushInstruction({ op: "i32.const", value: 0 });
2067
+ this.pushInstruction({ op: "local.set", index: i });
2068
+
2069
+ this.pushInstruction({ op: "block", blockType: "void" });
2070
+ this.pushInstruction({ op: "loop", blockType: "void" });
2071
+ this.pushInstruction({ op: "local.get", index: i });
2072
+ this.pushInstruction({ op: "local.get", index: n });
2073
+ this.pushInstruction("i32.ge_s");
2074
+ this.pushInstruction({ op: "br_if", depth: 1 });
2075
+ this.pushInstruction({ op: "local.get", index: arr });
2076
+ this.pushInstruction({ op: "local.get", index: i });
2077
+ this.pushInstruction({ op: "local.get", index: i });
2078
+ this._callStdlib("str_part");
2079
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
2080
+ this.pushInstruction({ op: "local.get", index: i });
2081
+ this.pushInstruction({ op: "i32.const", value: 1 });
2082
+ this.pushInstruction("i32.add");
2083
+ this.pushInstruction({ op: "local.set", index: i });
2084
+ this.pushInstruction({ op: "br", depth: 0 });
2085
+ this.pushInstruction("end");
2086
+ this.pushInstruction("end");
2087
+
2088
+ this.pushInstruction({ op: "local.get", index: arr });
2089
+ this.pushInstruction({ op: "i32.const", value: 0 });
2090
+ this.pushInstruction({ op: "local.get", index: n });
2091
+ this.pushInstruction({ op: "local.get", index: n });
2092
+ this.pushInstruction({ op: "struct.new", typeIndex: sliceInfo.typeIndex });
2093
+
2094
+ this.releaseTemp(arr, arrW);
2095
+ this.releaseTemp(i, "i32");
2096
+ this.releaseTemp(n, "i32");
2097
+ }
2098
+
2099
+ emitStringsJoin(args) {
2100
+ const strGo = { kind: "basic", name: "string" };
2101
+ const sliceInfo = this.mod.getSliceType(strGo);
2102
+ const arrInfo = sliceInfo.arrInfo;
2103
+ const sliceW = {
2104
+ kind: "ref",
2105
+ nullable: true,
2106
+ typeIndex: sliceInfo.typeIndex,
2107
+ };
2108
+ const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
2109
+ const s = this.acquireTemp(sliceW);
2110
+ const arr = this.acquireTemp(arrW);
2111
+ const off = this.acquireTemp("i32");
2112
+ const len = this.acquireTemp("i32");
2113
+ const i = this.acquireTemp("i32");
2114
+ const sep = this.acquireTemp("externref");
2115
+ const acc = this.acquireTemp("externref");
2116
+ const concat = this.mod.getStringConcatImportIndex();
2117
+
2118
+ this.emitExpr(args[0], sliceW);
2119
+ this.pushInstruction({ op: "local.set", index: s });
2120
+ this.emitExpr(args[1], "externref");
2121
+ this.pushInstruction({ op: "local.set", index: sep });
2122
+ // acc = ""
2123
+ this.pushInstruction({ op: "i32.const", value: this.mod.internString("") });
2124
+ this.pushInstruction({
2125
+ op: "call",
2126
+ funcIndex: this.mod.getStringImportIndex(),
2127
+ });
2128
+ this.pushInstruction({ op: "local.set", index: acc });
2129
+
2130
+ this.pushInstruction({ op: "block", blockType: "void" });
2131
+ this.pushInstruction({ op: "local.get", index: s });
2132
+ this.pushInstruction("ref.is_null");
2133
+ this.pushInstruction({ op: "br_if", depth: 0 });
2134
+ this._emitSliceUnpack(s, sliceInfo, { arr, off, len });
2135
+ this.pushInstruction({ op: "i32.const", value: 0 });
2136
+ this.pushInstruction({ op: "local.set", index: i });
2137
+ this.pushInstruction({ op: "loop", blockType: "void" });
2138
+ this.pushInstruction({ op: "local.get", index: i });
2139
+ this.pushInstruction({ op: "local.get", index: len });
2140
+ this.pushInstruction("i32.ge_s");
2141
+ this.pushInstruction({ op: "br_if", depth: 1 });
2142
+ // if i > 0: acc = acc + sep
2143
+ this.pushInstruction({ op: "local.get", index: i });
2144
+ this.pushInstruction({ op: "if", blockType: "void" });
2145
+ this.pushInstruction({ op: "local.get", index: acc });
2146
+ this.pushInstruction({ op: "local.get", index: sep });
2147
+ this.pushInstruction({ op: "call", funcIndex: concat });
2148
+ this.pushInstruction({ op: "local.set", index: acc });
2149
+ this.pushInstruction("end");
2150
+ // acc = acc + arr[off+i]
2151
+ this.pushInstruction({ op: "local.get", index: acc });
2152
+ this.pushInstruction({ op: "local.get", index: arr });
2153
+ this.pushInstruction({ op: "local.get", index: off });
2154
+ this.pushInstruction({ op: "local.get", index: i });
2155
+ this.pushInstruction("i32.add");
2156
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
2157
+ this.pushInstruction({ op: "call", funcIndex: concat });
2158
+ this.pushInstruction({ op: "local.set", index: acc });
2159
+ this.pushInstruction({ op: "local.get", index: i });
2160
+ this.pushInstruction({ op: "i32.const", value: 1 });
2161
+ this.pushInstruction("i32.add");
2162
+ this.pushInstruction({ op: "local.set", index: i });
2163
+ this.pushInstruction({ op: "br", depth: 0 });
2164
+ this.pushInstruction("end");
2165
+ this.pushInstruction("end");
2166
+ this.pushInstruction({ op: "local.get", index: acc });
2167
+
2168
+ this.releaseTemp(acc, "externref");
2169
+ this.releaseTemp(sep, "externref");
2170
+ this.releaseTemp(i, "i32");
2171
+ this.releaseTemp(len, "i32");
2172
+ this.releaseTemp(off, "i32");
2173
+ this.releaseTemp(arr, arrW);
2174
+ this.releaseTemp(s, sliceW);
2175
+ }
2176
+
2177
+ emitStrconvCall(name, args) {
2178
+ switch (name) {
2179
+ case "Itoa": {
2180
+ const argWType = toWasmType(args[0]?._type, this.mod.checker);
2181
+ if (argWType === "i64") {
2182
+ this.emitExpr(args[0], "i64");
2183
+ this.pushInstruction({
2184
+ op: "call",
2185
+ funcIndex: this.mod.getStrFromI64ImportIndex(),
2186
+ });
2187
+ } else {
2188
+ this.emitExpr(args[0], "i32");
2189
+ this.pushInstruction({
2190
+ op: "call",
2191
+ funcIndex: this.mod.getStrFromI32ImportIndex(),
2192
+ });
2193
+ }
2194
+ return;
2195
+ }
2196
+ case "FormatInt": {
2197
+ this.emitExpr(args[0], "i64");
2198
+ this._emitI32Arg(args[1]);
2199
+ this._callStdlib("strconv_format_int");
2200
+ return;
2201
+ }
2202
+ case "FormatBool": {
2203
+ // bool → "true"/"false" via the string table
2204
+ this.emitExpr(args[0], "i32");
2205
+ this.pushInstruction({ op: "if", blockType: "externref" });
2206
+ this.pushInstruction({
2207
+ op: "i32.const",
2208
+ value: this.mod.internString("true"),
2209
+ });
2210
+ this.pushInstruction({
2211
+ op: "call",
2212
+ funcIndex: this.mod.getStringImportIndex(),
2213
+ });
2214
+ this.pushInstruction("else");
2215
+ this.pushInstruction({
2216
+ op: "i32.const",
2217
+ value: this.mod.internString("false"),
2218
+ });
2219
+ this.pushInstruction({
2220
+ op: "call",
2221
+ funcIndex: this.mod.getStringImportIndex(),
2222
+ });
2223
+ this.pushInstruction("end");
2224
+ return;
2225
+ }
2226
+ case "FormatFloat": {
2227
+ this.emitExpr(args[0], "f64");
2228
+ this._emitI32Arg(args[1]);
2229
+ this._emitI32Arg(args[2]);
2230
+ this._emitI32Arg(args[3]);
2231
+ this._callStdlib("strconv_format_float");
2232
+ return;
2233
+ }
2234
+ case "Quote": {
2235
+ this.emitExpr(args[0], "externref");
2236
+ this._callStdlib("strconv_quote");
2237
+ return;
2238
+ }
2239
+ case "Atoi":
2240
+ case "ParseInt":
2241
+ case "ParseFloat":
2242
+ case "ParseBool": {
2243
+ // (value, error): the JS side records a failure message that
2244
+ // `strconv_err` returns (null on success) → runtime error value.
2245
+ this.emitExpr(args[0], "externref");
2246
+ if (name === "Atoi") this._callStdlib("strconv_atoi");
2247
+ else if (name === "ParseInt") {
2248
+ this._emitI32Arg(args[1]);
2249
+ this._emitI32Arg(args[2]);
2250
+ this._callStdlib("strconv_parse_int");
2251
+ } else if (name === "ParseFloat")
2252
+ this._callStdlib("strconv_parse_float");
2253
+ else this._callStdlib("strconv_parse_bool");
2254
+ this._callStdlib("strconv_err");
2255
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
2256
+ this.pushInstruction({
2257
+ op: "call",
2258
+ funcIndex: this.mod.resolveFuncIndex("__mk_error"),
2259
+ });
2260
+ return;
2261
+ }
2262
+ default:
2263
+ throw new Error(`Unsupported strconv function: strconv.${name}`);
2264
+ }
2265
+ }
2266
+
2267
+ emitFmtCall(name, args) {
2268
+ if (name === "Println" || name === "Print") {
2269
+ const isPrintln = name === "Println";
2270
+ if (args.length === 0 && isPrintln) {
2271
+ const logIdx = this.mod.getPrintlnEmptyIndex();
2272
+ this.pushInstruction({ op: "call", funcIndex: logIdx });
2273
+ return;
2274
+ }
2275
+ for (let i = 0; i < args.length; i++) {
2276
+ const arg = args[i];
2277
+ const isLast = i === args.length - 1 && isPrintln;
2278
+ const goType = arg._type ?? this._resolveExprGoType(arg);
2279
+ const isBool =
2280
+ goType?.name === "bool" ||
2281
+ (arg.kind === "BasicLit" && arg.litKind === "BOOL");
2282
+ if (isErrorType(goType) && this.mod.hasErrorType) {
2283
+ this.emitExpr(arg, "anyref");
2284
+ this.pushInstruction({
2285
+ op: "call",
2286
+ funcIndex: this.mod.resolveFuncIndex("__error_str"),
2287
+ });
2288
+ this.pushInstruction({
2289
+ op: "call",
2290
+ funcIndex: this.mod.getLogImportIndex("externref", isLast, false),
2291
+ });
2292
+ continue;
2293
+ }
2294
+ const wType = toWasmType(goType, this.mod.checker);
2295
+ this.emitExpr(arg, wType);
2296
+ const logFuncIdx = this.mod.getLogImportIndex(wType, isLast, isBool);
2297
+ this.pushInstruction({ op: "call", funcIndex: logFuncIdx });
2298
+ }
2299
+ return;
2300
+ }
2301
+ if (name === "Sprintf" || name === "Printf" || name === "Errorf") {
2302
+ const [format, ...rest] = args;
2303
+ const temps = this._emitArgBuffer(rest);
2304
+ this.emitExpr(format, "externref");
2305
+ if (name === "Printf") {
2306
+ this._callStdlib("fmt_printf");
2307
+ this._releaseArgBuffer(temps);
2308
+ return;
2309
+ }
2310
+ this._callStdlib("fmt_sprintf");
2311
+ if (name === "Errorf") {
2312
+ // `%w` wraps its operand as the cause (first %w only, like Go 1.13).
2313
+ const wIdx = _wrapVerbIndex(format);
2314
+ const cause = wIdx >= 0 ? temps[wIdx] : null;
2315
+ const causeTmp =
2316
+ cause?.errTmp ?? (cause?.wType === "anyref" ? cause.tmp : null);
2317
+ if (causeTmp !== null && causeTmp !== undefined) {
2318
+ this.pushInstruction({ op: "local.get", index: causeTmp });
2319
+ } else {
2320
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
2321
+ }
2322
+ this.pushInstruction({
2323
+ op: "call",
2324
+ funcIndex: this.mod.resolveFuncIndex("__mk_error"),
2325
+ });
2326
+ }
2327
+ this._releaseArgBuffer(temps);
2328
+ return;
2329
+ }
2330
+ throw new Error(`Unsupported fmt function: fmt.${name}`);
2331
+ }
2332
+
2333
+ emitErrorsCall(name, args) {
2334
+ switch (name) {
2335
+ case "New":
2336
+ this.emitExpr(args[0], "externref");
2337
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
2338
+ this.pushInstruction({
2339
+ op: "call",
2340
+ funcIndex: this.mod.resolveFuncIndex("__mk_error"),
2341
+ });
2342
+ return;
2343
+ case "Unwrap":
2344
+ this.emitExpr(args[0], "anyref");
2345
+ this.pushInstruction({
2346
+ op: "call",
2347
+ funcIndex: this.mod.resolveFuncIndex("__errors_unwrap"),
2348
+ });
2349
+ return;
2350
+ case "Is":
2351
+ this.emitExpr(args[0], "anyref");
2352
+ this.emitExpr(args[1], "anyref");
2353
+ this.pushInstruction({
2354
+ op: "call",
2355
+ funcIndex: this.mod.resolveFuncIndex("__errors_is"),
2356
+ });
2357
+ return;
2358
+ default:
2359
+ throw new Error(`Unsupported errors function: errors.${name}`);
2360
+ }
2361
+ }
2362
+
2363
+ emitUtf8Call(name, args, targetWasmType) {
2364
+ const wide = (targetWasmType ?? "i64") === "i64";
2365
+ switch (name) {
2366
+ case "RuneCountInString":
2367
+ this.emitExpr(args[0], "externref");
2368
+ this._callStdlib("utf8_rune_count");
2369
+ if (wide) this.pushInstruction("i64.extend_i32_s");
2370
+ return;
2371
+ case "RuneLen":
2372
+ this._emitI32Arg(args[0]);
2373
+ this._callStdlib("utf8_rune_len");
2374
+ if (wide) this.pushInstruction("i64.extend_i32_s");
2375
+ return;
2376
+ case "ValidString":
2377
+ this.emitExpr(args[0], "externref");
2378
+ this._callStdlib("utf8_valid_string");
2379
+ return;
2380
+ case "FullRuneInString":
2381
+ this.emitExpr(args[0], "externref");
2382
+ this._callStdlib("utf8_full_rune");
2383
+ return;
2384
+ case "ValidRune": {
2385
+ // 0 <= r <= MaxRune && !(0xD800 <= r <= 0xDFFF) ⇔ RuneLen(r) > 0
2386
+ this._emitI32Arg(args[0]);
2387
+ this._callStdlib("utf8_rune_len");
2388
+ this.pushInstruction({ op: "i32.const", value: 0 });
2389
+ this.pushInstruction("i32.gt_s");
2390
+ return;
2391
+ }
2392
+ case "DecodeRuneInString":
2393
+ case "DecodeLastRuneInString":
2394
+ // (rune, size): decode stores the size for the follow-up import.
2395
+ this.emitExpr(args[0], "externref");
2396
+ this.pushInstruction({
2397
+ op: "i32.const",
2398
+ value: name === "DecodeLastRuneInString" ? 1 : 0,
2399
+ });
2400
+ this._callStdlib("utf8_decode_rune");
2401
+ this.pushInstruction("i64.extend_i32_s");
2402
+ this._callStdlib("utf8_decode_size");
2403
+ this.pushInstruction("i64.extend_i32_s");
2404
+ return;
2405
+ default:
2406
+ throw new Error(`Unsupported utf8 function: utf8.${name}`);
2407
+ }
2408
+ }
2409
+
2410
+ emitSortCall(name, args, targetWasmType) {
2411
+ switch (name) {
2412
+ case "Ints":
2413
+ case "Float64s":
2414
+ case "Strings":
2415
+ this.emitSlicesSort(args);
2416
+ return;
2417
+ case "IntsAreSorted":
2418
+ case "Float64sAreSorted":
2419
+ case "StringsAreSorted":
2420
+ this.emitSortIsSorted(args, null);
2421
+ return;
2422
+ case "Slice":
2423
+ case "SliceStable":
2424
+ this.emitSortSlice(args);
2425
+ return;
2426
+ case "SliceIsSorted":
2427
+ this.emitSortIsSorted(args, args[1]);
2428
+ return;
2429
+ case "Search":
2430
+ this.emitSortSearch(args, targetWasmType);
2431
+ return;
2432
+ default:
2433
+ throw new Error(`Unsupported sort function: sort.${name}`);
2434
+ }
2435
+ }
2436
+
2437
+ // Calls `less(i, j)` (a `func(int, int) bool` closure held in a temp) with
2438
+ // two i32 index temps.
2439
+ _emitCallLess(closureTmp, closureInfo, iLoc, jLoc) {
2440
+ this.pushInstruction({ op: "local.get", index: closureTmp });
2441
+ this.pushInstruction({
2442
+ op: "struct.get",
2443
+ typeIndex: closureInfo.typeIndex,
2444
+ fieldIndex: 1,
2445
+ });
2446
+ for (const loc of [iLoc, jLoc]) {
2447
+ this.pushInstruction({ op: "local.get", index: loc });
2448
+ if (closureInfo.paramWTypes[0] === "i64")
2449
+ this.pushInstruction("i64.extend_i32_s");
2450
+ }
2451
+ this.pushInstruction({ op: "local.get", index: closureTmp });
2452
+ this.pushInstruction({
2453
+ op: "struct.get",
2454
+ typeIndex: closureInfo.typeIndex,
2455
+ fieldIndex: 0,
2456
+ });
2457
+ this.pushInstruction({
2458
+ op: "call_ref",
2459
+ typeIndex: closureInfo.funcTypeIndex,
2460
+ });
2461
+ }
2462
+
2463
+ // Stable insertion sort driven by `less(i, j)` on live indices: swaps
2464
+ // adjacent elements while less(j, j-1), so the closure always observes
2465
+ // the slice's current layout.
2466
+ emitSortSlice(args) {
2467
+ const sNode = args[0];
2468
+ const sType = sNode._type ?? this._resolveExprGoType(sNode);
2469
+ const elemGoType = this._getSliceElemType(sType);
2470
+ const sliceInfo = this.mod.getSliceType(elemGoType);
2471
+ const arrInfo = sliceInfo.arrInfo;
2472
+ const sliceW = {
2473
+ kind: "ref",
2474
+ nullable: true,
2475
+ typeIndex: sliceInfo.typeIndex,
2476
+ };
2477
+ const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
2478
+ const closureInfo = this.mod.getClosureType(
2479
+ this._resolveExprGoType(args[1]),
2480
+ );
2481
+ const closureW = {
2482
+ kind: "ref",
2483
+ nullable: true,
2484
+ typeIndex: closureInfo.typeIndex,
2485
+ };
2486
+
2487
+ const s = this.acquireTemp(sliceW);
2488
+ const cl = this.acquireTemp(closureW);
2489
+ const arr = this.acquireTemp(arrW);
2490
+ const off = this.acquireTemp("i32");
2491
+ const len = this.acquireTemp("i32");
2492
+ const i = this.acquireTemp("i32");
2493
+ const j = this.acquireTemp("i32");
2494
+ const jm1 = this.acquireTemp("i32");
2495
+ const tmp = this.acquireTemp(arrInfo.elemWType);
2496
+
2497
+ this.emitExpr(sNode, sliceW);
2498
+ this.pushInstruction({ op: "local.set", index: s });
2499
+ this.emitExpr(args[1], closureW);
2500
+ this.pushInstruction({ op: "local.set", index: cl });
2501
+
2502
+ this.pushInstruction({ op: "block", blockType: "void" });
2503
+ this.pushInstruction({ op: "local.get", index: s });
2504
+ this.pushInstruction("ref.is_null");
2505
+ this.pushInstruction({ op: "br_if", depth: 0 });
2506
+ this._emitSliceUnpack(s, sliceInfo, { arr, off, len });
2507
+
2508
+ this.pushInstruction({ op: "i32.const", value: 1 });
2509
+ this.pushInstruction({ op: "local.set", index: i });
2510
+ this.pushInstruction({ op: "loop", blockType: "void" }); // outer
2511
+ this.pushInstruction({ op: "local.get", index: i });
2512
+ this.pushInstruction({ op: "local.get", index: len });
2513
+ this.pushInstruction("i32.ge_s");
2514
+ this.pushInstruction({ op: "br_if", depth: 1 });
2515
+
2516
+ this.pushInstruction({ op: "local.get", index: i });
2517
+ this.pushInstruction({ op: "local.set", index: j });
2518
+ this.pushInstruction({ op: "block", blockType: "void" });
2519
+ this.pushInstruction({ op: "loop", blockType: "void" }); // inner
2520
+ this.pushInstruction({ op: "local.get", index: j });
2521
+ this.pushInstruction("i32.eqz");
2522
+ this.pushInstruction({ op: "br_if", depth: 1 });
2523
+ this.pushInstruction({ op: "local.get", index: j });
2524
+ this.pushInstruction({ op: "i32.const", value: 1 });
2525
+ this.pushInstruction("i32.sub");
2526
+ this.pushInstruction({ op: "local.set", index: jm1 });
2527
+ this._emitCallLess(cl, closureInfo, j, jm1);
2528
+ this.pushInstruction("i32.eqz");
2529
+ this.pushInstruction({ op: "br_if", depth: 1 });
2530
+ // swap arr[off+j], arr[off+j-1]
2531
+ this.pushInstruction({ op: "local.get", index: arr });
2532
+ this.pushInstruction({ op: "local.get", index: off });
2533
+ this.pushInstruction({ op: "local.get", index: j });
2534
+ this.pushInstruction("i32.add");
2535
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
2536
+ this.pushInstruction({ op: "local.set", index: tmp });
2537
+ this.pushInstruction({ op: "local.get", index: arr });
2538
+ this.pushInstruction({ op: "local.get", index: off });
2539
+ this.pushInstruction({ op: "local.get", index: j });
2540
+ this.pushInstruction("i32.add");
2541
+ this.pushInstruction({ op: "local.get", index: arr });
2542
+ this.pushInstruction({ op: "local.get", index: off });
2543
+ this.pushInstruction({ op: "local.get", index: jm1 });
2544
+ this.pushInstruction("i32.add");
2545
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
2546
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
2547
+ this.pushInstruction({ op: "local.get", index: arr });
2548
+ this.pushInstruction({ op: "local.get", index: off });
2549
+ this.pushInstruction({ op: "local.get", index: jm1 });
2550
+ this.pushInstruction("i32.add");
2551
+ this.pushInstruction({ op: "local.get", index: tmp });
2552
+ this.pushInstruction({ op: "array.set", typeIndex: arrInfo.typeIndex });
2553
+ this.pushInstruction({ op: "local.get", index: jm1 });
2554
+ this.pushInstruction({ op: "local.set", index: j });
2555
+ this.pushInstruction({ op: "br", depth: 0 });
2556
+ this.pushInstruction("end");
2557
+ this.pushInstruction("end");
2558
+
2559
+ this.pushInstruction({ op: "local.get", index: i });
2560
+ this.pushInstruction({ op: "i32.const", value: 1 });
2561
+ this.pushInstruction("i32.add");
2562
+ this.pushInstruction({ op: "local.set", index: i });
2563
+ this.pushInstruction({ op: "br", depth: 0 });
2564
+ this.pushInstruction("end"); // outer
2565
+ this.pushInstruction("end"); // block
2566
+
2567
+ this.releaseTemp(tmp, arrInfo.elemWType);
2568
+ this.releaseTemp(jm1, "i32");
2569
+ this.releaseTemp(j, "i32");
2570
+ this.releaseTemp(i, "i32");
2571
+ this.releaseTemp(len, "i32");
2572
+ this.releaseTemp(off, "i32");
2573
+ this.releaseTemp(arr, arrW);
2574
+ this.releaseTemp(cl, closureW);
2575
+ this.releaseTemp(s, sliceW);
2576
+ }
2577
+
2578
+ // sort.*AreSorted / sort.SliceIsSorted: true unless some adjacent pair is
2579
+ // out of order (`less(i, i-1)` or natural `<`).
2580
+ emitSortIsSorted(args, lessNode) {
2581
+ const sNode = args[0];
2582
+ const sType = sNode._type ?? this._resolveExprGoType(sNode);
2583
+ const elemGoType = this._getSliceElemType(sType);
2584
+ const sliceInfo = this.mod.getSliceType(elemGoType);
2585
+ const arrInfo = sliceInfo.arrInfo;
2586
+ const sliceW = {
2587
+ kind: "ref",
2588
+ nullable: true,
2589
+ typeIndex: sliceInfo.typeIndex,
2590
+ };
2591
+ const arrW = { kind: "ref", nullable: true, typeIndex: arrInfo.typeIndex };
2592
+ const closureInfo = lessNode
2593
+ ? this.mod.getClosureType(this._resolveExprGoType(lessNode))
2594
+ : null;
2595
+ const closureW = closureInfo
2596
+ ? { kind: "ref", nullable: true, typeIndex: closureInfo.typeIndex }
2597
+ : null;
2598
+
2599
+ const s = this.acquireTemp(sliceW);
2600
+ const cl = closureInfo ? this.acquireTemp(closureW) : null;
2601
+ const arr = this.acquireTemp(arrW);
2602
+ const off = this.acquireTemp("i32");
2603
+ const len = this.acquireTemp("i32");
2604
+ const i = this.acquireTemp("i32");
2605
+ const im1 = this.acquireTemp("i32");
2606
+ const res = this.acquireTemp("i32");
2607
+ const a = lessNode ? null : this.acquireTemp(arrInfo.elemWType);
2608
+ const b = lessNode ? null : this.acquireTemp(arrInfo.elemWType);
2609
+
2610
+ this.emitExpr(sNode, sliceW);
2611
+ this.pushInstruction({ op: "local.set", index: s });
2612
+ if (lessNode) {
2613
+ this.emitExpr(lessNode, closureW);
2614
+ this.pushInstruction({ op: "local.set", index: cl });
2615
+ }
2616
+ this.pushInstruction({ op: "i32.const", value: 1 });
2617
+ this.pushInstruction({ op: "local.set", index: res });
2618
+
2619
+ this.pushInstruction({ op: "block", blockType: "void" });
2620
+ this.pushInstruction({ op: "local.get", index: s });
2621
+ this.pushInstruction("ref.is_null");
2622
+ this.pushInstruction({ op: "br_if", depth: 0 });
2623
+ this._emitSliceUnpack(s, sliceInfo, { arr, off, len });
2624
+ this.pushInstruction({ op: "i32.const", value: 1 });
2625
+ this.pushInstruction({ op: "local.set", index: i });
2626
+ this.pushInstruction({ op: "loop", blockType: "void" });
2627
+ this.pushInstruction({ op: "local.get", index: i });
2628
+ this.pushInstruction({ op: "local.get", index: len });
2629
+ this.pushInstruction("i32.ge_s");
2630
+ this.pushInstruction({ op: "br_if", depth: 1 });
2631
+ this.pushInstruction({ op: "local.get", index: i });
2632
+ this.pushInstruction({ op: "i32.const", value: 1 });
2633
+ this.pushInstruction("i32.sub");
2634
+ this.pushInstruction({ op: "local.set", index: im1 });
2635
+ if (lessNode) {
2636
+ this._emitCallLess(cl, closureInfo, i, im1);
2637
+ } else {
2638
+ for (const [dst, idx] of [
2639
+ [a, i],
2640
+ [b, im1],
2641
+ ]) {
2642
+ this.pushInstruction({ op: "local.get", index: arr });
2643
+ this.pushInstruction({ op: "local.get", index: off });
2644
+ this.pushInstruction({ op: "local.get", index: idx });
2645
+ this.pushInstruction("i32.add");
2646
+ this.pushInstruction({ op: "array.get", typeIndex: arrInfo.typeIndex });
2647
+ this.pushInstruction({ op: "local.set", index: dst });
2648
+ }
2649
+ this.emitElemLt(a, b, elemGoType);
2650
+ }
2651
+ // out of order → res = 0, exit
2652
+ this.pushInstruction({ op: "if", blockType: "void" });
2653
+ this.pushInstruction({ op: "i32.const", value: 0 });
2654
+ this.pushInstruction({ op: "local.set", index: res });
2655
+ this.pushInstruction({ op: "br", depth: 2 });
2656
+ this.pushInstruction("end");
2657
+ this.pushInstruction({ op: "local.get", index: i });
2658
+ this.pushInstruction({ op: "i32.const", value: 1 });
2659
+ this.pushInstruction("i32.add");
2660
+ this.pushInstruction({ op: "local.set", index: i });
2661
+ this.pushInstruction({ op: "br", depth: 0 });
2662
+ this.pushInstruction("end");
2663
+ this.pushInstruction("end");
2664
+ this.pushInstruction({ op: "local.get", index: res });
2665
+
2666
+ if (b !== null) this.releaseTemp(b, arrInfo.elemWType);
2667
+ if (a !== null) this.releaseTemp(a, arrInfo.elemWType);
2668
+ this.releaseTemp(res, "i32");
2669
+ this.releaseTemp(im1, "i32");
2670
+ this.releaseTemp(i, "i32");
2671
+ this.releaseTemp(len, "i32");
2672
+ this.releaseTemp(off, "i32");
2673
+ this.releaseTemp(arr, arrW);
2674
+ if (cl !== null) this.releaseTemp(cl, closureW);
2675
+ this.releaseTemp(s, sliceW);
2676
+ }
2677
+
2678
+ // sort.Search(n, f): smallest i in [0, n) with f(i) true (binary search).
2679
+ emitSortSearch(args, targetWasmType) {
2680
+ const closureInfo = this.mod.getClosureType(
2681
+ this._resolveExprGoType(args[1]),
2682
+ );
2683
+ const closureW = {
2684
+ kind: "ref",
2685
+ nullable: true,
2686
+ typeIndex: closureInfo.typeIndex,
2687
+ };
2688
+ const cl = this.acquireTemp(closureW);
2689
+ const lo = this.acquireTemp("i32");
2690
+ const hi = this.acquireTemp("i32");
2691
+ const mid = this.acquireTemp("i32");
2692
+
2693
+ this._emitI32Arg(args[0]);
2694
+ this.pushInstruction({ op: "local.set", index: hi });
2695
+ this.emitExpr(args[1], closureW);
2696
+ this.pushInstruction({ op: "local.set", index: cl });
2697
+ this.pushInstruction({ op: "i32.const", value: 0 });
2698
+ this.pushInstruction({ op: "local.set", index: lo });
2699
+
2700
+ this.pushInstruction({ op: "block", blockType: "void" });
2701
+ this.pushInstruction({ op: "loop", blockType: "void" });
2702
+ this.pushInstruction({ op: "local.get", index: lo });
2703
+ this.pushInstruction({ op: "local.get", index: hi });
2704
+ this.pushInstruction("i32.ge_s");
2705
+ this.pushInstruction({ op: "br_if", depth: 1 });
2706
+ // mid = lo + (hi-lo)/2
2707
+ this.pushInstruction({ op: "local.get", index: lo });
2708
+ this.pushInstruction({ op: "local.get", index: hi });
2709
+ this.pushInstruction({ op: "local.get", index: lo });
2710
+ this.pushInstruction("i32.sub");
2711
+ this.pushInstruction({ op: "i32.const", value: 1 });
2712
+ this.pushInstruction("i32.shr_u");
2713
+ this.pushInstruction("i32.add");
2714
+ this.pushInstruction({ op: "local.set", index: mid });
2715
+ // f(mid)
2716
+ this.pushInstruction({ op: "local.get", index: cl });
2717
+ this.pushInstruction({
2718
+ op: "struct.get",
2719
+ typeIndex: closureInfo.typeIndex,
2720
+ fieldIndex: 1,
2721
+ });
2722
+ this.pushInstruction({ op: "local.get", index: mid });
2723
+ if (closureInfo.paramWTypes[0] === "i64")
2724
+ this.pushInstruction("i64.extend_i32_s");
2725
+ this.pushInstruction({ op: "local.get", index: cl });
2726
+ this.pushInstruction({
2727
+ op: "struct.get",
2728
+ typeIndex: closureInfo.typeIndex,
2729
+ fieldIndex: 0,
2730
+ });
2731
+ this.pushInstruction({
2732
+ op: "call_ref",
2733
+ typeIndex: closureInfo.funcTypeIndex,
2734
+ });
2735
+ this.pushInstruction({ op: "if", blockType: "void" });
2736
+ this.pushInstruction({ op: "local.get", index: mid });
2737
+ this.pushInstruction({ op: "local.set", index: hi });
2738
+ this.pushInstruction("else");
2739
+ this.pushInstruction({ op: "local.get", index: mid });
2740
+ this.pushInstruction({ op: "i32.const", value: 1 });
2741
+ this.pushInstruction("i32.add");
2742
+ this.pushInstruction({ op: "local.set", index: lo });
2743
+ this.pushInstruction("end");
2744
+ this.pushInstruction({ op: "br", depth: 0 });
2745
+ this.pushInstruction("end");
2746
+ this.pushInstruction("end");
2747
+ this.pushInstruction({ op: "local.get", index: lo });
2748
+ if ((targetWasmType ?? "i64") === "i64")
2749
+ this.pushInstruction("i64.extend_i32_s");
2750
+
2751
+ this.releaseTemp(mid, "i32");
2752
+ this.releaseTemp(hi, "i32");
2753
+ this.releaseTemp(lo, "i32");
2754
+ this.releaseTemp(cl, closureW);
2755
+ }
2756
+
2757
+ // `a == b` for two anyref operands (interface values). `ref.eq` only
2758
+ // accepts eqref, so host strings (internalised externs) are compared via
2759
+ // `str_eq` and anything else by identity.
2760
+ _emitAnyEq() {
2761
+ const idx = this.mod.resolveFuncIndex("__any_eq");
2762
+ if (idx === null) {
2763
+ throw new Error(
2764
+ "internal: anyref equality emitted but __any_eq was not declared (usesAnyEq scan missed a site)",
2765
+ );
2766
+ }
2767
+ this.pushInstruction({ op: "call", funcIndex: idx });
2768
+ }
2769
+
2770
+ // Bodies of the synthetic runtime helpers declared in compileWasmModule.
2771
+ emitRuntimeHelper(fn) {
2772
+ const errInfo = this.mod.getStructType(ERROR_STRUCT);
2773
+ switch (fn._isRuntimeHelper) {
2774
+ case "any_eq": {
2775
+ const bothNull = () => {
2776
+ this.pushInstruction({ op: "local.get", index: 0 });
2777
+ this.pushInstruction("ref.is_null");
2778
+ this.pushInstruction({ op: "local.get", index: 1 });
2779
+ this.pushInstruction("ref.is_null");
2780
+ };
2781
+ bothNull();
2782
+ this.pushInstruction("i32.and");
2783
+ this.pushInstruction({ op: "if", blockType: "void" });
2784
+ this.pushInstruction({ op: "i32.const", value: 1 });
2785
+ this.pushInstruction("return");
2786
+ this.pushInstruction("end");
2787
+ bothNull();
2788
+ this.pushInstruction("i32.or");
2789
+ this.pushInstruction({ op: "if", blockType: "void" });
2790
+ this.pushInstruction({ op: "i32.const", value: 0 });
2791
+ this.pushInstruction("return");
2792
+ this.pushInstruction("end");
2793
+ // Boxed scalars compare by value (Go: any(3) == any(3)).
2794
+ for (const wType of ["i32", "i64", "f32", "f64"]) {
2795
+ const box = this.mod.getBoxType(wType);
2796
+ for (const idx of [0, 1]) {
2797
+ this.pushInstruction({ op: "local.get", index: idx });
2798
+ this.pushInstruction({ op: "ref.test", typeIndex: box.typeIndex });
2799
+ }
2800
+ this.pushInstruction("i32.and");
2801
+ this.pushInstruction({ op: "if", blockType: "void" });
2802
+ for (const idx of [0, 1]) {
2803
+ this.pushInstruction({ op: "local.get", index: idx });
2804
+ this.pushInstruction({ op: "ref.cast", typeIndex: box.typeIndex });
2805
+ this.pushInstruction({
2806
+ op: "struct.get",
2807
+ typeIndex: box.typeIndex,
2808
+ fieldIndex: 0,
2809
+ });
2810
+ }
2811
+ this.pushInstruction(`${wType}.eq`);
2812
+ this.pushInstruction("return");
2813
+ this.pushInstruction("end");
2814
+ }
2815
+ this.pushInstruction({ op: "local.get", index: 0 });
2816
+ this.pushInstruction({ op: "ref.test", heapType: "eq" });
2817
+ this.pushInstruction({ op: "local.get", index: 1 });
2818
+ this.pushInstruction({ op: "ref.test", heapType: "eq" });
2819
+ this.pushInstruction("i32.and");
2820
+ this.pushInstruction({ op: "if", blockType: "i32" });
2821
+ this.pushInstruction({ op: "local.get", index: 0 });
2822
+ this.pushInstruction({ op: "ref.cast", heapType: "eq" });
2823
+ this.pushInstruction({ op: "local.get", index: 1 });
2824
+ this.pushInstruction({ op: "ref.cast", heapType: "eq" });
2825
+ this.pushInstruction("ref.eq");
2826
+ this.pushInstruction("else");
2827
+ this.pushInstruction({ op: "local.get", index: 0 });
2828
+ this.pushInstruction({
2829
+ op: "call",
2830
+ funcIndex: this.mod.getIsStringImportIndex(),
2831
+ });
2832
+ this.pushInstruction({ op: "local.get", index: 1 });
2833
+ this.pushInstruction({
2834
+ op: "call",
2835
+ funcIndex: this.mod.getIsStringImportIndex(),
2836
+ });
2837
+ this.pushInstruction("i32.and");
2838
+ this.pushInstruction({ op: "if", blockType: "i32" });
2839
+ this.pushInstruction({ op: "local.get", index: 0 });
2840
+ this.pushInstruction("extern.convert_any");
2841
+ this.pushInstruction({ op: "local.get", index: 1 });
2842
+ this.pushInstruction("extern.convert_any");
2843
+ this.pushInstruction({
2844
+ op: "call",
2845
+ funcIndex: this.mod.getStringCmpImportIndex("=="),
2846
+ });
2847
+ this.pushInstruction("else");
2848
+ this.pushInstruction({ op: "i32.const", value: 0 });
2849
+ this.pushInstruction("end");
2850
+ this.pushInstruction("end");
2851
+ this.pushInstruction("return");
2852
+ return;
2853
+ }
2854
+ case "error_msg":
2855
+ case "error_cause":
2856
+ this.pushInstruction({ op: "local.get", index: 0 });
2857
+ this.pushInstruction({
2858
+ op: "struct.get",
2859
+ typeIndex: errInfo.typeIndex,
2860
+ fieldIndex: fn._isRuntimeHelper === "error_msg" ? 0 : 1,
2861
+ });
2862
+ this.pushInstruction("return");
2863
+ return;
2864
+ case "mk_error":
2865
+ // nil message (strconv success) → nil error
2866
+ this.pushInstruction({ op: "local.get", index: 0 });
2867
+ this.pushInstruction("any.convert_extern");
2868
+ this.pushInstruction("ref.is_null");
2869
+ this.pushInstruction({ op: "if", blockType: "void" });
2870
+ this.pushInstruction({ op: "ref.null", heapType: "any" });
2871
+ this.pushInstruction("return");
2872
+ this.pushInstruction("end");
2873
+ this.pushInstruction({ op: "local.get", index: 0 });
2874
+ this.pushInstruction({ op: "local.get", index: 1 });
2875
+ this.pushInstruction({
2876
+ op: "struct.new",
2877
+ typeIndex: errInfo.typeIndex,
2878
+ });
2879
+ this.pushInstruction("return");
2880
+ return;
2881
+ case "error_str":
2882
+ this.pushInstruction({ op: "local.get", index: 0 });
2883
+ this.pushInstruction("ref.is_null");
2884
+ this.pushInstruction({ op: "if", blockType: "void" });
2885
+ this.pushInstruction({
2886
+ op: "i32.const",
2887
+ value: this.mod.internString("<nil>"),
2888
+ });
2889
+ this.pushInstruction({
2890
+ op: "call",
2891
+ funcIndex: this.mod.getStringImportIndex(),
2892
+ });
2893
+ this.pushInstruction("return");
2894
+ this.pushInstruction("end");
2895
+ this.pushInstruction({ op: "local.get", index: 0 });
2896
+ this.pushInstruction({
2897
+ op: "call",
2898
+ funcIndex: this.mod.resolveFuncIndex("__dispatch_error_Error"),
2899
+ });
2900
+ this.pushInstruction("return");
2901
+ return;
2902
+ case "errors_is": {
2903
+ // for err != nil { if err == target || sameMsg(err, target) { return true }; err = unwrap(err) }
2904
+ this.pushInstruction({ op: "block", blockType: "void" });
2905
+ this.pushInstruction({ op: "loop", blockType: "void" });
2906
+ this.pushInstruction({ op: "local.get", index: 0 });
2907
+ this.pushInstruction("ref.is_null");
2908
+ this.pushInstruction({ op: "br_if", depth: 1 });
2909
+ this.pushInstruction({ op: "local.get", index: 0 });
2910
+ this.pushInstruction({ op: "local.get", index: 1 });
2911
+ this._emitAnyEq();
2912
+ this.pushInstruction({ op: "if", blockType: "void" });
2913
+ this.pushInstruction({ op: "i32.const", value: 1 });
2914
+ this.pushInstruction("return");
2915
+ this.pushInstruction("end");
2916
+ // both runtime errors with equal messages (mirrors the JS backend)
2917
+ this.pushInstruction({ op: "local.get", index: 0 });
2918
+ this.pushInstruction({ op: "ref.test", typeIndex: errInfo.typeIndex });
2919
+ this.pushInstruction({ op: "local.get", index: 1 });
2920
+ this.pushInstruction({ op: "ref.test", typeIndex: errInfo.typeIndex });
2921
+ this.pushInstruction("i32.and");
2922
+ this.pushInstruction({ op: "if", blockType: "void" });
2923
+ for (const idx of [0, 1]) {
2924
+ this.pushInstruction({ op: "local.get", index: idx });
2925
+ this.pushInstruction({
2926
+ op: "ref.cast",
2927
+ typeIndex: errInfo.typeIndex,
2928
+ });
2929
+ this.pushInstruction({
2930
+ op: "struct.get",
2931
+ typeIndex: errInfo.typeIndex,
2932
+ fieldIndex: 0,
2933
+ });
2934
+ }
2935
+ this.pushInstruction({
2936
+ op: "call",
2937
+ funcIndex: this.mod.getStringCmpImportIndex("=="),
2938
+ });
2939
+ this.pushInstruction({ op: "if", blockType: "void" });
2940
+ this.pushInstruction({ op: "i32.const", value: 1 });
2941
+ this.pushInstruction("return");
2942
+ this.pushInstruction("end");
2943
+ this.pushInstruction("end");
2944
+ this.pushInstruction({ op: "local.get", index: 0 });
2945
+ this.pushInstruction({
2946
+ op: "call",
2947
+ funcIndex: this.mod.resolveFuncIndex("__errors_unwrap"),
2948
+ });
2949
+ this.pushInstruction({ op: "local.set", index: 0 });
2950
+ this.pushInstruction({ op: "br", depth: 0 });
2951
+ this.pushInstruction("end");
2952
+ this.pushInstruction("end");
2953
+ this.pushInstruction({ op: "i32.const", value: 0 });
2954
+ this.pushInstruction("return");
2955
+ return;
2956
+ }
2957
+ case "testing_run_cb": {
2958
+ // (fn anyref, t externref): fn is a func(*testing.T) closure
2959
+ const closureInfo = this.mod.getTestingRunClosureType();
2960
+ this.pushInstruction({ op: "local.get", index: 0 });
2961
+ this.pushInstruction({
2962
+ op: "ref.cast",
2963
+ typeIndex: closureInfo.typeIndex,
2964
+ });
2965
+ this.pushInstruction({
2966
+ op: "struct.get",
2967
+ typeIndex: closureInfo.typeIndex,
2968
+ fieldIndex: 1,
2969
+ });
2970
+ this.pushInstruction({ op: "local.get", index: 1 });
2971
+ this.pushInstruction({ op: "local.get", index: 0 });
2972
+ this.pushInstruction({
2973
+ op: "ref.cast",
2974
+ typeIndex: closureInfo.typeIndex,
2975
+ });
2976
+ this.pushInstruction({
2977
+ op: "struct.get",
2978
+ typeIndex: closureInfo.typeIndex,
2979
+ fieldIndex: 0,
2980
+ });
2981
+ this.pushInstruction({
2982
+ op: "call_ref",
2983
+ typeIndex: closureInfo.funcTypeIndex,
2984
+ });
2985
+ this.pushInstruction("return");
2986
+ return;
2987
+ }
2988
+ default:
2989
+ throw new Error(`Unknown runtime helper: ${fn._isRuntimeHelper}`);
2990
+ }
2991
+ }
2992
+ }
2993
+
2994
+ // Index of the argument consumed by the first `%w` verb in a literal format
2995
+ // string, or -1.
2996
+ function _wrapVerbIndex(format) {
2997
+ if (format?.kind !== "BasicLit" || format.litKind !== "STRING") return -1;
2998
+ const re = /%([#+\- 0]*)([0-9]*)\.?([0-9]*)([sdvftxXqobeEgGw%])/g;
2999
+ let i = 0;
3000
+ for (const m of String(format.value ?? "").matchAll(re)) {
3001
+ if (m[4] === "%") continue;
3002
+ if (m[4] === "w") return i;
3003
+ i++;
3004
+ }
3005
+ return -1;
3006
+ }