gofront 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +174 -466
  2. package/package.json +16 -20
  3. package/src/backend/js/expressions.js +288 -128
  4. package/src/backend/js/index.js +138 -56
  5. package/src/backend/js/runtime.js +1055 -53
  6. package/src/backend/js/source-map.js +32 -0
  7. package/src/backend/js/statements.js +151 -83
  8. package/src/backend/js/stdlib/base64.js +12 -0
  9. package/src/backend/js/stdlib/binary.js +102 -0
  10. package/src/backend/js/stdlib/bits.js +15 -1
  11. package/src/backend/js/stdlib/builder.js +28 -9
  12. package/src/backend/js/stdlib/bytes.js +75 -18
  13. package/src/backend/js/stdlib/errors.js +22 -3
  14. package/src/backend/js/stdlib/fmt.js +95 -20
  15. package/src/backend/js/stdlib/hex.js +17 -0
  16. package/src/backend/js/stdlib/html.js +1 -1
  17. package/src/backend/js/stdlib/index.js +19 -0
  18. package/src/backend/js/stdlib/io.js +105 -3
  19. package/src/backend/js/stdlib/maps.js +1 -1
  20. package/src/backend/js/stdlib/math.js +13 -1
  21. package/src/backend/js/stdlib/path.js +20 -6
  22. package/src/backend/js/stdlib/rand.js +10 -5
  23. package/src/backend/js/stdlib/sha256.js +12 -0
  24. package/src/backend/js/stdlib/shared.js +17 -0
  25. package/src/backend/js/stdlib/slices.js +53 -27
  26. package/src/backend/js/stdlib/sort.js +1 -1
  27. package/src/backend/js/stdlib/strconv.js +52 -11
  28. package/src/backend/js/stdlib/strings.js +152 -16
  29. package/src/backend/js/stdlib/sync.js +13 -0
  30. package/src/backend/js/stdlib/testing.js +2 -2
  31. package/src/backend/js/stdlib/time.js +93 -22
  32. package/src/backend/js/stdlib/unicode.js +2 -2
  33. package/src/backend/js/templ.js +1 -1
  34. package/src/backend/wasm/boundary.js +1677 -58
  35. package/src/backend/wasm/const-fold.js +205 -0
  36. package/src/backend/wasm/emit-builtins.js +1230 -0
  37. package/src/backend/wasm/emit-exprs.js +2096 -0
  38. package/src/backend/wasm/emit-maps.js +1999 -0
  39. package/src/backend/wasm/emit-shared.js +519 -0
  40. package/src/backend/wasm/emit-stdlib.js +13 -0
  41. package/src/backend/wasm/emit-stmts.js +2466 -0
  42. package/src/backend/wasm/emit.js +656 -5031
  43. package/src/backend/wasm/encode.js +259 -85
  44. package/src/backend/wasm/glue.js +19 -5
  45. package/src/backend/wasm/index.js +2432 -86
  46. package/src/backend/wasm/monomorph.js +711 -0
  47. package/src/backend/wasm/optimize.js +236 -0
  48. package/src/backend/wasm/stdlib/bytes.js +5039 -0
  49. package/src/backend/wasm/stdlib/core.js +8903 -0
  50. package/src/backend/wasm/stdlib/index.js +40 -0
  51. package/src/backend/wasm/stdlib/io.js +2161 -0
  52. package/src/backend/wasm/stdlib/packages.js +2400 -0
  53. package/src/backend/wasm/stdlib/pure.js +720 -0
  54. package/src/backend/wasm/stdlib/time.js +1200 -0
  55. package/src/backend/wasm/types.js +312 -36
  56. package/src/backend/wasm/wat.js +49 -2
  57. package/src/cli-core.js +276 -73
  58. package/src/compiler.js +162 -25
  59. package/src/dts-parser.js +79 -126
  60. package/src/index.js +88 -121
  61. package/src/lexer.js +80 -29
  62. package/src/lower/boxing.js +4 -6
  63. package/src/lower/captures.js +12 -23
  64. package/src/lower/escape.js +4 -9
  65. package/src/lower/functions.js +277 -5
  66. package/src/lower/globals.js +7 -15
  67. package/src/lower/index.js +5 -2
  68. package/src/lower/ownership.js +35 -19
  69. package/src/lower/range.js +2 -5
  70. package/src/lower/walk.js +25 -0
  71. package/src/minifier.js +20 -68
  72. package/src/parser/declarations.js +3 -6
  73. package/src/parser/expressions.js +3 -8
  74. package/src/parser/index.js +18 -3
  75. package/src/parser/statements.js +25 -38
  76. package/src/parser/types.js +1 -2
  77. package/src/resolver.js +3 -27
  78. package/src/templ-lexer.js +29 -63
  79. package/src/templ-parser.js +22 -45
  80. package/src/test-runner.js +20 -0
  81. package/src/typechecker/assignability.js +123 -14
  82. package/src/typechecker/expressions.js +244 -4
  83. package/src/typechecker/index.js +27 -1
  84. package/src/typechecker/resolve.js +71 -12
  85. package/src/typechecker/statements.js +0 -7
  86. package/src/typechecker/stdlib/core.js +292 -103
  87. package/src/typechecker/stdlib/extended.js +471 -4
  88. package/src/typechecker/stdlib/shared.js +53 -0
  89. package/src/typechecker/stdlib.js +2 -0
  90. package/src/typechecker/types.js +545 -8
  91. package/src/vendor.js +2 -2
@@ -10,15 +10,254 @@ import {
10
10
  collectBoundaryMeta,
11
11
  generateFacade,
12
12
  scanBoundaryImports,
13
+ wTypeKey,
13
14
  } from "./boundary.js";
15
+ import { foldConstExpr } from "./const-fold.js";
14
16
  import { FunctionEmitter } from "./emit.js";
15
17
  import { encodeModule, isGoFrontWasm } from "./encode.js";
16
- import { getFuncSignature, isTestingT, toWasmType } from "./types.js";
18
+ import { hasGenerics, monomorphise } from "./monomorph.js";
19
+ import { hasNativeWasmOpt, optimizeWasm } from "./optimize.js";
20
+ import { BINARY_MATH_IMPORTS } from "./stdlib/index.js";
21
+ import {
22
+ anonIfaceName,
23
+ ERROR_STRUCT,
24
+ getFuncSignature,
25
+ getInterfaceMethodList,
26
+ getMapKeyValTypes,
27
+ getReceiverTypeName,
28
+ goTypeKey,
29
+ isInterfaceType,
30
+ isMapType,
31
+ isNonEmptyInterface,
32
+ isTestingT,
33
+ toWasmType,
34
+ } from "./types.js";
17
35
  import { emitWat } from "./wat.js";
18
36
 
19
- export { isGoFrontWasm };
37
+ export { hasNativeWasmOpt, isGoFrontWasm, optimizeWasm };
38
+
39
+ const ERROR_PRODUCING_PKGS = new Set([
40
+ "errors",
41
+ "fmt",
42
+ "strconv",
43
+ "path",
44
+ "path/filepath",
45
+ "io",
46
+ "strings",
47
+ "bytes",
48
+ "time",
49
+ "sync",
50
+ "crypto/rand",
51
+ "rand",
52
+ "crypto/sha256",
53
+ "sha256",
54
+ "encoding/binary",
55
+ "binary",
56
+ "encoding/hex",
57
+ "hex",
58
+ "encoding/base64",
59
+ "base64",
60
+ ]);
61
+ // Packages whose calls lower to the `env.*` imports in STDLIB_IMPORTS (the
62
+ // matching JS lives in WASM_STDLIB_JS). Only modules importing one of them
63
+ // declare the imports and ship that JS block.
64
+ const STDLIB_ENV_PKGS = new Set([
65
+ "strings",
66
+ "strconv",
67
+ "unicode/utf8",
68
+ "fmt",
69
+ "testing",
70
+ "unicode",
71
+ "math/rand",
72
+ "path",
73
+ "path/filepath",
74
+ "html",
75
+ "bytes",
76
+ "io",
77
+ "time",
78
+ "sync",
79
+ "crypto/rand",
80
+ "rand",
81
+ "crypto/sha256",
82
+ "sha256",
83
+ "encoding/binary",
84
+ "binary",
85
+ "encoding/hex",
86
+ "hex",
87
+ "encoding/base64",
88
+ "base64",
89
+ ]);
90
+
91
+ function importsAnyOf(programs, pkgs) {
92
+ const has = (p) => (pkgs instanceof Set ? pkgs.has(p) : pkgs.includes(p));
93
+ return (programs ?? []).some((p) =>
94
+ (p.imports ?? []).some((decl) =>
95
+ (decl.imports ?? [decl]).some((imp) => has(imp.path)),
96
+ ),
97
+ );
98
+ }
99
+
100
+ function hasErrorUsage(programs) {
101
+ return (programs ?? []).some((p) => {
102
+ let found = false;
103
+ const scan = (n) => {
104
+ if (!n || found || typeof n !== "object") return;
105
+ if (Array.isArray(n)) {
106
+ for (const x of n) scan(x);
107
+ return;
108
+ }
109
+ if (
110
+ (n.kind === "Ident" ||
111
+ n.kind === "TypeName" ||
112
+ n.kind === "basic" ||
113
+ n.kind === "interface") &&
114
+ n.name === "error"
115
+ ) {
116
+ found = true;
117
+ return;
118
+ }
119
+ for (const k of Object.keys(n)) {
120
+ if (!k.startsWith("_")) scan(n[k]);
121
+ }
122
+ };
123
+ scan(p);
124
+ return found;
125
+ });
126
+ }
20
127
 
21
- export class ModuleEmitter {
128
+ // [name, params, results] for `env.*` imports backing stdlib calls; the JS
129
+ // side lives in WASM_STDLIB_JS (boundary.js). All are registered eagerly
130
+ // (when any STDLIB_ENV_PKGS package is imported) because the import table is
131
+ // locked before function bodies are emitted.
132
+ const S = "externref";
133
+ const STDLIB_IMPORTS = [
134
+ // strings
135
+ ["str_trim", [S, S], [S]],
136
+ ["str_trim_left", [S, S], [S]],
137
+ ["str_trim_right", [S, S], [S]],
138
+ ["str_trim_prefix", [S, S], [S]],
139
+ ["str_trim_suffix", [S, S], [S]],
140
+ ["str_cut", [S, S], ["i32"]],
141
+ ["str_cut_before", [], [S]],
142
+ ["str_cut_after", [], [S]],
143
+ ["str_cut_prefix", [S, S], ["i32"]],
144
+ ["str_cut_suffix", [S, S], ["i32"]],
145
+ ["str_replace", [S, S, S, "i32"], [S]],
146
+ ["str_contains_rune", [S, "i32"], ["i32"]],
147
+ ["str_contains_any", [S, S], ["i32"]],
148
+ ["str_index_byte", [S, "i32"], ["i32"]],
149
+ ["str_index_rune", [S, "i32"], ["i32"]],
150
+ ["str_index_any", [S, S], ["i32"]],
151
+ ["str_last_index_byte", [S, "i32"], ["i32"]],
152
+ ["str_last_index_any", [S, S], ["i32"]],
153
+ ["str_title", [S], [S]],
154
+ ["str_split", [S, S], ["i32"]],
155
+ ["str_split_n", [S, S, "i32"], ["i32"]],
156
+ ["str_split_after", [S, S], ["i32"]],
157
+ ["str_split_after_n", [S, S, "i32"], ["i32"]],
158
+ ["str_fields", [S], ["i32"]],
159
+ ["str_part", ["i32"], [S]],
160
+ ["str_replacer_init", [S], []],
161
+ ["str_replacer_add", [S, S], []],
162
+ ["str_replacer_run", [], [S]],
163
+ // strconv
164
+ ["strconv_atoi", [S], ["i64"]],
165
+ ["strconv_parse_int", [S, "i32", "i32"], ["i64"]],
166
+ ["strconv_parse_float", [S], ["f64"]],
167
+ ["strconv_parse_bool", [S], ["i32"]],
168
+ ["strconv_err", [], [S]],
169
+ ["strconv_err_kind", [], ["i32"]],
170
+ ["strconv_format_float", ["f64", "i32", "i32", "i32"], [S]],
171
+ ["strconv_format_int", ["i64", "i32"], [S]],
172
+ ["strconv_format_uint", ["i64", "i32"], [S]],
173
+ ["strconv_parse_uint", [S, "i32", "i32"], ["i64"]],
174
+ ["strconv_quote", [S], [S]],
175
+ ["strconv_unquote", [S], [S]],
176
+ // unicode/utf8
177
+ ["utf8_rune_count", [S], ["i32"]],
178
+ ["utf8_rune_len", ["i32"], ["i32"]],
179
+ ["utf8_valid_string", [S], ["i32"]],
180
+ ["utf8_decode_rune", [S, "i32"], ["i32"]],
181
+ ["utf8_decode_size", [], ["i32"]],
182
+ ["utf8_full_rune", [S], ["i32"]],
183
+ // fmt (arguments travel through the testing_arg_* buffer)
184
+ ["fmt_sprintf", [S], [S]],
185
+ ["fmt_printf", [S], []],
186
+ ["fmt_format_v", [S], [S]],
187
+ ["fmt_sscan_init", [S, "i32"], ["i32"]],
188
+ ["fmt_sscanf_init", [S, S], ["i32"]],
189
+ ["fmt_sscan_has_err", [], ["i32"]],
190
+ ["fmt_sscan_err_msg", [], [S]],
191
+ ["fmt_sscan_token_str", ["i32"], [S]],
192
+ ["fmt_sscan_token_i64", ["i32"], ["i64"]],
193
+ ["fmt_sscan_token_f64", ["i32"], ["f64"]],
194
+ ["fmt_sscan_token_bool", ["i32"], ["i32"]],
195
+ // testing.T.Run: the harness calls back into the exported __testing_run_cb
196
+ ["testing_run", [S, S, "anyref"], ["i32"]],
197
+ // unicode
198
+ ["unicode_is_letter", ["i32"], ["i32"]],
199
+ ["unicode_is_digit", ["i32"], ["i32"]],
200
+ ["unicode_is_space", ["i32"], ["i32"]],
201
+ ["unicode_is_upper", ["i32"], ["i32"]],
202
+ ["unicode_is_lower", ["i32"], ["i32"]],
203
+ ["unicode_is_punct", ["i32"], ["i32"]],
204
+ ["unicode_is_control", ["i32"], ["i32"]],
205
+ ["unicode_is_print", ["i32"], ["i32"]],
206
+ ["unicode_is_graphic", ["i32"], ["i32"]],
207
+ ["unicode_to_upper", ["i32"], ["i32"]],
208
+ ["unicode_to_lower", ["i32"], ["i32"]],
209
+ // html
210
+ ["html_escape", [S], [S]],
211
+ ["html_unescape", [S], [S]],
212
+ // path
213
+ ["path_base", [S], [S]],
214
+ ["path_dir", [S], [S]],
215
+ ["path_ext", [S], [S]],
216
+ ["path_is_abs", [S], ["i32"]],
217
+ ["path_clean", [S], [S]],
218
+ ["path_split", [S], []],
219
+ ["path_split_dir", [], [S]],
220
+ ["path_split_file", [], [S]],
221
+ ["path_join_init", [], []],
222
+ ["path_join_push", [S], []],
223
+ ["path_join_run", [], [S]],
224
+ ["path_match", [S, S], ["i32"]],
225
+ ["path_match_has_err", [], ["i32"]],
226
+ ["bytes_trim", [S, S, "i32", "i32"], []],
227
+ ["bytes_trim_start", [], ["i32"]],
228
+ ["bytes_trim_end", [], ["i32"]],
229
+ // time
230
+ ["time_now_ms", [], ["f64"]],
231
+ ["time_timezone_offset", ["f64"], ["i32"]],
232
+ ["time_parse", [S, S], ["f64"]],
233
+ ["time_parse_err", [], [S]],
234
+ ["time_parse_offset", [], ["i32"]],
235
+ [
236
+ "time_format",
237
+ ["i32", "i32", "i32", "i32", "i32", "i32", "i32", "i32", "i32", S],
238
+ [S],
239
+ ],
240
+ ["time_duration_string", ["i64"], [S]],
241
+ // raw binary buffer for crypto/rand, crypto/sha256, hex, base64
242
+ ["raw_bytes_init", ["i32"], []],
243
+ ["raw_bytes_push", ["i32"], []],
244
+ ["raw_bytes_len", [], ["i32"]],
245
+ ["raw_bytes_get", ["i32"], ["i32"]],
246
+ // crypto/rand
247
+ ["crypto_rand_fill", ["i32"], ["i32"]],
248
+ // crypto/sha256
249
+ ["crypto_sha256_sum", [], ["i32"]],
250
+ // encoding/hex
251
+ ["hex_encode_to_string", [], [S]],
252
+ ["hex_decode_string", [S], ["i32"]],
253
+ ["hex_get_err", [], [S]],
254
+ // encoding/base64
255
+ ["base64_encode_to_string", ["i32"], [S]],
256
+ ["base64_decode_string", ["i32", S], ["i32"]],
257
+ ["base64_get_err", [], [S]],
258
+ ];
259
+
260
+ class ModuleEmitter {
22
261
  constructor(checker, lowerResult = null, programs = [], options = {}) {
23
262
  this.checker = checker;
24
263
  this.lowerResult = lowerResult;
@@ -34,9 +273,13 @@ export class ModuleEmitter {
34
273
 
35
274
  this.structTypes = new Map(); // name -> struct info
36
275
  this.boxTypes = new Map(); // wType key -> box info
276
+ this.ifaceBoxTypes = new Map(); // key -> iface box info
37
277
  this.arrayTypes = new Map(); // wType key -> array info
38
278
  this.sliceTypes = new Map(); // wType key -> slice info
39
279
  this.sliceTypesByIndex = new Map(); // typeIndex -> slice info
280
+ this.mapTypes = new Map(); // wType key -> map info
281
+ this.mapTypesByIndex = new Map(); // typeIndex -> map info
282
+ this.mapBucketsType = null; // shared (array (mut i32))
40
283
  this.closureTypes = new Map(); // key -> closure info
41
284
  this.envTypes = new Map(); // key -> env info
42
285
 
@@ -54,11 +297,19 @@ export class ModuleEmitter {
54
297
  this.funcMap = new Map(); // name -> global func index
55
298
  this.funcParamTypes = new Map(); // name -> param types array
56
299
  this.funcParamGoTypes = new Map(); // name -> param Go types array
300
+ this.funcReturnTypesByIdx = new Map(); // global func index -> result wasm types
57
301
 
58
302
  this.exports = []; // export entries
59
303
  this.stringTable = []; // string literals
60
304
  this.stringCache = new Map();
61
305
 
306
+ // `errors.New` / `fmt.Errorf` / strconv parse failures allocate the
307
+ // runtime's own error struct; it joins the struct rec group so the
308
+ // `error.Error` dispatcher can `ref.test` it like any user type.
309
+ this.hasErrorType =
310
+ importsAnyOf(programs, ERROR_PRODUCING_PKGS) || hasErrorUsage(programs);
311
+ this.usesStdlibEnv = importsAnyOf(programs, STDLIB_ENV_PKGS);
312
+
62
313
  if (programs && programs.length > 0) {
63
314
  this.initTypes(programs);
64
315
  }
@@ -73,6 +324,20 @@ export class ModuleEmitter {
73
324
  const seen = new Set();
74
325
  const aliases = []; // qualified key (pkg.T) -> unqualified struct name
75
326
 
327
+ if (this.hasErrorType) {
328
+ seen.add(ERROR_STRUCT);
329
+ rawStructs.push({
330
+ name: ERROR_STRUCT,
331
+ structType: {
332
+ kind: "struct",
333
+ fields: new Map([
334
+ ["msg", { kind: "basic", name: "string" }],
335
+ ["cause", { kind: "basic", name: "any" }],
336
+ ]),
337
+ },
338
+ });
339
+ }
340
+
76
341
  for (const p of progs) {
77
342
  for (const d of p.decls ?? []) {
78
343
  if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
@@ -136,7 +401,8 @@ export class ModuleEmitter {
136
401
  for (let i = 0; i < rawStructs.length; i++) {
137
402
  const s = rawStructs[i];
138
403
  const info = this.structTypes.get(s.name);
139
- const resolvedStruct = this.checker?.types?.get(s.name)?.underlying;
404
+ const resolvedStruct =
405
+ s.structType ?? this.checker?.types?.get(s.name)?.underlying;
140
406
 
141
407
  const fields = [];
142
408
  const fieldIndexMap = new Map();
@@ -211,23 +477,95 @@ export class ModuleEmitter {
211
477
  return this.structTypes.get(name) ?? null;
212
478
  }
213
479
 
480
+ getAnonStructType(fields) {
481
+ const key = fields.map((f) => `${f.name}:${goTypeKey(f.goType)}`).join(";");
482
+ const name = `__anon_${key.replace(/[^a-zA-Z0-9_]/g, "_")}`;
483
+ if (this.structTypes.has(name)) {
484
+ return this.structTypes.get(name);
485
+ }
486
+ const fieldWTypes = fields.map((f) =>
487
+ toWasmType(f.goType, this.checker, this),
488
+ );
489
+ const typeIndex = this._getTotalTypeCount();
490
+ const typeEntry = {
491
+ form: "struct",
492
+ fields: fieldWTypes.map((t) => ({ type: t, mutable: true })),
493
+ };
494
+ this._pushType(typeEntry);
495
+ const info = {
496
+ name,
497
+ typeIndex,
498
+ typeEntry,
499
+ fields: fields.map((f, i) => ({
500
+ name: f.name,
501
+ wType: fieldWTypes[i],
502
+ goType: f.goType,
503
+ fieldIndex: i,
504
+ })),
505
+ fieldIndexMap: new Map(fields.map((f, i) => [f.name, i])),
506
+ embeds: [],
507
+ pkgName: null,
508
+ pkgTarget: "wasm",
509
+ isAnon: true,
510
+ };
511
+ this.structTypes.set(name, info);
512
+ return info;
513
+ }
514
+
214
515
  getBoxType(goType) {
215
- const wType = toWasmType(goType, this.checker, this);
216
- const key = typeof wType === "string" ? wType : JSON.stringify(wType);
516
+ const key = goTypeKey(goType);
217
517
  if (this.boxTypes.has(key)) {
218
518
  return this.boxTypes.get(key);
219
519
  }
520
+ const wType = toWasmType(goType, this.checker, this);
220
521
  const typeIndex = this._getTotalTypeCount();
221
522
  const typeEntry = {
222
523
  form: "struct",
223
524
  fields: [{ type: wType, mutable: true }],
224
525
  };
225
526
  this._pushType(typeEntry);
226
- const boxInfo = { typeIndex, typeEntry, wType };
527
+ const boxInfo = { key, typeIndex, typeEntry, wType, goType };
227
528
  this.boxTypes.set(key, boxInfo);
228
529
  return boxInfo;
229
530
  }
230
531
 
532
+ getIfaceBoxType(goType) {
533
+ const key = goTypeKey(goType);
534
+ if (this.ifaceBoxTypes.has(key)) {
535
+ return this.ifaceBoxTypes.get(key);
536
+ }
537
+ const wType = toWasmType(goType, this.checker, this);
538
+ const tagIndex = this.ifaceBoxTypes.size;
539
+ const tagTypeIndex = this._getTotalTypeCount();
540
+ const tagFields = [];
541
+ for (let i = 0; i < tagIndex; i++) {
542
+ tagFields.push({ type: "i32", mutable: false });
543
+ }
544
+ this._pushType({
545
+ form: "struct",
546
+ fields: tagFields,
547
+ });
548
+ const typeIndex = this._getTotalTypeCount();
549
+ const typeEntry = {
550
+ form: "struct",
551
+ fields: [
552
+ { type: wType, mutable: true },
553
+ {
554
+ type: {
555
+ kind: "ref",
556
+ nullable: true,
557
+ typeIndex: tagTypeIndex,
558
+ },
559
+ mutable: false,
560
+ },
561
+ ],
562
+ };
563
+ this._pushType(typeEntry);
564
+ const boxInfo = { key, typeIndex, typeEntry, wType, goType, tagTypeIndex };
565
+ this.ifaceBoxTypes.set(key, boxInfo);
566
+ return boxInfo;
567
+ }
568
+
231
569
  getArrayType(elemGoType) {
232
570
  const elemWType = toWasmType(elemGoType, this.checker, this);
233
571
  const key =
@@ -308,6 +646,107 @@ export class ModuleEmitter {
308
646
  return this.sliceTypesByIndex?.get(typeIndex) ?? null;
309
647
  }
310
648
 
649
+ getMapType(keyGoType, valGoType) {
650
+ const keyWType = toWasmType(keyGoType, this.checker, this);
651
+ const valWType = toWasmType(valGoType, this.checker, this);
652
+ const key = `${wTypeKey(keyWType)}:${wTypeKey(valWType)}`;
653
+ if (this.mapTypes.has(key)) {
654
+ return this.mapTypes.get(key);
655
+ }
656
+
657
+ if (!this.mapBucketsType) {
658
+ const typeIndex = this._getTotalTypeCount();
659
+ this._pushType({
660
+ form: "array",
661
+ elemType: "i32",
662
+ mutable: true,
663
+ });
664
+ this.mapBucketsType = { typeIndex };
665
+ }
666
+
667
+ const entryTypeIndex = this._getTotalTypeCount();
668
+ this._pushType({
669
+ form: "struct",
670
+ fields: [
671
+ { type: keyWType, mutable: true }, // 0: key
672
+ { type: valWType, mutable: true }, // 1: val
673
+ { type: "i32", mutable: true }, // 2: next (bucket chain)
674
+ { type: "i32", mutable: true }, // 3: order_prev (insertion order)
675
+ { type: "i32", mutable: true }, // 4: order_next (insertion order)
676
+ { type: "i32", mutable: true }, // 5: active (1 = active, 0 = deleted)
677
+ ],
678
+ });
679
+
680
+ const entriesTypeIndex = this._getTotalTypeCount();
681
+ this._pushType({
682
+ form: "array",
683
+ elemType: { kind: "ref", nullable: true, typeIndex: entryTypeIndex },
684
+ mutable: true,
685
+ });
686
+
687
+ const mapTypeIndex = this._getTotalTypeCount();
688
+ this._pushType({
689
+ form: "struct",
690
+ fields: [
691
+ {
692
+ type: {
693
+ kind: "ref",
694
+ nullable: true,
695
+ typeIndex: this.mapBucketsType.typeIndex,
696
+ },
697
+ mutable: true,
698
+ }, // 0: buckets
699
+ {
700
+ type: {
701
+ kind: "ref",
702
+ nullable: true,
703
+ typeIndex: entriesTypeIndex,
704
+ },
705
+ mutable: true,
706
+ }, // 1: entries
707
+ { type: "i32", mutable: true }, // 2: len
708
+ { type: "i32", mutable: true }, // 3: cap
709
+ { type: "i32", mutable: true }, // 4: count
710
+ { type: "i32", mutable: true }, // 5: head
711
+ { type: "i32", mutable: true }, // 6: tail
712
+ { type: "i32", mutable: true }, // 7: free_head
713
+ { type: "i32", mutable: true }, // 8: num_buckets
714
+ ],
715
+ });
716
+
717
+ const suffix = key.replace(/[^a-zA-Z0-9_]/g, "_");
718
+ const mapInfo = {
719
+ key,
720
+ suffix,
721
+ typeIndex: mapTypeIndex,
722
+ entryTypeIndex,
723
+ entriesTypeIndex,
724
+ bucketsTypeIndex: this.mapBucketsType.typeIndex,
725
+ keyWType,
726
+ valWType,
727
+ keyGoType,
728
+ valGoType,
729
+ wType: { kind: "ref", nullable: true, typeIndex: mapTypeIndex },
730
+ makeFuncName: `__map_make_${suffix}`,
731
+ getFuncName: `__map_get_${suffix}`,
732
+ getOkFuncName: `__map_get_ok_${suffix}`,
733
+ setFuncName: `__map_set_${suffix}`,
734
+ deleteFuncName: `__map_delete_${suffix}`,
735
+ lenFuncName: `__map_len_${suffix}`,
736
+ clearFuncName: `__map_clear_${suffix}`,
737
+ keysFuncName: `__map_keys_${suffix}`,
738
+ valuesFuncName: `__map_values_${suffix}`,
739
+ cloneFuncName: `__map_clone_${suffix}`,
740
+ };
741
+ this.mapTypes.set(key, mapInfo);
742
+ this.mapTypesByIndex.set(mapTypeIndex, mapInfo);
743
+ return mapInfo;
744
+ }
745
+
746
+ getMapTypeByIndex(typeIndex) {
747
+ return this.mapTypesByIndex?.get(typeIndex) ?? null;
748
+ }
749
+
311
750
  getClosureType(goType) {
312
751
  const sig = getFuncSignature(goType, this.checker);
313
752
  const paramWTypes = sig.params
@@ -399,9 +838,11 @@ export class ModuleEmitter {
399
838
 
400
839
  _initRuntimeImports() {
401
840
  this._initPanicTag();
841
+ this._initPanicGlobal();
402
842
  this.getPanicImportIndex(); // env.panic
403
843
  this.internString("runtime error: index out of range");
404
844
  this.internString("runtime error: slice bounds out of range");
845
+ this.internString("assignment to entry in nil map");
405
846
 
406
847
  // String built-ins
407
848
  this.getStringImportIndex(); // env.str
@@ -417,7 +858,51 @@ export class ModuleEmitter {
417
858
  this.getStringSliceImportIndex(); // env.str_slice
418
859
  this.getStringFromCodePointImportIndex(); // env.str_from_code_point
419
860
  this.getStringCodePointAtImportIndex(); // env.str_code_point_at
861
+ this.getStringHashImportIndex(); // env.str_hash
862
+ this.getStringToUpperImportIndex();
863
+ this.getStringToLowerImportIndex();
864
+ this.getStringTrimSpaceImportIndex();
865
+ this.getStringContainsImportIndex();
866
+ this.getStringHasPrefixImportIndex();
867
+ this.getStringHasSuffixImportIndex();
868
+ this.getStringIndexImportIndex();
869
+ this.getStringLastIndexImportIndex();
870
+ this.getStringRepeatImportIndex();
871
+ this.getStringReplaceAllImportIndex();
872
+ this.getStringEqualFoldImportIndex();
873
+ this.getStringCountImportIndex();
874
+ this.getStrFromI64ImportIndex();
875
+ this.getStrFromI32ImportIndex();
876
+ this.getStrFromF64ImportIndex();
420
877
  this.getIsStringImportIndex(); // env.is_string
878
+ this.getStrToBytesImportIndex();
879
+ this.getStrByteAtImportIndex();
880
+ this.getBytesToStrInitImportIndex();
881
+ this.getBytesToStrPushImportIndex();
882
+ this.getBytesToStrFinishImportIndex();
883
+ this.getTestingCallImportIndex();
884
+ this.getTestingNameImportIndex();
885
+ this.getTestingFlagImportIndex();
886
+ for (const t of ["i32", "i64", "f32", "f64", "externref", "anyref"]) {
887
+ this.getTestingArgImportIndex(t, false);
888
+ }
889
+ this.getTestingArgImportIndex("i32", true);
890
+ this.getFmtNewArrayIndex();
891
+ this.getFmtArrayPushIndex();
892
+ this.getFmtNewMapIndex();
893
+ this.getFmtMapSetIndex();
894
+ this.getFmtNewStructIndex();
895
+ this.getFmtStructSetIndex();
896
+ this.getFmtBoxI32Index();
897
+ this.getFmtBoxI64Index();
898
+ this.getFmtBoxF64Index();
899
+ this.getFmtBoxBoolIndex();
900
+ this.getFmtNewBoxIndex();
901
+ if (this.usesStdlibEnv) {
902
+ for (const [name, params, results] of STDLIB_IMPORTS) {
903
+ this.getOrAddFuncImport("env", name, params, results);
904
+ }
905
+ }
421
906
 
422
907
  // Logging built-ins
423
908
  this.getPrintlnEmptyIndex(); // env.println_empty
@@ -442,12 +927,12 @@ export class ModuleEmitter {
442
927
  "log2",
443
928
  "log10",
444
929
  "round",
930
+ "cbrt",
445
931
  ];
446
932
  for (const m of unaryMath) {
447
933
  this.getMathImportIndex(m, false);
448
934
  }
449
- const binaryMath = ["atan2", "pow"];
450
- for (const m of binaryMath) {
935
+ for (const m of BINARY_MATH_IMPORTS) {
451
936
  this.getMathImportIndex(m, true);
452
937
  }
453
938
  }
@@ -461,6 +946,188 @@ export class ModuleEmitter {
461
946
  this.exports.push({ name: "panicTag", kind: "tag", index: 0 });
462
947
  }
463
948
 
949
+ getPanicNodeTypeIndex() {
950
+ if (this.panicNodeTypeIndex !== undefined) {
951
+ return this.panicNodeTypeIndex;
952
+ }
953
+ const typeIndex = this._getTotalTypeCount();
954
+ const typeEntry = {
955
+ form: "rec",
956
+ types: [
957
+ {
958
+ form: "struct",
959
+ fields: [
960
+ {
961
+ type: "anyref",
962
+ mutable: true,
963
+ },
964
+ {
965
+ type: "i32",
966
+ mutable: true,
967
+ },
968
+ {
969
+ type: {
970
+ kind: "ref",
971
+ nullable: true,
972
+ typeIndex,
973
+ },
974
+ mutable: true,
975
+ },
976
+ ],
977
+ },
978
+ ],
979
+ };
980
+ this._pushType(typeEntry);
981
+ this.panicNodeTypeIndex = typeIndex;
982
+ return typeIndex;
983
+ }
984
+
985
+ _initPanicGlobal() {
986
+ const pType = this.getPanicNodeTypeIndex();
987
+ const gIdx = this.globals.length;
988
+ this.globals.push({
989
+ type: { kind: "ref", nullable: true, typeIndex: pType },
990
+ mutable: true,
991
+ init: [{ op: "ref.null", typeIndex: pType }],
992
+ });
993
+ this.panicGlobalIndex = gIdx;
994
+ this.exports.push({ name: "__panic", kind: "global", index: gIdx });
995
+ }
996
+
997
+ getPanicGlobalIndex() {
998
+ return this.panicGlobalIndex;
999
+ }
1000
+
1001
+ // `__deferArmed` is set to 1 immediately before a deferred closure is
1002
+ // invoked and cleared by that closure's prologue. recover() only takes
1003
+ // effect when the current frame observed the flag, which gives Go's
1004
+ // "recover must be called directly by the deferred function" rule.
1005
+ getDeferArmedGlobalIndex() {
1006
+ if (this.deferArmedGlobalIndex !== undefined) {
1007
+ return this.deferArmedGlobalIndex;
1008
+ }
1009
+ const gIdx = this.globals.length;
1010
+ this.globals.push({
1011
+ type: "i32",
1012
+ mutable: true,
1013
+ init: [{ op: "i32.const", value: 0 }],
1014
+ });
1015
+ this.deferArmedGlobalIndex = gIdx;
1016
+ return gIdx;
1017
+ }
1018
+
1019
+ getRandStateGlobalIndex() {
1020
+ if (this.randStateGlobalIndex !== undefined) {
1021
+ return this.randStateGlobalIndex;
1022
+ }
1023
+ const gIdx = this.globals.length;
1024
+ this.globals.push({
1025
+ type: "i64",
1026
+ mutable: true,
1027
+ init: [{ op: "i64.const", value: 1n }],
1028
+ });
1029
+ this.randStateGlobalIndex = gIdx;
1030
+ return gIdx;
1031
+ }
1032
+
1033
+ getStrconvErrGlobalIndex(field) {
1034
+ if (!this.strconvErrGlobals) this.strconvErrGlobals = new Map();
1035
+ if (this.strconvErrGlobals.has(field)) {
1036
+ return this.strconvErrGlobals.get(field);
1037
+ }
1038
+ const gIdx = this.globals.length;
1039
+ this.globals.push({
1040
+ type: "anyref",
1041
+ mutable: true,
1042
+ init: [{ op: "ref.null", heapType: "any" }],
1043
+ });
1044
+ this.strconvErrGlobals.set(field, gIdx);
1045
+ return gIdx;
1046
+ }
1047
+
1048
+ getPathErrGlobalIndex(field) {
1049
+ if (!this.pathErrGlobals) this.pathErrGlobals = new Map();
1050
+ if (this.pathErrGlobals.has(field)) {
1051
+ return this.pathErrGlobals.get(field);
1052
+ }
1053
+ const gIdx = this.globals.length;
1054
+ this.globals.push({
1055
+ type: "anyref",
1056
+ mutable: true,
1057
+ init: [{ op: "ref.null", heapType: "any" }],
1058
+ });
1059
+ this.pathErrGlobals.set(field, gIdx);
1060
+ return gIdx;
1061
+ }
1062
+
1063
+ getIoErrGlobalIndex(field) {
1064
+ if (!this.ioErrGlobals) this.ioErrGlobals = new Map();
1065
+ if (this.ioErrGlobals.has(field)) {
1066
+ return this.ioErrGlobals.get(field);
1067
+ }
1068
+ const gIdx = this.globals.length;
1069
+ this.globals.push({
1070
+ type: "anyref",
1071
+ mutable: true,
1072
+ init: [{ op: "ref.null", heapType: "any" }],
1073
+ });
1074
+ this.ioErrGlobals.set(field, gIdx);
1075
+ return gIdx;
1076
+ }
1077
+
1078
+ getIoDiscardGlobalIndex() {
1079
+ if (this.ioDiscardGlobalIndex !== undefined) {
1080
+ return this.ioDiscardGlobalIndex;
1081
+ }
1082
+ this.getStructType("io.discard");
1083
+ const gIdx = this.globals.length;
1084
+ this.globals.push({
1085
+ type: "anyref",
1086
+ mutable: true,
1087
+ init: [{ op: "ref.null", heapType: "any" }],
1088
+ });
1089
+ this.ioDiscardGlobalIndex = gIdx;
1090
+ return gIdx;
1091
+ }
1092
+
1093
+ getDeferNodeTypeIndex() {
1094
+ if (this.deferNodeTypeIndex !== undefined) {
1095
+ return this.deferNodeTypeIndex;
1096
+ }
1097
+ const deferClosureSig = { kind: "Signature", params: [], results: [] };
1098
+ const deferClosureInfo = this.getClosureType(deferClosureSig);
1099
+ const typeIndex = this._getTotalTypeCount();
1100
+ const typeEntry = {
1101
+ form: "rec",
1102
+ types: [
1103
+ {
1104
+ form: "struct",
1105
+ fields: [
1106
+ {
1107
+ type: {
1108
+ kind: "ref",
1109
+ nullable: true,
1110
+ typeIndex: deferClosureInfo.typeIndex,
1111
+ },
1112
+ mutable: false,
1113
+ },
1114
+ {
1115
+ type: {
1116
+ kind: "ref",
1117
+ nullable: true,
1118
+ typeIndex,
1119
+ },
1120
+ mutable: true,
1121
+ },
1122
+ ],
1123
+ },
1124
+ ],
1125
+ };
1126
+ this._pushType(typeEntry);
1127
+ this.deferNodeTypeIndex = typeIndex;
1128
+ return typeIndex;
1129
+ }
1130
+
464
1131
  getTypeIndex(params, results) {
465
1132
  const key = `${params.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${results.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
466
1133
  if (this.typeCache.has(key)) {
@@ -518,6 +1185,32 @@ export class ModuleEmitter {
518
1185
  return this.resolveFuncIndex("__bounds_panic");
519
1186
  }
520
1187
 
1188
+ // `gofront/shared` buffers are `(base i32, len i32)` views over the single
1189
+ // exported linear memory, bump-allocated by `__shared_new` during start.
1190
+ getSharedType() {
1191
+ if (this.sharedType) return this.sharedType;
1192
+ const typeIndex = this._getTotalTypeCount();
1193
+ const typeEntry = {
1194
+ form: "struct",
1195
+ fields: [
1196
+ { type: "i32", mutable: false }, // byte base
1197
+ { type: "i32", mutable: false }, // element count
1198
+ ],
1199
+ };
1200
+ this._pushType(typeEntry);
1201
+ this.memory = { min: 1 };
1202
+ this.exports.push({ name: "memory", kind: "memory", index: 0 });
1203
+ const topGlobalIndex = this.globals.length;
1204
+ this.globals.push({
1205
+ type: "i32",
1206
+ mutable: true,
1207
+ init: [{ op: "i32.const", value: 0 }],
1208
+ });
1209
+ const wType = { kind: "ref", nullable: true, typeIndex };
1210
+ this.sharedType = { typeIndex, typeEntry, wType, topGlobalIndex };
1211
+ return this.sharedType;
1212
+ }
1213
+
521
1214
  getSliceBoundsPanicFuncIndex() {
522
1215
  return this.resolveFuncIndex("__slice_bounds_panic");
523
1216
  }
@@ -526,6 +1219,90 @@ export class ModuleEmitter {
526
1219
  return this.getOrAddFuncImport("env", "println_empty", [], []);
527
1220
  }
528
1221
 
1222
+ getFmtNewArrayIndex() {
1223
+ return this.getOrAddFuncImport("env", "fmt_new_array", [], ["externref"]);
1224
+ }
1225
+
1226
+ getFmtArrayPushIndex() {
1227
+ return this.getOrAddFuncImport(
1228
+ "env",
1229
+ "fmt_array_push",
1230
+ ["externref", "externref"],
1231
+ [],
1232
+ );
1233
+ }
1234
+
1235
+ getFmtNewMapIndex() {
1236
+ return this.getOrAddFuncImport("env", "fmt_new_map", [], ["externref"]);
1237
+ }
1238
+
1239
+ getFmtMapSetIndex() {
1240
+ return this.getOrAddFuncImport(
1241
+ "env",
1242
+ "fmt_map_set",
1243
+ ["externref", "externref", "externref"],
1244
+ [],
1245
+ );
1246
+ }
1247
+
1248
+ getFmtNewStructIndex() {
1249
+ return this.getOrAddFuncImport(
1250
+ "env",
1251
+ "fmt_new_struct",
1252
+ ["anyref", "i32"],
1253
+ ["externref"],
1254
+ );
1255
+ }
1256
+
1257
+ getFmtStructSetIndex() {
1258
+ return this.getOrAddFuncImport(
1259
+ "env",
1260
+ "fmt_struct_set",
1261
+ ["externref", "i32", "externref"],
1262
+ [],
1263
+ );
1264
+ }
1265
+
1266
+ getFmtBoxI32Index() {
1267
+ return this.getOrAddFuncImport(
1268
+ "env",
1269
+ "fmt_box_i32",
1270
+ ["i32"],
1271
+ ["externref"],
1272
+ );
1273
+ }
1274
+
1275
+ getFmtBoxI64Index() {
1276
+ return this.getOrAddFuncImport(
1277
+ "env",
1278
+ "fmt_box_i64",
1279
+ ["i64"],
1280
+ ["externref"],
1281
+ );
1282
+ }
1283
+
1284
+ getFmtBoxF64Index() {
1285
+ return this.getOrAddFuncImport(
1286
+ "env",
1287
+ "fmt_box_f64",
1288
+ ["f64"],
1289
+ ["externref"],
1290
+ );
1291
+ }
1292
+
1293
+ getFmtBoxBoolIndex() {
1294
+ return this.getOrAddFuncImport(
1295
+ "env",
1296
+ "fmt_box_bool",
1297
+ ["i32"],
1298
+ ["externref"],
1299
+ );
1300
+ }
1301
+
1302
+ getFmtNewBoxIndex() {
1303
+ return this.getOrAddFuncImport("env", "fmt_new_box", [], ["externref"]);
1304
+ }
1305
+
529
1306
  // `*testing.T` method calls: args are pushed one by one to a JS-side
530
1307
  // buffer, then `testing_call(t, methodNameStrIdx)` invokes the method.
531
1308
  getTestingArgImportIndex(wType, isBool = false) {
@@ -535,7 +1312,7 @@ export class ModuleEmitter {
535
1312
  wType === "externref" ? "str" : wType === "anyref" ? "any" : wType;
536
1313
  if (!["i32", "i64", "f32", "f64", "str", "any"].includes(suffix)) {
537
1314
  throw new Error(
538
- "only primitive, string and any arguments are supported for testing.T methods in wasm packages",
1315
+ "only primitive, string, error and any arguments are supported for fmt and testing.T calls in wasm packages (format slices/structs field by field)",
539
1316
  );
540
1317
  }
541
1318
  return this.getOrAddFuncImport(
@@ -594,9 +1371,15 @@ export class ModuleEmitter {
594
1371
  ["externref"],
595
1372
  [],
596
1373
  );
597
- case "anyref":
598
- return this.getOrAddFuncImport("env", `${prefix}_any`, ["anyref"], []);
599
1374
  default:
1375
+ if (typeof wType === "object") {
1376
+ return this.getOrAddFuncImport(
1377
+ "env",
1378
+ `${prefix}_any`,
1379
+ ["anyref"],
1380
+ [],
1381
+ );
1382
+ }
600
1383
  return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
601
1384
  }
602
1385
  }
@@ -672,37 +1455,441 @@ export class ModuleEmitter {
672
1455
  return this.getOrAddFuncImport("env", "is_string", ["anyref"], ["i32"]);
673
1456
  }
674
1457
 
675
- getMathImportIndex(jsName, isBinary = false) {
676
- const params = isBinary ? ["f64", "f64"] : ["f64"];
677
- return this.getOrAddFuncImport("Math", jsName, params, ["f64"]);
678
- }
679
-
680
- resolveFuncIndex(name) {
681
- return this.funcMap.get(name) ?? null;
1458
+ getStringHashImportIndex() {
1459
+ return this.getOrAddFuncImport("env", "str_hash", ["externref"], ["i32"]);
682
1460
  }
683
1461
 
684
- getFuncParamTypes(name) {
685
- return this.funcParamTypes.get(name) ?? [];
1462
+ getStringToUpperImportIndex() {
1463
+ return this.getOrAddFuncImport(
1464
+ "env",
1465
+ "str_to_upper",
1466
+ ["externref"],
1467
+ ["externref"],
1468
+ );
686
1469
  }
687
1470
 
688
- getFuncParamGoTypes(name) {
689
- return this.funcParamGoTypes.get(name) ?? [];
1471
+ getStringToLowerImportIndex() {
1472
+ return this.getOrAddFuncImport(
1473
+ "env",
1474
+ "str_to_lower",
1475
+ ["externref"],
1476
+ ["externref"],
1477
+ );
690
1478
  }
691
1479
 
692
- resolveGlobal(name) {
693
- return this.globalCache.get(name) ?? null;
1480
+ getStringTrimSpaceImportIndex() {
1481
+ return this.getOrAddFuncImport(
1482
+ "env",
1483
+ "str_trim_space",
1484
+ ["externref"],
1485
+ ["externref"],
1486
+ );
694
1487
  }
695
1488
 
696
- resolveConst(name) {
1489
+ getStringContainsImportIndex() {
1490
+ return this.getOrAddFuncImport(
1491
+ "env",
1492
+ "str_contains",
1493
+ ["externref", "externref"],
1494
+ ["i32"],
1495
+ );
1496
+ }
1497
+
1498
+ getStringHasPrefixImportIndex() {
1499
+ return this.getOrAddFuncImport(
1500
+ "env",
1501
+ "str_has_prefix",
1502
+ ["externref", "externref"],
1503
+ ["i32"],
1504
+ );
1505
+ }
1506
+
1507
+ getStringHasSuffixImportIndex() {
1508
+ return this.getOrAddFuncImport(
1509
+ "env",
1510
+ "str_has_suffix",
1511
+ ["externref", "externref"],
1512
+ ["i32"],
1513
+ );
1514
+ }
1515
+
1516
+ getStringIndexImportIndex() {
1517
+ return this.getOrAddFuncImport(
1518
+ "env",
1519
+ "str_index",
1520
+ ["externref", "externref"],
1521
+ ["i32"],
1522
+ );
1523
+ }
1524
+
1525
+ getStringLastIndexImportIndex() {
1526
+ return this.getOrAddFuncImport(
1527
+ "env",
1528
+ "str_last_index",
1529
+ ["externref", "externref"],
1530
+ ["i32"],
1531
+ );
1532
+ }
1533
+
1534
+ getStringRepeatImportIndex() {
1535
+ return this.getOrAddFuncImport(
1536
+ "env",
1537
+ "str_repeat",
1538
+ ["externref", "i32"],
1539
+ ["externref"],
1540
+ );
1541
+ }
1542
+
1543
+ getStringReplaceAllImportIndex() {
1544
+ return this.getOrAddFuncImport(
1545
+ "env",
1546
+ "str_replace_all",
1547
+ ["externref", "externref", "externref"],
1548
+ ["externref"],
1549
+ );
1550
+ }
1551
+
1552
+ getStringEqualFoldImportIndex() {
1553
+ return this.getOrAddFuncImport(
1554
+ "env",
1555
+ "str_equal_fold",
1556
+ ["externref", "externref"],
1557
+ ["i32"],
1558
+ );
1559
+ }
1560
+
1561
+ getStringCountImportIndex() {
1562
+ return this.getOrAddFuncImport(
1563
+ "env",
1564
+ "str_count",
1565
+ ["externref", "externref"],
1566
+ ["i32"],
1567
+ );
1568
+ }
1569
+
1570
+ getStrFromI64ImportIndex() {
1571
+ return this.getOrAddFuncImport(
1572
+ "env",
1573
+ "str_from_i64",
1574
+ ["i64"],
1575
+ ["externref"],
1576
+ );
1577
+ }
1578
+
1579
+ getStrFromI32ImportIndex() {
1580
+ return this.getOrAddFuncImport(
1581
+ "env",
1582
+ "str_from_i32",
1583
+ ["i32"],
1584
+ ["externref"],
1585
+ );
1586
+ }
1587
+
1588
+ getStrFromF64ImportIndex() {
1589
+ return this.getOrAddFuncImport(
1590
+ "env",
1591
+ "str_from_f64",
1592
+ ["f64"],
1593
+ ["externref"],
1594
+ );
1595
+ }
1596
+
1597
+ getStrToBytesImportIndex() {
1598
+ return this.getOrAddFuncImport(
1599
+ "env",
1600
+ "str_to_bytes",
1601
+ ["externref"],
1602
+ ["i32"],
1603
+ );
1604
+ }
1605
+
1606
+ getStrByteAtImportIndex() {
1607
+ return this.getOrAddFuncImport("env", "str_byte_at", ["i32"], ["i32"]);
1608
+ }
1609
+
1610
+ getBytesToStrInitImportIndex() {
1611
+ return this.getOrAddFuncImport("env", "bytes_to_str_init", ["i32"], []);
1612
+ }
1613
+
1614
+ getBytesToStrPushImportIndex() {
1615
+ return this.getOrAddFuncImport("env", "bytes_to_str_push", ["i32"], []);
1616
+ }
1617
+
1618
+ getBytesToStrFinishImportIndex() {
1619
+ return this.getOrAddFuncImport(
1620
+ "env",
1621
+ "bytes_to_str_finish",
1622
+ [],
1623
+ ["externref"],
1624
+ );
1625
+ }
1626
+
1627
+ getMathImportIndex(jsName, isBinary = false) {
1628
+ const params = isBinary ? ["f64", "f64"] : ["f64"];
1629
+ return this.getOrAddFuncImport("Math", jsName, params, ["f64"]);
1630
+ }
1631
+
1632
+ getMathRandomImportIndex() {
1633
+ return this.getOrAddFuncImport("Math", "random", [], ["f64"]);
1634
+ }
1635
+
1636
+ // Imports from the STDLIB_IMPORTS table (strings/strconv/utf8/fmt/testing.Run).
1637
+ getStdlibImportIndex(name) {
1638
+ const idx = this.importCache.get(`env.${name}`);
1639
+ if (idx === undefined) throw new Error(`Unknown runtime import: ${name}`);
1640
+ return idx;
1641
+ }
1642
+
1643
+ getTimezoneOffsetImportIndex() {
1644
+ return this.getStdlibImportIndex("time_timezone_offset");
1645
+ }
1646
+
1647
+ // Closure type of `func(*testing.T)` (the t.Run body).
1648
+ getTestingRunClosureType() {
1649
+ return this.getClosureType({
1650
+ kind: "func",
1651
+ params: [{ kind: "pointer", base: { kind: "named", name: "testing.T" } }],
1652
+ returns: [],
1653
+ });
1654
+ }
1655
+
1656
+ resolveFuncIndex(name) {
1657
+ return this.funcMap.get(name) ?? null;
1658
+ }
1659
+
1660
+ getFuncParamTypes(name) {
1661
+ return this.funcParamTypes.get(name) ?? [];
1662
+ }
1663
+
1664
+ getFuncParamGoTypes(name) {
1665
+ return this.funcParamGoTypes.get(name) ?? [];
1666
+ }
1667
+
1668
+ resolveGlobal(name) {
1669
+ return this.globalCache.get(name) ?? null;
1670
+ }
1671
+
1672
+ resolveConst(name) {
697
1673
  return this.constCache.get(name) ?? null;
698
1674
  }
699
1675
 
700
1676
  isNonLiteralConst(name) {
701
1677
  return this.nonLiteralConsts.has(name);
702
1678
  }
1679
+
1680
+ // Interface method dispatch is a `ref.test` chain over every concrete type
1681
+ // that declares `methodName` (no itabs); the static interface type is not
1682
+ // needed to build the candidate list.
1683
+ findInterfaceCandidates(_ifaceType, methodName) {
1684
+ const candidates = [];
1685
+ const seenKeys = new Set();
1686
+
1687
+ // 1. Struct types
1688
+ for (const [sName, sInfo] of this.structTypes.entries()) {
1689
+ const directMethodName = `${sName}.${methodName}`;
1690
+ if (this.funcMap.has(directMethodName)) {
1691
+ const fIdx = this.resolveFuncIndex(directMethodName);
1692
+ const paramGoTypes = this.getFuncParamGoTypes(directMethodName);
1693
+ const paramWTypes = this.getFuncParamTypes(directMethodName);
1694
+ const recvGoType = paramGoTypes[0];
1695
+ const isPtrRecv = recvGoType?.kind === "pointer";
1696
+
1697
+ if (isPtrRecv) {
1698
+ const key = `ptr:${sName}`;
1699
+ if (!seenKeys.has(key)) {
1700
+ seenKeys.add(key);
1701
+ candidates.push({
1702
+ recvTypeName: sName,
1703
+ testTypeIndex: sInfo.typeIndex,
1704
+ isBoxedValue: false,
1705
+ needsValueDeref: false,
1706
+ embedPath: null,
1707
+ funcIndex: fIdx,
1708
+ targetParamTypes: paramWTypes,
1709
+ structInfo: sInfo,
1710
+ });
1711
+ }
1712
+ } else {
1713
+ // Value receiver: both *sName and sName match
1714
+ const ptrKey = `ptr:${sName}`;
1715
+ if (!seenKeys.has(ptrKey)) {
1716
+ seenKeys.add(ptrKey);
1717
+ candidates.push({
1718
+ recvTypeName: sName,
1719
+ testTypeIndex: sInfo.typeIndex,
1720
+ isBoxedValue: false,
1721
+ needsValueDeref: true,
1722
+ embedPath: null,
1723
+ funcIndex: fIdx,
1724
+ targetParamTypes: paramWTypes,
1725
+ targetRecvWType: paramWTypes[0],
1726
+ structInfo: sInfo,
1727
+ });
1728
+ }
1729
+ const box = this.getIfaceBoxType({ kind: "named", name: sName });
1730
+ const valKey = `val:${sName}`;
1731
+ if (box && !seenKeys.has(valKey)) {
1732
+ seenKeys.add(valKey);
1733
+ candidates.push({
1734
+ recvTypeName: sName,
1735
+ testTypeIndex: box.typeIndex,
1736
+ isBoxedValue: true,
1737
+ needsValueDeref: false,
1738
+ embedPath: null,
1739
+ funcIndex: fIdx,
1740
+ targetParamTypes: paramWTypes,
1741
+ targetRecvWType: paramWTypes[0],
1742
+ structInfo: sInfo,
1743
+ });
1744
+ }
1745
+ }
1746
+ }
1747
+
1748
+ // Promoted methods on sName
1749
+ if (sInfo.embeds && sInfo.embeds.length > 0) {
1750
+ for (const embed of sInfo.embeds) {
1751
+ const embedMethodName = `${embed.name}.${methodName}`;
1752
+ if (this.funcMap.has(embedMethodName)) {
1753
+ const fIdx = this.resolveFuncIndex(embedMethodName);
1754
+ const paramGoTypes = this.getFuncParamGoTypes(embedMethodName);
1755
+ const paramWTypes = this.getFuncParamTypes(embedMethodName);
1756
+ const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
1757
+ const embedSInfo = this.getStructType(embed.name);
1758
+
1759
+ const ptrKey = `ptr:${sName}`;
1760
+ if (!seenKeys.has(ptrKey)) {
1761
+ seenKeys.add(ptrKey);
1762
+ candidates.push({
1763
+ recvTypeName: sName,
1764
+ testTypeIndex: sInfo.typeIndex,
1765
+ isBoxedValue: false,
1766
+ needsValueDeref: !isPtrRecv,
1767
+ embedPath: [
1768
+ {
1769
+ parentTypeIndex: sInfo.typeIndex,
1770
+ fieldIndex: embed.fieldIndex,
1771
+ },
1772
+ ],
1773
+ funcIndex: fIdx,
1774
+ targetParamTypes: paramWTypes,
1775
+ targetRecvWType: paramWTypes[0],
1776
+ structInfo: embedSInfo,
1777
+ });
1778
+ }
1779
+
1780
+ if (!isPtrRecv) {
1781
+ const box = this.getIfaceBoxType({ kind: "named", name: sName });
1782
+ const valKey = `val:${sName}`;
1783
+ if (box && !seenKeys.has(valKey)) {
1784
+ seenKeys.add(valKey);
1785
+ candidates.push({
1786
+ recvTypeName: sName,
1787
+ testTypeIndex: box.typeIndex,
1788
+ isBoxedValue: true,
1789
+ needsValueDeref: false,
1790
+ embedPath: [
1791
+ {
1792
+ parentTypeIndex: sInfo.typeIndex,
1793
+ fieldIndex: embed.fieldIndex,
1794
+ },
1795
+ ],
1796
+ funcIndex: fIdx,
1797
+ targetParamTypes: paramWTypes,
1798
+ targetRecvWType: paramWTypes[0],
1799
+ structInfo: embedSInfo,
1800
+ });
1801
+ }
1802
+ }
1803
+ }
1804
+ }
1805
+ }
1806
+ }
1807
+
1808
+ // 2. Named non-struct types
1809
+ for (const [fnName, fIdx] of this.funcMap.entries()) {
1810
+ if (fnName.endsWith(`.${methodName}`)) {
1811
+ const rName = fnName.slice(0, fnName.length - methodName.length - 1);
1812
+ if (!this.structTypes.has(rName)) {
1813
+ const box = this.getIfaceBoxType({ kind: "named", name: rName });
1814
+ const key = `named:${rName}`;
1815
+ if (box && !seenKeys.has(key)) {
1816
+ seenKeys.add(key);
1817
+ const paramWTypes = this.getFuncParamTypes(fnName);
1818
+ candidates.push({
1819
+ recvTypeName: rName,
1820
+ testTypeIndex: box.typeIndex,
1821
+ isBoxedValue: true,
1822
+ needsValueDeref: false,
1823
+ embedPath: null,
1824
+ funcIndex: fIdx,
1825
+ targetParamTypes: paramWTypes,
1826
+ });
1827
+ }
1828
+ }
1829
+ }
1830
+ }
1831
+
1832
+ return candidates;
1833
+ }
1834
+
1835
+ getTypesImplementingInterface(ifaceType) {
1836
+ const candidates = [];
1837
+ const seen = new Set();
1838
+ const ifaceResolved =
1839
+ this.checker?.resolveType?.(
1840
+ ifaceType?.kind === "named" ? ifaceType.underlying : ifaceType,
1841
+ ) ?? ifaceType;
1842
+
1843
+ // 1. Struct types
1844
+ for (const [sName, sInfo] of this.structTypes.entries()) {
1845
+ const namedType = this.checker?.types?.get(sName) ?? {
1846
+ kind: "named",
1847
+ name: sName,
1848
+ };
1849
+ const ptrType = { kind: "pointer", base: namedType };
1850
+
1851
+ // Check pointer to struct
1852
+ if (this.checker?.implements?.(ptrType, ifaceResolved)) {
1853
+ if (!seen.has(sInfo.typeIndex)) {
1854
+ seen.add(sInfo.typeIndex);
1855
+ candidates.push({ name: `*${sName}`, typeIndex: sInfo.typeIndex });
1856
+ }
1857
+ }
1858
+
1859
+ // Check struct value (boxed)
1860
+ if (this.checker?.implements?.(namedType, ifaceResolved)) {
1861
+ const box = this.getIfaceBoxType(namedType);
1862
+ if (box && !seen.has(box.typeIndex)) {
1863
+ seen.add(box.typeIndex);
1864
+ candidates.push({ name: sName, typeIndex: box.typeIndex });
1865
+ }
1866
+ }
1867
+ }
1868
+
1869
+ // 2. Named non-struct types that have methods
1870
+ if (this.checker?.types) {
1871
+ for (const [tName, tVal] of this.checker.types.entries()) {
1872
+ if (
1873
+ tVal?.kind === "named" &&
1874
+ tVal.underlying?.kind !== "struct" &&
1875
+ tVal.underlying?.kind !== "interface"
1876
+ ) {
1877
+ if (this.checker.implements(tVal, ifaceResolved)) {
1878
+ const box = this.getIfaceBoxType(tVal);
1879
+ if (box && !seen.has(box.typeIndex)) {
1880
+ seen.add(box.typeIndex);
1881
+ candidates.push({ name: tName, typeIndex: box.typeIndex });
1882
+ }
1883
+ }
1884
+ }
1885
+ }
1886
+ }
1887
+
1888
+ return candidates;
1889
+ }
703
1890
  }
704
1891
 
705
- export function peepholeOptimize(instructions) {
1892
+ function peepholeOptimize(instructions) {
706
1893
  let changed = true;
707
1894
  let current = instructions;
708
1895
  while (changed) {
@@ -768,9 +1955,20 @@ export function compileWasmModule(
768
1955
  lowerResult = null,
769
1956
  options = {},
770
1957
  ) {
771
- const progs = Array.isArray(programs) ? programs : [programs];
772
- const resolvedLowerResult = lowerResult ?? lower(progs, checker);
773
- const mod = new ModuleEmitter(checker, resolvedLowerResult, progs, {
1958
+ let progs = Array.isArray(programs) ? programs : [programs];
1959
+ let resolvedChecker = checker;
1960
+ let resolvedLowerResult = lowerResult;
1961
+
1962
+ if (hasGenerics(progs)) {
1963
+ const mono = monomorphise(progs, checker);
1964
+ progs = mono.programs;
1965
+ resolvedChecker = mono.checker;
1966
+ resolvedLowerResult = lower(progs, resolvedChecker);
1967
+ } else if (!resolvedLowerResult) {
1968
+ resolvedLowerResult = lower(progs, resolvedChecker);
1969
+ }
1970
+
1971
+ const mod = new ModuleEmitter(resolvedChecker, resolvedLowerResult, progs, {
774
1972
  bundledPackages: options.bundledPackages,
775
1973
  });
776
1974
 
@@ -785,6 +1983,7 @@ export function compileWasmModule(
785
1983
  if (d.kind === "FuncDecl") {
786
1984
  d._pkgName = pkgName;
787
1985
  d._pkgTarget = pkgTarget;
1986
+ d._srcFile = p._path ?? p._filename ?? null;
788
1987
  if (d.name === "init" || d.name.startsWith("init$")) {
789
1988
  const renamed = initCount === 0 ? "init" : `init$${initCount}`;
790
1989
  d.name = renamed;
@@ -812,6 +2011,8 @@ export function compileWasmModule(
812
2011
  _sourceDecl: d,
813
2012
  _pkgName: pkgName,
814
2013
  _pkgTarget: pkgTarget,
2014
+ _srcFile: p._path ?? p._filename ?? null,
2015
+ _line: d._line,
815
2016
  };
816
2017
  funcDecls.push(normFn);
817
2018
  }
@@ -846,6 +2047,8 @@ export function compileWasmModule(
846
2047
  _isClosure: true,
847
2048
  _funcLit: node,
848
2049
  _rootFuncDecl: rootFn,
2050
+ _srcFile: rootFn?._srcFile ?? null,
2051
+ _line: typeof node._line === "number" ? node._line : rootFn?._line,
849
2052
  };
850
2053
  node._liftedFn = liftedFn;
851
2054
  closures.push(liftedFn);
@@ -928,8 +2131,19 @@ export function compileWasmModule(
928
2131
  }
929
2132
  funcDecls.push(...trampolines);
930
2133
 
2134
+ _collectPackageConsts(progs, mod, checker);
2135
+ if (checker?.errors?.length > 0) {
2136
+ if (options.boundary) {
2137
+ throw new Error(checker.errors.map((e) => e.message).join("\n"));
2138
+ }
2139
+ return {
2140
+ wasm: null,
2141
+ facade: null,
2142
+ stringTable: null,
2143
+ errors: checker.errors,
2144
+ };
2145
+ }
931
2146
  const pendingInits = _collectPackageGlobals(progs, mod, checker);
932
- _collectPackageConsts(progs, mod);
933
2147
  const globalInitFn =
934
2148
  pendingInits.length > 0 || initNames.length > 0
935
2149
  ? _makeGlobalInitFunc(pendingInits, initNames)
@@ -956,7 +2170,370 @@ export function compileWasmModule(
956
2170
  };
957
2171
  funcDecls.push(sliceBoundsPanicFn);
958
2172
 
959
- // 1d. Boundary metadata (exported surface of wasm packages)
2173
+ const pushPanicFn = {
2174
+ kind: "FuncDecl",
2175
+ name: "__push_panic",
2176
+ params: [{ name: "msg", type: { kind: "basic", name: "string" } }],
2177
+ returnType: null,
2178
+ _isPushPanic: true,
2179
+ };
2180
+ funcDecls.push(pushPanicFn);
2181
+
2182
+ // 1d. Interface dispatchers
2183
+ _collectInterfaceDispatchers(progs, funcDecls, mod, checker);
2184
+
2185
+ // 1e. Map helpers
2186
+ function scanMapTypes(node) {
2187
+ if (!node || typeof node !== "object") return;
2188
+ if (Array.isArray(node)) {
2189
+ for (const item of node) scanMapTypes(item);
2190
+ return;
2191
+ }
2192
+ if (node._type && isMapType(node._type, checker)) {
2193
+ const { keyType, valType } = getMapKeyValTypes(node._type, checker);
2194
+ if (keyType && valType) {
2195
+ mod.getMapType(keyType, valType);
2196
+ }
2197
+ }
2198
+ if (node.kind === "MapType") {
2199
+ const keyGoType = node.key;
2200
+ const valGoType = node.value ?? node.elem;
2201
+ if (keyGoType && valGoType) {
2202
+ mod.getMapType(keyGoType, valGoType);
2203
+ }
2204
+ }
2205
+ for (const key of Object.keys(node)) {
2206
+ if (key.startsWith("_")) continue;
2207
+ scanMapTypes(node[key]);
2208
+ }
2209
+ }
2210
+
2211
+ for (const fn of funcDecls) {
2212
+ if (fn.params) scanMapTypes(fn.params);
2213
+ if (fn.returnType) scanMapTypes(fn.returnType);
2214
+ if (fn.body) scanMapTypes(fn.body);
2215
+ }
2216
+ for (const p of progs) {
2217
+ for (const d of p.decls ?? []) {
2218
+ scanMapTypes(d);
2219
+ }
2220
+ }
2221
+
2222
+ for (const mapInfo of mod.mapTypes.values()) {
2223
+ // make
2224
+ funcDecls.push({
2225
+ kind: "FuncDecl",
2226
+ name: mapInfo.makeFuncName,
2227
+ params: [{ name: "cap", type: { kind: "basic", name: "int32" } }],
2228
+ returnType: mapInfo.wType,
2229
+ body: { kind: "Block", stmts: [], list: [] },
2230
+ _isMapHelper: true,
2231
+ _mapHelperKind: "make",
2232
+ _mapInfo: mapInfo,
2233
+ _rootFuncDecl: null,
2234
+ });
2235
+ // get
2236
+ funcDecls.push({
2237
+ kind: "FuncDecl",
2238
+ name: mapInfo.getFuncName,
2239
+ params: [
2240
+ { name: "m", type: mapInfo.wType },
2241
+ { name: "k", type: mapInfo.keyGoType },
2242
+ ],
2243
+ returnType: mapInfo.valGoType,
2244
+ body: { kind: "Block", stmts: [], list: [] },
2245
+ _isMapHelper: true,
2246
+ _mapHelperKind: "get",
2247
+ _mapInfo: mapInfo,
2248
+ _rootFuncDecl: null,
2249
+ });
2250
+ // get_ok
2251
+ funcDecls.push({
2252
+ kind: "FuncDecl",
2253
+ name: mapInfo.getOkFuncName,
2254
+ params: [
2255
+ { name: "m", type: mapInfo.wType },
2256
+ { name: "k", type: mapInfo.keyGoType },
2257
+ ],
2258
+ returnType: {
2259
+ kind: "tuple",
2260
+ types: [mapInfo.valGoType, { kind: "basic", name: "bool" }],
2261
+ },
2262
+ body: { kind: "Block", stmts: [], list: [] },
2263
+ _isMapHelper: true,
2264
+ _mapHelperKind: "get_ok",
2265
+ _mapInfo: mapInfo,
2266
+ _rootFuncDecl: null,
2267
+ });
2268
+ // set
2269
+ funcDecls.push({
2270
+ kind: "FuncDecl",
2271
+ name: mapInfo.setFuncName,
2272
+ params: [
2273
+ { name: "m", type: mapInfo.wType },
2274
+ { name: "k", type: mapInfo.keyGoType },
2275
+ { name: "v", type: mapInfo.valGoType },
2276
+ ],
2277
+ returnType: null,
2278
+ body: { kind: "Block", stmts: [], list: [] },
2279
+ _isMapHelper: true,
2280
+ _mapHelperKind: "set",
2281
+ _mapInfo: mapInfo,
2282
+ _rootFuncDecl: null,
2283
+ });
2284
+ // delete
2285
+ funcDecls.push({
2286
+ kind: "FuncDecl",
2287
+ name: mapInfo.deleteFuncName,
2288
+ params: [
2289
+ { name: "m", type: mapInfo.wType },
2290
+ { name: "k", type: mapInfo.keyGoType },
2291
+ ],
2292
+ returnType: null,
2293
+ body: { kind: "Block", stmts: [], list: [] },
2294
+ _isMapHelper: true,
2295
+ _mapHelperKind: "delete",
2296
+ _mapInfo: mapInfo,
2297
+ _rootFuncDecl: null,
2298
+ });
2299
+ // len
2300
+ funcDecls.push({
2301
+ kind: "FuncDecl",
2302
+ name: mapInfo.lenFuncName,
2303
+ params: [{ name: "m", type: mapInfo.wType }],
2304
+ returnType: { kind: "basic", name: "int32" },
2305
+ body: { kind: "Block", stmts: [], list: [] },
2306
+ _isMapHelper: true,
2307
+ _mapHelperKind: "len",
2308
+ _mapInfo: mapInfo,
2309
+ _rootFuncDecl: null,
2310
+ });
2311
+ // clear
2312
+ funcDecls.push({
2313
+ kind: "FuncDecl",
2314
+ name: mapInfo.clearFuncName,
2315
+ params: [{ name: "m", type: mapInfo.wType }],
2316
+ returnType: null,
2317
+ body: { kind: "Block", stmts: [], list: [] },
2318
+ _isMapHelper: true,
2319
+ _mapHelperKind: "clear",
2320
+ _mapInfo: mapInfo,
2321
+ _rootFuncDecl: null,
2322
+ });
2323
+ // keys
2324
+ funcDecls.push({
2325
+ kind: "FuncDecl",
2326
+ name: mapInfo.keysFuncName,
2327
+ params: [{ name: "m", type: mapInfo.wType }],
2328
+ returnType: { kind: "slice", elem: mapInfo.keyGoType },
2329
+ body: { kind: "Block", stmts: [], list: [] },
2330
+ _isMapHelper: true,
2331
+ _mapHelperKind: "keys",
2332
+ _mapInfo: mapInfo,
2333
+ _rootFuncDecl: null,
2334
+ });
2335
+ // values
2336
+ funcDecls.push({
2337
+ kind: "FuncDecl",
2338
+ name: mapInfo.valuesFuncName,
2339
+ params: [{ name: "m", type: mapInfo.wType }],
2340
+ returnType: { kind: "slice", elem: mapInfo.valGoType },
2341
+ body: { kind: "Block", stmts: [], list: [] },
2342
+ _isMapHelper: true,
2343
+ _mapHelperKind: "values",
2344
+ _mapInfo: mapInfo,
2345
+ _rootFuncDecl: null,
2346
+ });
2347
+ // clone
2348
+ funcDecls.push({
2349
+ kind: "FuncDecl",
2350
+ name: mapInfo.cloneFuncName,
2351
+ params: [{ name: "m", type: mapInfo.wType }],
2352
+ returnType: mapInfo.wType,
2353
+ body: { kind: "Block", stmts: [], list: [] },
2354
+ _isMapHelper: true,
2355
+ _mapHelperKind: "clone",
2356
+ _mapInfo: mapInfo,
2357
+ _rootFuncDecl: null,
2358
+ });
2359
+ }
2360
+
2361
+ // 1e2. `gofront/shared` linear-memory helpers (only when imported).
2362
+ const usesShared = progs.some((p) =>
2363
+ (p.imports ?? []).some((decl) =>
2364
+ (decl.imports ?? [decl]).some((imp) => imp.path === "gofront/shared"),
2365
+ ),
2366
+ );
2367
+ if (usesShared) {
2368
+ const sharedWType = mod.getSharedType().wType;
2369
+ const i32 = { kind: "basic", name: "int32" };
2370
+ funcDecls.push({
2371
+ kind: "FuncDecl",
2372
+ name: "__shared_len_panic",
2373
+ params: [],
2374
+ returnType: null,
2375
+ _isRuntimePanic: true,
2376
+ _panicMsg: "runtime error: shared buffer length out of range",
2377
+ });
2378
+ funcDecls.push({
2379
+ kind: "FuncDecl",
2380
+ name: "__shared_new",
2381
+ params: [
2382
+ { name: "len", type: i32 },
2383
+ { name: "bytes", type: i32 },
2384
+ ],
2385
+ returnType: sharedWType,
2386
+ body: { kind: "Block", stmts: [], list: [] },
2387
+ _isSharedHelper: "new",
2388
+ _rootFuncDecl: null,
2389
+ });
2390
+ funcDecls.push({
2391
+ kind: "FuncDecl",
2392
+ name: "__shared_sub",
2393
+ params: [
2394
+ { name: "buf", type: sharedWType },
2395
+ { name: "lo", type: i32 },
2396
+ { name: "hi", type: i32 },
2397
+ { name: "bytes", type: i32 },
2398
+ ],
2399
+ returnType: sharedWType,
2400
+ body: { kind: "Block", stmts: [], list: [] },
2401
+ _isSharedHelper: "sub",
2402
+ _rootFuncDecl: null,
2403
+ });
2404
+ }
2405
+
2406
+ // 1e3. Runtime helpers: error values, interface equality, t.Run callback.
2407
+ mod.usesTestingRun = funcDecls.some((fn) => _usesTestingRun(fn.body));
2408
+ mod.usesAnyEq = _usesAnyEq(funcDecls, mod);
2409
+ const ANY_T = { kind: "basic", name: "any" };
2410
+ const STR_T = { kind: "basic", name: "string" };
2411
+ const BOOL_T = { kind: "basic", name: "bool" };
2412
+ const helper = (name, params, returnType, kind, extra = {}) => ({
2413
+ kind: "FuncDecl",
2414
+ name,
2415
+ params,
2416
+ returnType,
2417
+ body: { kind: "Block", stmts: [], list: [] },
2418
+ _isRuntimeHelper: kind,
2419
+ _rootFuncDecl: null,
2420
+ ...extra,
2421
+ });
2422
+ if (mod.usesAnyEq) {
2423
+ funcDecls.push(
2424
+ helper(
2425
+ "__any_eq",
2426
+ [
2427
+ { name: "a", type: ANY_T },
2428
+ { name: "b", type: ANY_T },
2429
+ ],
2430
+ BOOL_T,
2431
+ "any_eq",
2432
+ ),
2433
+ );
2434
+ }
2435
+ mod.usesAnyHash = false;
2436
+ for (const info of mod.mapTypes.values()) {
2437
+ if (isInterfaceType(info.keyGoType, checker)) {
2438
+ mod.usesAnyHash = true;
2439
+ break;
2440
+ }
2441
+ }
2442
+ if (mod.usesAnyHash) {
2443
+ funcDecls.push(
2444
+ helper(
2445
+ "__any_hash",
2446
+ [{ name: "a", type: ANY_T }],
2447
+ { kind: "basic", name: "int32" },
2448
+ "any_hash",
2449
+ ),
2450
+ );
2451
+ }
2452
+ if (mod.hasErrorType) {
2453
+ const errPtr = {
2454
+ kind: "pointer",
2455
+ base: { kind: "named", name: ERROR_STRUCT },
2456
+ };
2457
+ funcDecls.push(
2458
+ helper(
2459
+ `${ERROR_STRUCT}.Error`,
2460
+ [{ name: "e", type: errPtr }],
2461
+ STR_T,
2462
+ "error_msg",
2463
+ {
2464
+ _isMethod: true,
2465
+ _recvTypeName: ERROR_STRUCT,
2466
+ _methodName: "Error",
2467
+ },
2468
+ ),
2469
+ helper(
2470
+ `${ERROR_STRUCT}.Unwrap`,
2471
+ [{ name: "e", type: errPtr }],
2472
+ ANY_T,
2473
+ "error_cause",
2474
+ {
2475
+ _isMethod: true,
2476
+ _recvTypeName: ERROR_STRUCT,
2477
+ _methodName: "Unwrap",
2478
+ },
2479
+ ),
2480
+ helper(
2481
+ "__mk_error",
2482
+ [
2483
+ { name: "msg", type: STR_T },
2484
+ { name: "cause", type: ANY_T },
2485
+ ],
2486
+ ANY_T,
2487
+ "mk_error",
2488
+ ),
2489
+ helper("__error_str", [{ name: "e", type: ANY_T }], STR_T, "error_str"),
2490
+ {
2491
+ kind: "FuncDecl",
2492
+ name: "__errors_unwrap",
2493
+ params: [{ name: "__recv", type: ANY_T }],
2494
+ returnType: ANY_T,
2495
+ body: { kind: "Block", stmts: [], list: [] },
2496
+ _isInterfaceDispatcher: true,
2497
+ _softDispatch: true,
2498
+ _ifaceName: "error",
2499
+ _ifaceType: null,
2500
+ _methodName: "Unwrap",
2501
+ _rootFuncDecl: null,
2502
+ },
2503
+ helper(
2504
+ "__errors_is",
2505
+ [
2506
+ { name: "err", type: ANY_T },
2507
+ { name: "target", type: ANY_T },
2508
+ ],
2509
+ BOOL_T,
2510
+ "errors_is",
2511
+ ),
2512
+ );
2513
+ }
2514
+ if (mod.usesTestingRun) {
2515
+ funcDecls.push(
2516
+ helper(
2517
+ "__testing_run_cb",
2518
+ [
2519
+ { name: "fn", type: ANY_T },
2520
+ {
2521
+ name: "t",
2522
+ type: {
2523
+ kind: "pointer",
2524
+ base: { kind: "named", name: "testing.T" },
2525
+ },
2526
+ },
2527
+ ],
2528
+ null,
2529
+ "testing_run_cb",
2530
+ { _forceExport: true },
2531
+ ),
2532
+ );
2533
+ }
2534
+ _registerIoSyntheticMethods(funcDecls, mod, STR_T, progs);
2535
+
2536
+ // 1f. Boundary metadata (exported surface of wasm packages)
960
2537
  const boundaryMeta = options.boundary
961
2538
  ? collectBoundaryMeta(progs, funcDecls, mod)
962
2539
  : null;
@@ -1009,14 +2586,21 @@ export function compileWasmModule(
1009
2586
  const typeIndex = mod.getTypeIndex(paramTypes, returnTypes);
1010
2587
  fn._typeIndex = typeIndex;
1011
2588
  fn._globalFuncIndex = globalIdx;
2589
+ mod.funcReturnTypesByIdx.set(globalIdx, returnTypes);
1012
2590
 
1013
2591
  // Export if public (capitalized) or main
1014
2592
  if (
1015
- !fn._isGlobalInit &&
1016
- (options.exportAll ||
1017
- fn.name === "main" ||
1018
- (fn.name[0] >= "A" && fn.name[0] <= "Z") ||
1019
- (fn._isMethod && fn._methodName[0] >= "A" && fn._methodName[0] <= "Z"))
2593
+ fn._forceExport ||
2594
+ (!fn._isGlobalInit &&
2595
+ !fn._isRuntimeHelper &&
2596
+ !fn._isIoHelper &&
2597
+ !fn._isPhase7Helper &&
2598
+ (options.exportAll ||
2599
+ fn.name === "main" ||
2600
+ (fn.name[0] >= "A" && fn.name[0] <= "Z") ||
2601
+ (fn._isMethod &&
2602
+ fn._methodName[0] >= "A" &&
2603
+ fn._methodName[0] <= "Z")))
1020
2604
  ) {
1021
2605
  mod.exports.push({ name: fn.name, kind: "func", index: globalIdx });
1022
2606
  if (fn._isMethod && fn._exportName) {
@@ -1027,6 +2611,16 @@ export function compileWasmModule(
1027
2611
  });
1028
2612
  }
1029
2613
  }
2614
+ if (
2615
+ fn._isPushPanic &&
2616
+ !mod.exports.some((e) => e.name === "__push_panic")
2617
+ ) {
2618
+ mod.exports.push({
2619
+ name: "__push_panic",
2620
+ kind: "func",
2621
+ index: globalIdx,
2622
+ });
2623
+ }
1030
2624
  }
1031
2625
 
1032
2626
  // 4. Emit function bodies
@@ -1037,7 +2631,31 @@ export function compileWasmModule(
1037
2631
  const funcIdx = mod.getStringImportIndex();
1038
2632
  emitter.pushInstruction({ op: "i32.const", value: strIdx });
1039
2633
  emitter.pushInstruction({ op: "call", funcIndex: funcIdx });
2634
+ emitter.pushInstruction("any.convert_extern");
1040
2635
  emitter.emitPanicThrow();
2636
+ } else if (fn._isPushPanic) {
2637
+ const panicNodeTypeIndex = mod.getPanicNodeTypeIndex();
2638
+ const panicGlobal = mod.getPanicGlobalIndex();
2639
+ emitter.pushInstruction({ op: "local.get", index: 0 });
2640
+ emitter.pushInstruction("any.convert_extern");
2641
+ emitter.pushInstruction({ op: "i32.const", value: 0 });
2642
+ emitter.pushInstruction({ op: "global.get", index: panicGlobal });
2643
+ emitter.pushInstruction({
2644
+ op: "struct.new",
2645
+ typeIndex: panicNodeTypeIndex,
2646
+ });
2647
+ emitter.pushInstruction({ op: "global.set", index: panicGlobal });
2648
+ emitter.pushInstruction("return");
2649
+ } else if (fn._isInterfaceDispatcher) {
2650
+ emitter.emitInterfaceDispatcher(fn);
2651
+ } else if (fn._isMapHelper) {
2652
+ emitter.emitMapHelper(fn);
2653
+ } else if (fn._isSharedHelper) {
2654
+ emitter.emitSharedHelper(fn);
2655
+ } else if (fn._isRuntimeHelper) {
2656
+ emitter.emitRuntimeHelper(fn);
2657
+ } else if (fn._isIoHelper || fn._isPhase7Helper) {
2658
+ emitter.emitIoHelper(fn);
1041
2659
  } else if (fn.body) {
1042
2660
  emitter.emitFunctionBody(fn.body);
1043
2661
  }
@@ -1061,6 +2679,10 @@ export function compileWasmModule(
1061
2679
  typeIndex: fn._typeIndex,
1062
2680
  locals: emitter.localTypes,
1063
2681
  body,
2682
+ src:
2683
+ fn._srcFile && typeof fn._line === "number"
2684
+ ? { file: fn._srcFile, line: fn._line }
2685
+ : null,
1064
2686
  });
1065
2687
  }
1066
2688
 
@@ -1071,6 +2693,7 @@ export function compileWasmModule(
1071
2693
  facade = generateFacade(boundaryMeta, {
1072
2694
  stringTable: mod.stringTable,
1073
2695
  callMain: Boolean(options.callMain) && mod.funcMap.has("main"),
2696
+ stdlibEnv: mod.usesStdlibEnv,
1074
2697
  });
1075
2698
  }
1076
2699
 
@@ -1082,44 +2705,150 @@ export function compileWasmModule(
1082
2705
  elements: mod.elements,
1083
2706
  funcs: mod.funcs,
1084
2707
  exports: mod.exports,
2708
+ memory: mod.memory ?? null,
1085
2709
  start: globalInitFn ? globalInitFn._globalFuncIndex : null,
2710
+ sourceMappingURL: options.sourceMappingURL ?? null,
1086
2711
  };
1087
2712
 
1088
2713
  const wasmBytes = encodeModule(moduleIR);
1089
2714
  const watText = options.emitWat ? emitWat(moduleIR) : null;
1090
2715
 
2716
+ // Function-level source positions: `sources` are the absolute Go files,
2717
+ // `mappings` pair each function body's byte offset with its 0-based line.
2718
+ const sources = [];
2719
+ const sourceIndex = new Map();
2720
+ const mappings = [];
2721
+ moduleIR.funcs.forEach((fn, i) => {
2722
+ if (!fn.src) return;
2723
+ let idx = sourceIndex.get(fn.src.file);
2724
+ if (idx === undefined) {
2725
+ idx = sources.length;
2726
+ sources.push(fn.src.file);
2727
+ sourceIndex.set(fn.src.file, idx);
2728
+ }
2729
+ mappings.push({
2730
+ offset: moduleIR.codeOffsets[i],
2731
+ srcFileIdx: idx,
2732
+ srcLine: Math.max(0, fn.src.line - 1),
2733
+ });
2734
+ });
2735
+
1091
2736
  return {
1092
2737
  wasm: wasmBytes,
1093
2738
  wat: watText,
1094
2739
  stringTable: mod.stringTable,
1095
2740
  moduleIR,
1096
2741
  facade,
2742
+ sourceMapInfo: { sources, mappings },
1097
2743
  };
1098
2744
  }
1099
2745
 
1100
- // Package-level `const` declarations with literal values (iota is already
1101
- // substituted by the parser). Non-literal constant expressions are not
1102
- // supported by the wasm backend yet.
1103
- function _collectPackageConsts(progs, mod) {
2746
+ // Package-level `const` declarations (iota is already substituted by the
2747
+ // parser). Literals and expressions the folder can reduce become BasicLits in
2748
+ // `constCache`; whatever is left is recorded in `nonLiteralConsts` and later
2749
+ // becomes a global initialised by the start function.
2750
+ function _collectPackageConsts(progs, mod, checker = null) {
2751
+ const entries = [];
1104
2752
  for (const p of progs) {
1105
2753
  for (const d of p.decls ?? []) {
1106
2754
  if (d.kind !== "ConstDecl") continue;
1107
2755
  for (const spec of d.decls ?? []) {
1108
2756
  for (let i = 0; i < spec.names.length; i++) {
1109
- let lit = spec.value?.[i];
1110
- if (
1111
- lit?.kind === "UnaryExpr" &&
1112
- lit.op === "-" &&
1113
- lit.operand?.kind === "BasicLit"
1114
- ) {
1115
- lit = { ...lit.operand, value: `-${lit.operand.value}` };
1116
- }
1117
- if (lit?.kind === "BasicLit") mod.constCache.set(spec.names[i], lit);
1118
- else if (lit) mod.nonLiteralConsts.add(spec.names[i]);
2757
+ if (spec.value?.[i])
2758
+ entries.push({
2759
+ name: spec.names[i],
2760
+ expr: spec.value[i],
2761
+ filename: p._filename,
2762
+ source: p._source,
2763
+ });
1119
2764
  }
1120
2765
  }
1121
2766
  }
1122
2767
  }
2768
+ const resolve = (name) => mod.constCache.get(name) ?? null;
2769
+ // Iterate to a fixpoint so a constant may refer to one declared later.
2770
+ let pending = entries;
2771
+ let progress = true;
2772
+ while (progress && pending.length > 0) {
2773
+ progress = false;
2774
+ const rest = [];
2775
+ for (const entry of pending) {
2776
+ let lit = null;
2777
+ try {
2778
+ lit = foldConstExpr(entry.expr, resolve);
2779
+ } catch (err) {
2780
+ if (checker?.err) {
2781
+ const savedFile = checker._currentFile;
2782
+ const savedSource = checker._currentSource;
2783
+ if (entry.filename) checker._currentFile = entry.filename;
2784
+ if (entry.source) checker._currentSource = entry.source;
2785
+ checker.err(err.message, entry.expr);
2786
+ checker._currentFile = savedFile;
2787
+ checker._currentSource = savedSource;
2788
+ } else {
2789
+ throw new Error(
2790
+ `Constant expression error for '${entry.name}': ${err.message}`,
2791
+ );
2792
+ }
2793
+ continue;
2794
+ }
2795
+ if (lit) {
2796
+ mod.constCache.set(entry.name, lit);
2797
+ progress = true;
2798
+ } else {
2799
+ rest.push(entry);
2800
+ }
2801
+ }
2802
+ pending = rest;
2803
+ }
2804
+ for (const entry of pending) mod.nonLiteralConsts.add(entry.name);
2805
+ }
2806
+
2807
+ // Depth-first search over AST nodes (skipping `_`-prefixed annotations).
2808
+ function _someNode(node, pred) {
2809
+ if (!node || typeof node !== "object") return false;
2810
+ if (Array.isArray(node)) return node.some((n) => _someNode(n, pred));
2811
+ if (pred(node)) return true;
2812
+ for (const key of Object.keys(node)) {
2813
+ if (key.startsWith("_")) continue;
2814
+ if (_someNode(node[key], pred)) return true;
2815
+ }
2816
+ return false;
2817
+ }
2818
+
2819
+ function _usesTestingRun(node) {
2820
+ return _someNode(
2821
+ node,
2822
+ (n) =>
2823
+ n.kind === "CallExpr" &&
2824
+ n.func?.kind === "SelectorExpr" &&
2825
+ n.func.field === "Run" &&
2826
+ isTestingT(n.func.expr?._type),
2827
+ );
2828
+ }
2829
+
2830
+ // `__any_eq` is only needed when some `==`/`!=` (or switch case) compares
2831
+ // anyref operands, a struct with an anyref field is compared, or the error
2832
+ // runtime (`errors.Is`) is present. `_emitAnyEq` throws if this misses a
2833
+ // site, so a gap surfaces as a compile error rather than a bad module.
2834
+ function _usesAnyEq(funcDecls, mod) {
2835
+ if (mod.hasErrorType) return true;
2836
+ for (const info of mod.mapTypes.values()) {
2837
+ if (isInterfaceType(info.keyGoType, mod.checker)) return true;
2838
+ }
2839
+ for (const info of mod.structTypes.values()) {
2840
+ if (info.fields?.some((f) => f.wType === "anyref")) return true;
2841
+ }
2842
+ const isAnyRef = (n) =>
2843
+ Boolean(n?._type) && toWasmType(n._type, mod.checker, mod) === "anyref";
2844
+ return funcDecls.some((fn) =>
2845
+ _someNode(fn.body, (n) => {
2846
+ if (n.kind === "BinaryExpr" && (n.op === "==" || n.op === "!="))
2847
+ return isAnyRef(n.left) || isAnyRef(n.right);
2848
+ if (n.kind === "SwitchStmt" && n.tag) return isAnyRef(n.tag);
2849
+ return false;
2850
+ }),
2851
+ );
1123
2852
  }
1124
2853
 
1125
2854
  function _scanImportsInBody(node, mod) {
@@ -1158,14 +2887,20 @@ function _scanImportsInBody(node, mod) {
1158
2887
  Atan: "atan",
1159
2888
  Atan2: "atan2",
1160
2889
  Pow: "pow",
2890
+ Mod: "mod",
2891
+ Hypot: "hypot",
2892
+ Cbrt: "cbrt",
1161
2893
  Exp: "exp",
1162
2894
  Log: "log",
1163
2895
  Log2: "log2",
1164
2896
  Log10: "log10",
1165
2897
  Round: "round",
2898
+ Sinh: "sinh",
2899
+ Cosh: "cosh",
2900
+ Tanh: "tanh",
1166
2901
  };
1167
2902
  if (jsMathMap[func.field]) {
1168
- const isBinary = func.field === "Atan2" || func.field === "Pow";
2903
+ const isBinary = BINARY_MATH_IMPORTS.has(jsMathMap[func.field]);
1169
2904
  mod.getMathImportIndex(jsMathMap[func.field], isBinary);
1170
2905
  }
1171
2906
  } else if (
@@ -1177,7 +2912,10 @@ function _scanImportsInBody(node, mod) {
1177
2912
  if (func.field === "Name") mod.getTestingNameImportIndex();
1178
2913
  else if (func.field === "Failed" || func.field === "Skipped")
1179
2914
  mod.getTestingFlagImportIndex();
1180
- else if (func.field !== "Run") {
2915
+ else if (func.field === "Run") {
2916
+ // closure type for func(*testing.T) must exist before bodies are emitted
2917
+ mod.getTestingRunClosureType();
2918
+ } else {
1181
2919
  mod.getTestingCallImportIndex();
1182
2920
  for (const arg of args) {
1183
2921
  mod.getTestingArgImportIndex(
@@ -1228,19 +2966,67 @@ export function compileWasm(source, options = {}) {
1228
2966
  exportAll: options.exportAll ?? true,
1229
2967
  });
1230
2968
 
2969
+ if (checker.errors?.length > 0) {
2970
+ return { wasm: null, errors: checker.errors };
2971
+ }
2972
+
1231
2973
  return { ...res, errors: [] };
1232
2974
  }
1233
2975
 
2976
+ function _isPointerGoType(t, mod) {
2977
+ if (!t) return false;
2978
+ if (t.kind === "pointer" || t.kind === "PointerType" || t.kind === "StarExpr")
2979
+ return true;
2980
+ // `type P *T` used as a field type: look through the name.
2981
+ const named =
2982
+ t.kind === "named" || t.kind === "TypeName" || t.kind === "Ident";
2983
+ const under = named
2984
+ ? (t.underlying ?? mod.checker?.types?.get(t.name)?.underlying)
2985
+ : null;
2986
+ return under ? _isPointerGoType(under, mod) : false;
2987
+ }
2988
+
2989
+ // Constant-expression zero value of a struct global. Nested struct values are
2990
+ // built in place: a null ref would trap on the first field access.
2991
+ function _zeroStructConst(structInfo, mod) {
2992
+ const out = [];
2993
+ for (const f of structInfo.fields) out.push(..._zeroFieldConst(f, mod));
2994
+ out.push({ op: "struct.new", typeIndex: structInfo.typeIndex });
2995
+ return out;
2996
+ }
2997
+
2998
+ function _zeroFieldConst(f, mod) {
2999
+ const w = f.wType;
3000
+ if (w === "i64") return [{ op: "i64.const", value: 0n }];
3001
+ if (w === "f32" || w === "f64") return [{ op: `${w}.const`, value: 0 }];
3002
+ if (w === "externref") return [{ op: "ref.null", heapType: "extern" }];
3003
+ if (w === "anyref") return [{ op: "ref.null", heapType: "any" }];
3004
+ if (typeof w !== "object" || w === null)
3005
+ return [{ op: "i32.const", value: 0 }];
3006
+ const ti = w.typeIndex;
3007
+ const slice = typeof ti === "number" ? mod.getSliceTypeByIndex(ti) : null;
3008
+ if (slice) return [{ op: "global.get", index: slice.emptyGlobalIndex }];
3009
+ if (typeof ti === "number" && !_isPointerGoType(f.goType, mod)) {
3010
+ for (const s of mod.structTypes.values())
3011
+ if (s.typeIndex === ti) return _zeroStructConst(s, mod);
3012
+ }
3013
+ return [{ op: "ref.null", heapType: ti ?? "any" }];
3014
+ }
3015
+
1234
3016
  function _collectPackageGlobals(progs, mod, checker) {
1235
3017
  const pending = []; // non-constant initializers, run by the start function
3018
+ const pendingConsts = []; // unfoldable consts; they run before any var
1236
3019
  for (const p of progs) {
1237
3020
  for (const d of p.decls ?? []) {
1238
- if (d.kind !== "VarDecl") continue;
3021
+ const isConst = d.kind === "ConstDecl";
3022
+ if (d.kind !== "VarDecl" && !isConst) continue;
1239
3023
  for (const spec of d.decls ?? d.specs ?? [d]) {
1240
3024
  const names = spec.names ?? (spec.name ? [spec.name] : []);
1241
3025
  const values = spec.value ?? (spec.init ? [spec.init] : []);
1242
3026
  for (let i = 0; i < names.length; i++) {
1243
3027
  const name = names[i];
3028
+ // Foldable consts live in `constCache`, not in a global.
3029
+ if (isConst && !mod.isNonLiteralConst(name)) continue;
1244
3030
  const rawType = spec.type ?? values[i]?._type;
1245
3031
  const wType = toWasmType(rawType, checker, mod);
1246
3032
  const gIdx = mod.globals.length;
@@ -1270,34 +3056,7 @@ function _collectPackageGlobals(progs, mod, checker) {
1270
3056
  (s) => s.typeIndex === wType.typeIndex,
1271
3057
  );
1272
3058
  if (structInfo) {
1273
- initInsts = [];
1274
- for (const f of structInfo.fields) {
1275
- if (f.wType === "i64")
1276
- initInsts.push({ op: "i64.const", value: 0n });
1277
- else if (f.wType === "f32" || f.wType === "f64")
1278
- initInsts.push({ op: `${f.wType}.const`, value: 0.0 });
1279
- else if (typeof f.wType === "object") {
1280
- const fieldSlice =
1281
- typeof f.wType.typeIndex === "number"
1282
- ? mod.getSliceTypeByIndex(f.wType.typeIndex)
1283
- : null;
1284
- if (fieldSlice) {
1285
- initInsts.push({
1286
- op: "global.get",
1287
- index: fieldSlice.emptyGlobalIndex,
1288
- });
1289
- } else {
1290
- initInsts.push({
1291
- op: "ref.null",
1292
- heapType: f.wType.typeIndex ?? "any",
1293
- });
1294
- }
1295
- } else initInsts.push({ op: "i32.const", value: 0 });
1296
- }
1297
- initInsts.push({
1298
- op: "struct.new",
1299
- typeIndex: structInfo.typeIndex,
1300
- });
3059
+ initInsts = _zeroStructConst(structInfo, mod);
1301
3060
  } else {
1302
3061
  initInsts = [
1303
3062
  { op: "ref.null", heapType: wType.typeIndex ?? "any" },
@@ -1332,15 +3091,17 @@ function _collectPackageGlobals(progs, mod, checker) {
1332
3091
  if (litNode && litNode.kind === "BasicLit") {
1333
3092
  if (litNode.litKind === "INT") {
1334
3093
  isConstInit = true;
3094
+ const s = String(litNode.value);
3095
+ const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
1335
3096
  initInsts = [
1336
3097
  wType === "i64"
1337
3098
  ? {
1338
3099
  op: "i64.const",
1339
- value: BigInt(sign) * BigInt(litNode.value),
3100
+ value: BigInt(sign) * BigInt(clean),
1340
3101
  }
1341
3102
  : {
1342
3103
  op: "i32.const",
1343
- value: (sign * Number(litNode.value)) | 0,
3104
+ value: (sign * Number(clean)) | 0,
1344
3105
  },
1345
3106
  ];
1346
3107
  } else if (litNode.litKind === "FLOAT") {
@@ -1362,7 +3123,10 @@ function _collectPackageGlobals(progs, mod, checker) {
1362
3123
  }
1363
3124
  }
1364
3125
  if (values[i] && !isConstInit && names.length === values.length) {
1365
- pending.push({ name, expr: values[i] });
3126
+ (isConst ? pendingConsts : pending).push({
3127
+ name,
3128
+ expr: values[i],
3129
+ });
1366
3130
  }
1367
3131
  mod.globals.push({
1368
3132
  type: wType,
@@ -1378,7 +3142,26 @@ function _collectPackageGlobals(progs, mod, checker) {
1378
3142
  }
1379
3143
  }
1380
3144
  }
1381
- return pending;
3145
+ return [..._orderConstInits(pendingConsts), ...pending];
3146
+ }
3147
+
3148
+ // Orders runtime-initialised consts so each runs after the consts it
3149
+ // references (Go allows a constant to use one declared later).
3150
+ function _orderConstInits(entries) {
3151
+ const byName = new Map(entries.map((e) => [e.name, e]));
3152
+ const ordered = [];
3153
+ const state = new Map();
3154
+ const visit = (entry) => {
3155
+ if (state.has(entry.name)) return;
3156
+ state.set(entry.name, true);
3157
+ _someNode(entry.expr, (n) => {
3158
+ if (n.kind === "Ident" && byName.has(n.name)) visit(byName.get(n.name));
3159
+ return false;
3160
+ });
3161
+ ordered.push(entry);
3162
+ };
3163
+ for (const e of entries) visit(e);
3164
+ return ordered;
1382
3165
  }
1383
3166
 
1384
3167
  // Synthesises `__init_globals()` assigning every non-constant package-level
@@ -1411,3 +3194,566 @@ function _makeGlobalInitFunc(pending, initNames = []) {
1411
3194
  _isGlobalInit: true,
1412
3195
  };
1413
3196
  }
3197
+
3198
+ function _scanInterfaceCallNodes(node, interfaceCalls, checker) {
3199
+ if (!node || typeof node !== "object") return;
3200
+ if (Array.isArray(node)) {
3201
+ for (const item of node)
3202
+ _scanInterfaceCallNodes(item, interfaceCalls, checker);
3203
+ return;
3204
+ }
3205
+ if (node.kind === "CallExpr" && node.func?.kind === "SelectorExpr") {
3206
+ const recvType = node.func.expr?._type;
3207
+ if (isNonEmptyInterface(recvType, checker)) {
3208
+ const ifaceName = getReceiverTypeName(recvType, node.func.expr) || "anon";
3209
+ const methodName = node.func.field;
3210
+ const key = `${ifaceName}.${methodName}`;
3211
+ if (!interfaceCalls.has(key)) {
3212
+ interfaceCalls.set(key, {
3213
+ ifaceName,
3214
+ ifaceType: recvType,
3215
+ methodName,
3216
+ callNode: node,
3217
+ });
3218
+ }
3219
+ }
3220
+ }
3221
+ for (const key of Object.keys(node)) {
3222
+ if (!key.startsWith("_"))
3223
+ _scanInterfaceCallNodes(node[key], interfaceCalls, checker);
3224
+ }
3225
+ }
3226
+
3227
+ function _scanInterfaceTypeDecls(progs, interfaceCalls, checker) {
3228
+ for (const p of progs) {
3229
+ for (const d of p.decls ?? []) {
3230
+ if (d.kind !== "TypeDecl") continue;
3231
+ const resolved = checker?.types?.get(d.name) ?? d.type;
3232
+ const underlying =
3233
+ resolved?.kind === "named"
3234
+ ? resolved.underlying
3235
+ : d.type?.kind === "InterfaceType"
3236
+ ? d.type
3237
+ : resolved;
3238
+ if (!isNonEmptyInterface(underlying, checker)) continue;
3239
+ const methods =
3240
+ underlying.methods instanceof Map
3241
+ ? Array.from(underlying.methods.entries())
3242
+ : Array.isArray(underlying.methods)
3243
+ ? underlying.methods.map((m) => [m.name ?? m, m])
3244
+ : [];
3245
+ for (const [mName, mSig] of methods) {
3246
+ const key = `${d.name}.${mName}`;
3247
+ if (!interfaceCalls.has(key)) {
3248
+ interfaceCalls.set(key, {
3249
+ ifaceName: d.name,
3250
+ ifaceType: resolved,
3251
+ methodName: mName,
3252
+ methodSig: mSig,
3253
+ });
3254
+ }
3255
+ }
3256
+ }
3257
+ }
3258
+ }
3259
+
3260
+ function _makeInterfaceDispatcher(info, checker) {
3261
+ let methodSig = info.methodSig;
3262
+ if (!methodSig) {
3263
+ const ifaceResolved =
3264
+ checker?.resolveType?.(
3265
+ info.ifaceType?.kind === "named"
3266
+ ? info.ifaceType.underlying
3267
+ : info.ifaceType,
3268
+ ) ?? info.ifaceType;
3269
+ if (ifaceResolved?.methods instanceof Map) {
3270
+ methodSig = ifaceResolved.methods.get(info.methodName);
3271
+ }
3272
+ }
3273
+
3274
+ const params = [{ name: "__recv", type: { kind: "basic", name: "any" } }];
3275
+ if (methodSig?.params) {
3276
+ for (let p = 0; p < methodSig.params.length; p++) {
3277
+ params.push({ name: `__arg${p}`, type: methodSig.params[p] });
3278
+ }
3279
+ } else if (info.callNode?.args) {
3280
+ for (let p = 0; p < info.callNode.args.length; p++) {
3281
+ params.push({
3282
+ name: `__arg${p}`,
3283
+ type: info.callNode.args[p]._type ?? {
3284
+ kind: "basic",
3285
+ name: "any",
3286
+ },
3287
+ });
3288
+ }
3289
+ }
3290
+
3291
+ let returnType = info.callNode?._type ?? null;
3292
+ if (methodSig?.returns) {
3293
+ returnType =
3294
+ methodSig.returns.length === 1
3295
+ ? methodSig.returns[0]
3296
+ : methodSig.returns.length > 1
3297
+ ? { kind: "tuple", types: methodSig.returns }
3298
+ : null;
3299
+ }
3300
+
3301
+ return {
3302
+ kind: "FuncDecl",
3303
+ name: `__dispatch_${info.ifaceName}_${info.methodName}`,
3304
+ params,
3305
+ returnType,
3306
+ body: { kind: "Block", stmts: [], list: [] },
3307
+ _isInterfaceDispatcher: true,
3308
+ _ifaceName: info.ifaceName,
3309
+ _ifaceType: info.ifaceType,
3310
+ _methodName: info.methodName,
3311
+ _rootFuncDecl: null,
3312
+ };
3313
+ }
3314
+
3315
+ function _scanFuncSigInterfaces(
3316
+ progs,
3317
+ funcDecls,
3318
+ interfaceCalls,
3319
+ checker,
3320
+ mod,
3321
+ ) {
3322
+ const scanType = (t) => {
3323
+ if (!t) return;
3324
+ if (t.kind === "InterfaceType" || (t.kind === "interface" && !t.name)) {
3325
+ const mList = getInterfaceMethodList(t);
3326
+ if (mList.length === 0) return;
3327
+ const ifaceName = anonIfaceName(t, checker, mod);
3328
+ for (const m of mList) {
3329
+ const key = `${ifaceName}.${m.name}`;
3330
+ if (!interfaceCalls.has(key)) {
3331
+ interfaceCalls.set(key, {
3332
+ ifaceName,
3333
+ ifaceType: t,
3334
+ methodName: m.name,
3335
+ methodSig: {
3336
+ kind: "func",
3337
+ params: m.params,
3338
+ returns: m.returns,
3339
+ },
3340
+ });
3341
+ }
3342
+ }
3343
+ }
3344
+ };
3345
+ for (const fn of funcDecls) {
3346
+ for (const p of fn.params ?? []) scanType(p.type);
3347
+ scanType(fn.returnType);
3348
+ }
3349
+ for (const p of progs) {
3350
+ for (const d of p.decls ?? []) {
3351
+ if (d.kind === "FuncDecl") {
3352
+ for (const param of d.params ?? []) scanType(param.type);
3353
+ scanType(d.returnType);
3354
+ }
3355
+ }
3356
+ }
3357
+ }
3358
+
3359
+ function _collectInterfaceDispatchers(progs, funcDecls, mod, checker) {
3360
+ const interfaceCalls = new Map();
3361
+ for (const fn of funcDecls) {
3362
+ if (fn.body) _scanInterfaceCallNodes(fn.body, interfaceCalls, checker);
3363
+ }
3364
+ _scanInterfaceTypeDecls(progs, interfaceCalls, checker);
3365
+ _scanFuncSigInterfaces(progs, funcDecls, interfaceCalls, checker, mod);
3366
+
3367
+ // `error.Error()` is reached implicitly by fmt verbs and errors.Is, so
3368
+ // the dispatcher must exist even when user code never calls it.
3369
+ if (mod.hasErrorType && !interfaceCalls.has("error.Error")) {
3370
+ interfaceCalls.set("error.Error", {
3371
+ ifaceName: "error",
3372
+ ifaceType: checker?.types?.get("error") ?? {
3373
+ kind: "interface",
3374
+ name: "error",
3375
+ },
3376
+ methodName: "Error",
3377
+ methodSig: {
3378
+ kind: "func",
3379
+ params: [],
3380
+ returns: [{ kind: "basic", name: "string" }],
3381
+ },
3382
+ });
3383
+ }
3384
+
3385
+ const hasIo = importsAnyOf(progs, [
3386
+ "io",
3387
+ "fmt",
3388
+ "strings",
3389
+ "bytes",
3390
+ "crypto/sha256",
3391
+ "sha256",
3392
+ "encoding/binary",
3393
+ "binary",
3394
+ "encoding/hex",
3395
+ "hex",
3396
+ "encoding/base64",
3397
+ "base64",
3398
+ ]);
3399
+ if (hasIo) {
3400
+ const byteSliceType = {
3401
+ kind: "slice",
3402
+ elem: { kind: "basic", name: "byte" },
3403
+ };
3404
+ const intErrReturns = [
3405
+ { kind: "basic", name: "int" },
3406
+ { kind: "interface", name: "error" },
3407
+ ];
3408
+ if (!interfaceCalls.has("io.Reader.Read")) {
3409
+ interfaceCalls.set("io.Reader.Read", {
3410
+ ifaceName: "io.Reader",
3411
+ ifaceType: checker?.types?.get("io.Reader"),
3412
+ methodName: "Read",
3413
+ methodSig: {
3414
+ kind: "func",
3415
+ params: [byteSliceType],
3416
+ returns: intErrReturns,
3417
+ },
3418
+ });
3419
+ }
3420
+ if (!interfaceCalls.has("io.Writer.Write")) {
3421
+ interfaceCalls.set("io.Writer.Write", {
3422
+ ifaceName: "io.Writer",
3423
+ ifaceType: checker?.types?.get("io.Writer"),
3424
+ methodName: "Write",
3425
+ methodSig: {
3426
+ kind: "func",
3427
+ params: [byteSliceType],
3428
+ returns: intErrReturns,
3429
+ },
3430
+ });
3431
+ }
3432
+ }
3433
+
3434
+ for (const info of interfaceCalls.values()) {
3435
+ funcDecls.push(_makeInterfaceDispatcher(info, checker));
3436
+ }
3437
+ }
3438
+
3439
+ function _registerIoSyntheticMethods(funcDecls, mod, strType, programs) {
3440
+ const progs = Array.isArray(programs) ? programs : [programs];
3441
+ const hasStrings = importsAnyOf(progs, ["strings"]);
3442
+ const hasBytes = importsAnyOf(progs, ["bytes"]);
3443
+ const hasIo = importsAnyOf(progs, ["io"]);
3444
+
3445
+ const BYTE_SLICE_T = {
3446
+ kind: "slice",
3447
+ elem: { kind: "basic", name: "byte" },
3448
+ };
3449
+ const INT_T = { kind: "basic", name: "int" };
3450
+ const ERR_T = { kind: "interface", name: "error" };
3451
+ const INT_ERR_RET = { kind: "tuple", types: [INT_T, ERR_T] };
3452
+ const ioHelper = (name, params, returnType, kind, extra = {}) => ({
3453
+ kind: "FuncDecl",
3454
+ name,
3455
+ params,
3456
+ returnType,
3457
+ body: { kind: "Block", stmts: [], list: [] },
3458
+ _isIoHelper: kind,
3459
+ _rootFuncDecl: null,
3460
+ ...extra,
3461
+ });
3462
+
3463
+ if (hasStrings && mod.structTypes.has("strings.Builder")) {
3464
+ const bPtr = {
3465
+ kind: "pointer",
3466
+ base: { kind: "named", name: "strings.Builder" },
3467
+ };
3468
+ funcDecls.push(
3469
+ ioHelper(
3470
+ "strings.Builder.Write",
3471
+ [
3472
+ { name: "b", type: bPtr },
3473
+ { name: "p", type: BYTE_SLICE_T },
3474
+ ],
3475
+ INT_ERR_RET,
3476
+ "builder_write",
3477
+ {
3478
+ _isMethod: true,
3479
+ _recvTypeName: "strings.Builder",
3480
+ _methodName: "Write",
3481
+ },
3482
+ ),
3483
+ ioHelper(
3484
+ "strings.Builder.WriteString",
3485
+ [
3486
+ { name: "b", type: bPtr },
3487
+ { name: "s", type: strType },
3488
+ ],
3489
+ INT_ERR_RET,
3490
+ "builder_write_string",
3491
+ {
3492
+ _isMethod: true,
3493
+ _recvTypeName: "strings.Builder",
3494
+ _methodName: "WriteString",
3495
+ },
3496
+ ),
3497
+ );
3498
+ }
3499
+ if (hasBytes && mod.structTypes.has("bytes.Buffer")) {
3500
+ const bPtr = {
3501
+ kind: "pointer",
3502
+ base: { kind: "named", name: "bytes.Buffer" },
3503
+ };
3504
+ funcDecls.push(
3505
+ ioHelper(
3506
+ "bytes.Buffer.Write",
3507
+ [
3508
+ { name: "b", type: bPtr },
3509
+ { name: "p", type: BYTE_SLICE_T },
3510
+ ],
3511
+ INT_ERR_RET,
3512
+ "buffer_write",
3513
+ {
3514
+ _isMethod: true,
3515
+ _recvTypeName: "bytes.Buffer",
3516
+ _methodName: "Write",
3517
+ },
3518
+ ),
3519
+ ioHelper(
3520
+ "bytes.Buffer.WriteString",
3521
+ [
3522
+ { name: "b", type: bPtr },
3523
+ { name: "s", type: strType },
3524
+ ],
3525
+ INT_ERR_RET,
3526
+ "buffer_write_string",
3527
+ {
3528
+ _isMethod: true,
3529
+ _recvTypeName: "bytes.Buffer",
3530
+ _methodName: "WriteString",
3531
+ },
3532
+ ),
3533
+ ioHelper(
3534
+ "bytes.Buffer.Read",
3535
+ [
3536
+ { name: "b", type: bPtr },
3537
+ { name: "p", type: BYTE_SLICE_T },
3538
+ ],
3539
+ INT_ERR_RET,
3540
+ "buffer_read",
3541
+ {
3542
+ _isMethod: true,
3543
+ _recvTypeName: "bytes.Buffer",
3544
+ _methodName: "Read",
3545
+ },
3546
+ ),
3547
+ );
3548
+ }
3549
+ if (hasStrings && mod.structTypes.has("strings.Reader")) {
3550
+ const rPtr = {
3551
+ kind: "pointer",
3552
+ base: { kind: "named", name: "strings.Reader" },
3553
+ };
3554
+ funcDecls.push(
3555
+ ioHelper(
3556
+ "strings.Reader.Read",
3557
+ [
3558
+ { name: "r", type: rPtr },
3559
+ { name: "p", type: BYTE_SLICE_T },
3560
+ ],
3561
+ INT_ERR_RET,
3562
+ "strings_reader_read",
3563
+ {
3564
+ _isMethod: true,
3565
+ _recvTypeName: "strings.Reader",
3566
+ _methodName: "Read",
3567
+ },
3568
+ ),
3569
+ ioHelper(
3570
+ "strings.Reader.Len",
3571
+ [{ name: "r", type: rPtr }],
3572
+ INT_T,
3573
+ "strings_reader_len",
3574
+ {
3575
+ _isMethod: true,
3576
+ _recvTypeName: "strings.Reader",
3577
+ _methodName: "Len",
3578
+ },
3579
+ ),
3580
+ ioHelper(
3581
+ "strings.Reader.Reset",
3582
+ [
3583
+ { name: "r", type: rPtr },
3584
+ { name: "s", type: strType },
3585
+ ],
3586
+ null,
3587
+ "strings_reader_reset",
3588
+ {
3589
+ _isMethod: true,
3590
+ _recvTypeName: "strings.Reader",
3591
+ _methodName: "Reset",
3592
+ },
3593
+ ),
3594
+ );
3595
+ }
3596
+ if (hasBytes && mod.structTypes.has("bytes.Reader")) {
3597
+ const rPtr = {
3598
+ kind: "pointer",
3599
+ base: { kind: "named", name: "bytes.Reader" },
3600
+ };
3601
+ funcDecls.push(
3602
+ ioHelper(
3603
+ "bytes.Reader.Read",
3604
+ [
3605
+ { name: "r", type: rPtr },
3606
+ { name: "p", type: BYTE_SLICE_T },
3607
+ ],
3608
+ INT_ERR_RET,
3609
+ "bytes_reader_read",
3610
+ { _isMethod: true, _recvTypeName: "bytes.Reader", _methodName: "Read" },
3611
+ ),
3612
+ ioHelper(
3613
+ "bytes.Reader.Len",
3614
+ [{ name: "r", type: rPtr }],
3615
+ INT_T,
3616
+ "bytes_reader_len",
3617
+ { _isMethod: true, _recvTypeName: "bytes.Reader", _methodName: "Len" },
3618
+ ),
3619
+ ioHelper(
3620
+ "bytes.Reader.Reset",
3621
+ [
3622
+ { name: "r", type: rPtr },
3623
+ { name: "b", type: BYTE_SLICE_T },
3624
+ ],
3625
+ null,
3626
+ "bytes_reader_reset",
3627
+ {
3628
+ _isMethod: true,
3629
+ _recvTypeName: "bytes.Reader",
3630
+ _methodName: "Reset",
3631
+ },
3632
+ ),
3633
+ );
3634
+ }
3635
+ if (hasIo && mod.structTypes.has("io.discard")) {
3636
+ const dPtr = {
3637
+ kind: "pointer",
3638
+ base: { kind: "named", name: "io.discard" },
3639
+ };
3640
+ funcDecls.push(
3641
+ ioHelper(
3642
+ "io.discard.Write",
3643
+ [
3644
+ { name: "d", type: dPtr },
3645
+ { name: "p", type: BYTE_SLICE_T },
3646
+ ],
3647
+ INT_ERR_RET,
3648
+ "discard_write",
3649
+ { _isMethod: true, _recvTypeName: "io.discard", _methodName: "Write" },
3650
+ ),
3651
+ ioHelper(
3652
+ "io.discard.WriteString",
3653
+ [
3654
+ { name: "d", type: dPtr },
3655
+ { name: "s", type: strType },
3656
+ ],
3657
+ INT_ERR_RET,
3658
+ "discard_write_string",
3659
+ {
3660
+ _isMethod: true,
3661
+ _recvTypeName: "io.discard",
3662
+ _methodName: "WriteString",
3663
+ },
3664
+ ),
3665
+ );
3666
+ }
3667
+ if (hasIo && mod.structTypes.has("io.LimitedReader")) {
3668
+ const lrPtr = {
3669
+ kind: "pointer",
3670
+ base: { kind: "named", name: "io.LimitedReader" },
3671
+ };
3672
+ funcDecls.push(
3673
+ ioHelper(
3674
+ "io.LimitedReader.Read",
3675
+ [
3676
+ { name: "l", type: lrPtr },
3677
+ { name: "p", type: BYTE_SLICE_T },
3678
+ ],
3679
+ INT_ERR_RET,
3680
+ "limited_reader_read",
3681
+ {
3682
+ _isMethod: true,
3683
+ _recvTypeName: "io.LimitedReader",
3684
+ _methodName: "Read",
3685
+ },
3686
+ ),
3687
+ );
3688
+ }
3689
+ const hasSha256 = importsAnyOf(progs, ["crypto/sha256", "sha256"]);
3690
+ if (
3691
+ hasSha256 &&
3692
+ (mod.structTypes.has("sha256.digest") ||
3693
+ mod.structTypes.has("crypto/sha256.digest"))
3694
+ ) {
3695
+ const dPtr = {
3696
+ kind: "pointer",
3697
+ base: { kind: "named", name: "sha256.digest" },
3698
+ };
3699
+ funcDecls.push(
3700
+ ioHelper(
3701
+ "sha256.digest.Write",
3702
+ [
3703
+ { name: "d", type: dPtr },
3704
+ { name: "p", type: BYTE_SLICE_T },
3705
+ ],
3706
+ INT_ERR_RET,
3707
+ "sha256_write",
3708
+ {
3709
+ _isMethod: true,
3710
+ _recvTypeName: "sha256.digest",
3711
+ _methodName: "Write",
3712
+ },
3713
+ ),
3714
+ ioHelper(
3715
+ "sha256.digest.Sum",
3716
+ [
3717
+ { name: "d", type: dPtr },
3718
+ { name: "b", type: BYTE_SLICE_T },
3719
+ ],
3720
+ BYTE_SLICE_T,
3721
+ "sha256_sum",
3722
+ { _isMethod: true, _recvTypeName: "sha256.digest", _methodName: "Sum" },
3723
+ ),
3724
+ ioHelper(
3725
+ "sha256.digest.Reset",
3726
+ [{ name: "d", type: dPtr }],
3727
+ null,
3728
+ "sha256_reset",
3729
+ {
3730
+ _isMethod: true,
3731
+ _recvTypeName: "sha256.digest",
3732
+ _methodName: "Reset",
3733
+ },
3734
+ ),
3735
+ ioHelper(
3736
+ "sha256.digest.Size",
3737
+ [{ name: "d", type: dPtr }],
3738
+ INT_T,
3739
+ "sha256_size",
3740
+ {
3741
+ _isMethod: true,
3742
+ _recvTypeName: "sha256.digest",
3743
+ _methodName: "Size",
3744
+ },
3745
+ ),
3746
+ ioHelper(
3747
+ "sha256.digest.BlockSize",
3748
+ [{ name: "d", type: dPtr }],
3749
+ INT_T,
3750
+ "sha256_block_size",
3751
+ {
3752
+ _isMethod: true,
3753
+ _recvTypeName: "sha256.digest",
3754
+ _methodName: "BlockSize",
3755
+ },
3756
+ ),
3757
+ );
3758
+ }
3759
+ }