gofront 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (128) hide show
  1. package/README.md +13 -13
  2. package/package.json +8 -37
  3. package/src/backend/js/expressions.js +1341 -148
  4. package/src/backend/js/fmt-runtime.js +573 -0
  5. package/src/backend/js/index.js +375 -57
  6. package/src/backend/js/runtime.js +1460 -133
  7. package/src/backend/js/statements.js +900 -86
  8. package/src/backend/js/stdlib/bits.js +87 -14
  9. package/src/backend/js/stdlib/builder.js +41 -6
  10. package/src/backend/js/stdlib/bytes.js +253 -7
  11. package/src/backend/js/stdlib/errors.js +2 -1
  12. package/src/backend/js/stdlib/fmt.js +349 -10
  13. package/src/backend/js/stdlib/index.js +8 -3
  14. package/src/backend/js/stdlib/json.js +247 -52
  15. package/src/backend/js/stdlib/maps.js +0 -8
  16. package/src/backend/js/stdlib/math.js +17 -1
  17. package/src/backend/js/stdlib/simd.js +10 -0
  18. package/src/backend/js/stdlib/slices.js +36 -6
  19. package/src/backend/js/stdlib/strconv.js +10 -5
  20. package/src/backend/js/stdlib/strings.js +83 -4
  21. package/src/backend/js/stdlib/testing.js +7 -0
  22. package/src/backend/js/stdlib/time.js +79 -55
  23. package/src/backend/js/stdlib/unicode.js +1 -1
  24. package/src/backend/js/stdlib/utf8.js +1 -1
  25. package/src/backend/js/templ.js +38 -76
  26. package/src/backend/js/type-info.js +272 -0
  27. package/src/backend/shared/fmt-types.js +188 -0
  28. package/src/backend/wasm/boundary.js +436 -80
  29. package/src/backend/wasm/emit-builtins.js +303 -13
  30. package/src/backend/wasm/emit-exprs.js +1397 -203
  31. package/src/backend/wasm/emit-maps.js +61 -13
  32. package/src/backend/wasm/emit-stmts.js +1359 -164
  33. package/src/backend/wasm/emit.js +512 -77
  34. package/src/backend/wasm/encode.js +43 -13
  35. package/src/backend/wasm/globals.js +452 -0
  36. package/src/backend/wasm/index.js +194 -2489
  37. package/src/backend/wasm/method-expr.js +381 -0
  38. package/src/backend/wasm/module-emitter.js +2240 -0
  39. package/src/backend/wasm/monomorph.js +62 -4
  40. package/src/backend/wasm/prescan.js +397 -0
  41. package/src/backend/wasm/stdlib/bytes.js +1804 -127
  42. package/src/backend/wasm/stdlib/core.js +1574 -199
  43. package/src/backend/wasm/stdlib/index.js +0 -5
  44. package/src/backend/wasm/stdlib/io.js +1567 -150
  45. package/src/backend/wasm/stdlib/packages.js +2 -0
  46. package/src/backend/wasm/stdlib/simd.js +293 -28
  47. package/src/backend/wasm/stdlib/time.js +1041 -12
  48. package/src/backend/wasm/types.js +200 -15
  49. package/src/backend/wasm/variadic.js +93 -0
  50. package/src/backend/wasm/wat.js +47 -20
  51. package/src/{cli-core.js → cli/cli-core.js} +236 -10
  52. package/src/{test-runner.js → cli/test-runner.js} +332 -39
  53. package/src/{vendor.js → cli/vendor.js} +3 -4
  54. package/src/compiler.js +107 -269
  55. package/src/index.js +15 -169
  56. package/src/{lexer.js → lexer/lexer.js} +102 -55
  57. package/src/{tokens.js → lexer/tokens.js} +1 -0
  58. package/src/link/sourcemap.js +97 -0
  59. package/src/link/stdlib.js +515 -0
  60. package/src/link/wasm.js +294 -0
  61. package/src/lower/bounds.js +394 -0
  62. package/src/lower/boxing.js +1 -1
  63. package/src/lower/captures.js +176 -32
  64. package/src/lower/index.js +26 -18
  65. package/src/lower/json-codecs.js +1398 -0
  66. package/src/lower/ownership.js +22 -1
  67. package/src/lower/range.js +86 -1
  68. package/src/lower/reachability.js +132 -0
  69. package/src/parser/declarations.js +58 -13
  70. package/src/parser/expressions.js +29 -7
  71. package/src/parser/index.js +2 -1
  72. package/src/parser/statements.js +37 -22
  73. package/src/{templ-parser.js → parser/templ.js} +138 -15
  74. package/src/parser/types.js +69 -9
  75. package/src/resolver.js +22 -1
  76. package/src/typechecker/assignability.js +176 -17
  77. package/src/typechecker/const-fold.js +356 -0
  78. package/src/typechecker/constants.js +256 -0
  79. package/src/typechecker/expressions.js +931 -122
  80. package/src/typechecker/index.js +459 -67
  81. package/src/typechecker/init-order.js +330 -0
  82. package/src/typechecker/resolve.js +451 -56
  83. package/src/typechecker/statements.js +90 -9
  84. package/src/typechecker/stdlib/core.js +333 -55
  85. package/src/typechecker/stdlib/excluded.js +898 -0
  86. package/src/typechecker/stdlib/extended.js +322 -26
  87. package/src/typechecker/{stdlib.js → stdlib/index.js} +5 -5
  88. package/src/typechecker/stdlib/simd.js +65 -15
  89. package/src/typechecker/termination.js +1 -1
  90. package/src/typechecker/types.js +149 -25
  91. package/stdlib/bytes/iter.go +192 -0
  92. package/stdlib/bytes/search.go +240 -0
  93. package/stdlib/bytes/split.go +218 -0
  94. package/stdlib/cmp/cmp.go +65 -0
  95. package/stdlib/encoding/json/errors.go +31 -0
  96. package/stdlib/encoding/json/indent.go +71 -0
  97. package/stdlib/encoding/json/number.go +12 -0
  98. package/stdlib/encoding/json/stream.go +173 -0
  99. package/stdlib/errors/overlay.go +12 -0
  100. package/stdlib/hash/hash.go +53 -0
  101. package/stdlib/internal/jsonrt/dec.go +738 -0
  102. package/stdlib/internal/jsonrt/enc.go +366 -0
  103. package/stdlib/internal/jsonrt/indent.go +135 -0
  104. package/stdlib/internal/jsonrt/number.go +27 -0
  105. package/stdlib/internal/jsonrt/reader.go +73 -0
  106. package/stdlib/internal/jsonrt/scanner.go +649 -0
  107. package/stdlib/io/io.go +346 -0
  108. package/stdlib/iter/iter.go +18 -0
  109. package/stdlib/maps/iter.go +65 -0
  110. package/stdlib/math/extra.go +270 -0
  111. package/stdlib/slices/extra.go +73 -0
  112. package/stdlib/slices/iter.go +113 -0
  113. package/stdlib/sort/sort.go +561 -0
  114. package/stdlib/strconv/quote.go +341 -0
  115. package/stdlib/strings/extra.go +35 -0
  116. package/stdlib/strings/iter.go +122 -0
  117. package/stdlib/sync/extra.go +105 -0
  118. package/stdlib/unicode/unicode.go +112 -0
  119. package/stdlib/unicode/utf8/bytes.go +239 -0
  120. package/src/backend/wasm/const-fold.js +0 -205
  121. package/src/backend/wasm/emit-stdlib.js +0 -13
  122. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  123. /package/src/{colors.js → cli/colors.js} +0 -0
  124. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  125. /package/src/{minifier.js → cli/minifier.js} +0 -0
  126. /package/src/{project-config.js → cli/project-config.js} +0 -0
  127. /package/src/{pwa.js → cli/pwa.js} +0 -0
  128. /package/src/{templ-lexer.js → lexer/templ-lexer.js} +0 -0
@@ -0,0 +1,2240 @@
1
+ // src/backend/wasm/module-emitter.js
2
+ // ModuleEmitter: Module IR builder that registers types, imports, globals and functions.
3
+
4
+ import { wTypeKey } from "./boundary.js";
5
+ import { hasErrorUsage, importsAnyOf } from "./prescan.js";
6
+ import { BINARY_MATH_IMPORTS } from "./stdlib/index.js";
7
+ import {
8
+ ERROR_STRUCT,
9
+ getFuncSignature,
10
+ getSliceElemType,
11
+ goTypeKey,
12
+ isScalarType,
13
+ isSliceType,
14
+ toWasmType,
15
+ } from "./types.js";
16
+
17
+ const ERROR_PRODUCING_PKGS = new Set([
18
+ "errors",
19
+ "fmt",
20
+ "strconv",
21
+ "path",
22
+ "path/filepath",
23
+ "io",
24
+ "os",
25
+ "strings",
26
+ "bytes",
27
+ "time",
28
+ "sync",
29
+ "crypto/rand",
30
+ "rand",
31
+ "crypto/sha256",
32
+ "sha256",
33
+ "encoding/binary",
34
+ "binary",
35
+ "encoding/hex",
36
+ "hex",
37
+ "encoding/base64",
38
+ "base64",
39
+ "math/bits",
40
+ "bits",
41
+ ]);
42
+ // Packages whose calls lower to the `env.*` imports in STDLIB_IMPORTS (the
43
+ // matching JS lives in WASM_STDLIB_JS). Only modules importing one of them
44
+ // declare the imports and ship that JS block.
45
+ const STDLIB_ENV_PKGS = new Set([
46
+ "strings",
47
+ "strconv",
48
+ "unicode/utf8",
49
+ "fmt",
50
+ "testing",
51
+ "unicode",
52
+ "math/rand",
53
+ "path",
54
+ "path/filepath",
55
+ "html",
56
+ "bytes",
57
+ "io",
58
+ "os",
59
+ "time",
60
+ "sync",
61
+ "crypto/rand",
62
+ "rand",
63
+ "crypto/sha256",
64
+ "sha256",
65
+ "encoding/binary",
66
+ "binary",
67
+ "encoding/hex",
68
+ "hex",
69
+ "encoding/base64",
70
+ "base64",
71
+ "math/bits",
72
+ "bits",
73
+ ]);
74
+
75
+ // [name, params, results] for `env.*` imports backing stdlib calls; the JS
76
+ // side lives in WASM_STDLIB_JS (boundary.js). All are registered eagerly
77
+ // (when any STDLIB_ENV_PKGS package is imported) because the import table is
78
+ // locked before function bodies are emitted.
79
+ const S = "externref";
80
+ const STDLIB_IMPORTS = [
81
+ // strings
82
+ ["str_trim", [S, S], [S]],
83
+ ["str_trim_left", [S, S], [S]],
84
+ ["str_trim_right", [S, S], [S]],
85
+ ["str_trim_prefix", [S, S], [S]],
86
+ ["str_trim_suffix", [S, S], [S]],
87
+ ["str_cut", [S, S], ["i32"]],
88
+ ["str_cut_before", [], [S]],
89
+ ["str_cut_after", [], [S]],
90
+ ["str_cut_prefix", [S, S], ["i32"]],
91
+ ["str_cut_suffix", [S, S], ["i32"]],
92
+ ["str_replace", [S, S, S, "i32"], [S]],
93
+ ["str_contains_rune", [S, "i32"], ["i32"]],
94
+ ["str_contains_any", [S, S], ["i32"]],
95
+ ["str_index_byte", [S, "i32"], ["i32"]],
96
+ ["str_index_rune", [S, "i32"], ["i32"]],
97
+ ["str_index_any", [S, S], ["i32"]],
98
+ ["str_last_index_byte", [S, "i32"], ["i32"]],
99
+ ["str_last_index_any", [S, S], ["i32"]],
100
+ ["str_title", [S], [S]],
101
+ ["str_split", [S, S], ["i32"]],
102
+ ["str_split_n", [S, S, "i32"], ["i32"]],
103
+ ["str_split_after", [S, S], ["i32"]],
104
+ ["str_split_after_n", [S, S, "i32"], ["i32"]],
105
+ ["str_fields", [S], ["i32"]],
106
+ ["str_part", ["i32"], [S]],
107
+ ["str_replacer_init", [S], []],
108
+ ["str_replacer_add", [S, S], []],
109
+ ["str_replacer_run", [], [S]],
110
+ // strconv
111
+ ["strconv_atoi", [S], ["i64"]],
112
+ ["strconv_parse_int", [S, "i32", "i32"], ["i64"]],
113
+ ["strconv_parse_float", [S], ["f64"]],
114
+ ["strconv_parse_bool", [S], ["i32"]],
115
+ ["strconv_err", [], [S]],
116
+ ["strconv_err_kind", [], ["i32"]],
117
+ ["strconv_format_float", ["f64", "i32", "i32", "i32"], [S]],
118
+ ["strconv_format_int", ["i64", "i32"], [S]],
119
+ ["strconv_format_uint", ["i64", "i32"], [S]],
120
+ ["strconv_parse_uint", [S, "i32", "i32"], ["i64"]],
121
+ ["strconv_quote", [S], [S]],
122
+ ["strconv_unquote", [S], [S]],
123
+ // unicode/utf8
124
+ ["utf8_rune_count", [S], ["i32"]],
125
+ ["utf8_rune_len", ["i32"], ["i32"]],
126
+ ["utf8_valid_string", [S], ["i32"]],
127
+ ["utf8_decode_rune", [S, "i32"], ["i32"]],
128
+ ["utf8_decode_size", [], ["i32"]],
129
+ ["utf8_full_rune", [S], ["i32"]],
130
+ // fmt (arguments travel through the testing_arg_* buffer)
131
+ ["fmt_sprintf", [S], [S]],
132
+ ["fmt_printf", [S], []],
133
+ ["fmt_sprint", ["i32"], [S]],
134
+ ["fmt_print", ["i32"], []],
135
+ ["fmt_print_raw", [S], []],
136
+ ["fmt_format_v", [S], [S]],
137
+ ["fmt_sscan_init", [S, "i32"], ["i32"]],
138
+ ["fmt_sscanf_init", [S, S], ["i32"]],
139
+ ["fmt_sscan_has_err", [], ["i32"]],
140
+ ["fmt_sscan_err_msg", [], [S]],
141
+ ["fmt_sscan_token_str", ["i32"], [S]],
142
+ ["fmt_sscan_token_i64", ["i32"], ["i64"]],
143
+ ["fmt_sscan_token_f64", ["i32"], ["f64"]],
144
+ ["fmt_sscan_token_bool", ["i32"], ["i32"]],
145
+ // testing.T.Run: the harness calls back into the exported __testing_run_cb
146
+ ["testing_run", [S, S, "anyref"], ["i32"]],
147
+ ["testing_cleanup", [S, "anyref"], []],
148
+ ["testing_n", [S], ["i64"]],
149
+ ["testing_elapsed", [S], ["i64"]],
150
+ ["testing_context", [S], [S]],
151
+ ["testing_output", [S], [S]],
152
+ // unicode
153
+ ["unicode_is_letter", ["i32"], ["i32"]],
154
+ ["unicode_is_digit", ["i32"], ["i32"]],
155
+ ["unicode_is_space", ["i32"], ["i32"]],
156
+ ["unicode_is_upper", ["i32"], ["i32"]],
157
+ ["unicode_is_lower", ["i32"], ["i32"]],
158
+ ["unicode_is_punct", ["i32"], ["i32"]],
159
+ ["unicode_is_control", ["i32"], ["i32"]],
160
+ ["unicode_is_print", ["i32"], ["i32"]],
161
+ ["unicode_is_graphic", ["i32"], ["i32"]],
162
+ ["unicode_to_upper", ["i32"], ["i32"]],
163
+ ["unicode_to_lower", ["i32"], ["i32"]],
164
+ // html
165
+ ["html_escape", [S], [S]],
166
+ ["html_unescape", [S], [S]],
167
+ // path
168
+ ["path_base", [S], [S]],
169
+ ["path_dir", [S], [S]],
170
+ ["path_ext", [S], [S]],
171
+ ["path_is_abs", [S], ["i32"]],
172
+ ["path_clean", [S], [S]],
173
+ ["path_split", [S], []],
174
+ ["path_split_dir", [], [S]],
175
+ ["path_split_file", [], [S]],
176
+ ["path_join_init", [], []],
177
+ ["path_join_push", [S], []],
178
+ ["path_join_run", [], [S]],
179
+ ["path_match", [S, S], ["i32"]],
180
+ ["path_match_has_err", [], ["i32"]],
181
+ ["bytes_trim", [S, S, "i32", "i32"], []],
182
+ ["bytes_trim_start", [], ["i32"]],
183
+ ["bytes_trim_end", [], ["i32"]],
184
+ // time
185
+ ["time_now_ms", [], ["f64"]],
186
+ ["time_timezone_offset", ["f64"], ["i32"]],
187
+ ["time_parse", [S, S], ["f64"]],
188
+ ["time_parse_err", [], [S]],
189
+ ["time_parse_offset", [], ["i32"]],
190
+ ["time_parse_sub_ns", [], ["i32"]],
191
+ [
192
+ "time_format",
193
+ ["i32", "i32", "i32", "i32", "i32", "i32", "i32", "i32", "i32", S],
194
+ [S],
195
+ ],
196
+ ["time_duration_string", ["i64"], [S]],
197
+ ["time_parse_duration", [S], ["i64"]],
198
+ ["time_parse_duration_err", [], [S]],
199
+ ["time_month_string", ["i32"], [S]],
200
+ ["time_weekday_string", ["i32"], [S]],
201
+ // raw binary buffer for crypto/rand, crypto/sha256, hex, base64
202
+ ["raw_bytes_init", ["i32"], []],
203
+ ["raw_bytes_push", ["i32"], []],
204
+ ["raw_bytes_len", [], ["i32"]],
205
+ ["raw_bytes_get", ["i32"], ["i32"]],
206
+ // crypto/rand
207
+ ["crypto_rand_fill", ["i32"], ["i32"]],
208
+ // crypto/sha256
209
+ ["crypto_sha256_sum", [], ["i32"]],
210
+ // encoding/hex
211
+ ["hex_encode_to_string", [], [S]],
212
+ ["hex_decode_string", [S], ["i32"]],
213
+ ["hex_get_err", [], [S]],
214
+ // encoding/base64
215
+ ["base64_encode_to_string", ["i32"], [S]],
216
+ ["base64_decode_string", ["i32", S], ["i32"]],
217
+ ["base64_get_err", [], [S]],
218
+ ["bits_div32", ["i32", "i32", "i32"], ["i32"]],
219
+ ["bits_div32_rem", [], ["i32"]],
220
+ ["bits_rem32", ["i32", "i32", "i32"], ["i32"]],
221
+ ["bits_div64", ["i64", "i64", "i64"], ["i64"]],
222
+ ["bits_div64_rem", [], ["i64"]],
223
+ ["bits_rem64", ["i64", "i64", "i64"], ["i64"]],
224
+ ];
225
+
226
+ export class ModuleEmitter {
227
+ constructor(checker, lowerResult = null, programs = [], options = {}) {
228
+ this.checker = checker;
229
+ this.lowerResult = lowerResult;
230
+ // Local package names whose code is linked into this module; selectors
231
+ // like `mathx.NewVec3` are de-qualified to `NewVec3` by the emitter.
232
+ this.bundledPackages = options.bundledPackages ?? new Set();
233
+ this.constCache = new Map(); // name -> BasicLit (package-level literal consts)
234
+ this.nonLiteralConsts = new Set(); // package-level consts the backend cannot evaluate yet
235
+
236
+ this.types = []; // type entries
237
+ this.typeCache = new Map(); // signature string -> index
238
+ this._openRec = null; // rec group receiving new types while structs are resolved
239
+
240
+ this.structTypes = new Map(); // name -> struct info
241
+ this.complexTypes = new Map(); // key -> complex struct info
242
+ this.ptrTypes = new Map(); // key -> ptr supertype info
243
+ this.boxTypes = new Map(); // wType key -> box info
244
+ this.ifaceBoxTypes = new Map(); // key -> iface box info
245
+ this.nilBoxTypes = new Map(); // key -> typed-nil marker info
246
+ this.arrayTypes = new Map(); // wType key -> array info
247
+ this.sliceTypes = new Map(); // wType key -> slice info
248
+ this.sliceTypesByIndex = new Map(); // typeIndex -> slice info
249
+ this.mapTypes = new Map(); // wType key -> map info
250
+ this.mapTypesByIndex = new Map(); // typeIndex -> map info
251
+ this.mapBucketsType = null; // shared (array (mut i32))
252
+ this.closureTypes = new Map(); // key -> closure info
253
+ this.envTypes = new Map(); // key -> env info
254
+
255
+ this.elements = []; // declarative elements for ref.func
256
+ this.elementSet = new Set();
257
+
258
+ this.imports = []; // import entries
259
+ this.importCache = new Map(); // key -> func index
260
+
261
+ this.tags = []; // exception tags
262
+ this.globals = []; // global entries
263
+ this.globalCache = new Map(); // name -> index
264
+
265
+ this.funcs = []; // internal func definitions
266
+ this.funcMap = new Map(); // name -> global func index
267
+ this.funcParamTypes = new Map(); // name -> param types array
268
+ this.funcParamGoTypes = new Map(); // name -> param Go types array
269
+ this.funcReturnTypesByIdx = new Map(); // global func index -> result wasm types
270
+
271
+ this.exports = []; // export entries
272
+ this.stringTable = []; // string literals
273
+ this.stringCache = new Map();
274
+
275
+ // `errors.New` / `fmt.Errorf` / strconv parse failures allocate the
276
+ // runtime's own error struct; it joins the struct rec group so the
277
+ // `error.Error` dispatcher can `ref.test` it like any user type.
278
+ this.hasErrorType =
279
+ importsAnyOf(programs, ERROR_PRODUCING_PKGS) || hasErrorUsage(programs);
280
+ this.usesStdlibEnv = importsAnyOf(programs, STDLIB_ENV_PKGS);
281
+ this.importsTesting = importsAnyOf(programs, ["testing"]);
282
+
283
+ if (programs && programs.length > 0) {
284
+ this.initTypes(programs);
285
+ }
286
+
287
+ this._importsLocked = false;
288
+ this._initRuntimeImports();
289
+ }
290
+
291
+ initTypes(programs) {
292
+ const progs = Array.isArray(programs) ? programs : [programs];
293
+ const rawStructs = [];
294
+ const seen = new Set();
295
+ const aliases = []; // qualified key (pkg.T) -> unqualified struct name
296
+
297
+ if (this.hasErrorType) {
298
+ seen.add(ERROR_STRUCT);
299
+ rawStructs.push({
300
+ name: ERROR_STRUCT,
301
+ structType: {
302
+ kind: "struct",
303
+ fields: new Map([
304
+ ["msg", { kind: "basic", name: "string" }],
305
+ ["cause", { kind: "basic", name: "any" }],
306
+ ]),
307
+ },
308
+ });
309
+ }
310
+
311
+ for (const p of progs) {
312
+ for (const d of p.decls ?? []) {
313
+ if (
314
+ d.kind === "TypeDecl" &&
315
+ !d.isAlias &&
316
+ d.type?.kind === "StructType"
317
+ ) {
318
+ if (!seen.has(d.name)) {
319
+ seen.add(d.name);
320
+ rawStructs.push({
321
+ name: d.name,
322
+ astFields: d.type.fields ?? [],
323
+ pkgName: p.pkg?.name ?? "main",
324
+ pkgTarget: p.target ?? "wasm",
325
+ });
326
+ }
327
+ }
328
+ }
329
+ }
330
+
331
+ if (this.checker?.types) {
332
+ for (const [key, t] of this.checker.types.entries()) {
333
+ if (t.kind === "named" && t.underlying?.kind === "struct") {
334
+ const name = t.name ?? key;
335
+ if (
336
+ name.startsWith("testing.") &&
337
+ (!this.importsTesting || name !== "testing.BenchmarkResult")
338
+ )
339
+ continue;
340
+ if (!seen.has(name)) {
341
+ seen.add(name);
342
+ rawStructs.push({ name, structType: t.underlying });
343
+ }
344
+ if (key !== name) aliases.push([key, name]);
345
+ }
346
+ }
347
+ }
348
+
349
+ if (rawStructs.length === 0) return;
350
+
351
+ // Field resolution may create array/slice/box types; they join this rec
352
+ // group so struct indices (base + i) stay valid and may be referenced
353
+ // from the auxiliary types in any order.
354
+ const base = this._getTotalTypeCount();
355
+ const structEntries = rawStructs.map(() => ({
356
+ form: "struct",
357
+ fields: [],
358
+ }));
359
+ this.types.push({ form: "rec", types: structEntries });
360
+ this._openRec = structEntries;
361
+
362
+ for (let i = 0; i < rawStructs.length; i++) {
363
+ const s = rawStructs[i];
364
+ this.structTypes.set(s.name, {
365
+ name: s.name,
366
+ typeIndex: base + i,
367
+ fields: [],
368
+ fieldIndexMap: new Map(),
369
+ embeds: [],
370
+ pkgName: s.pkgName ?? null,
371
+ pkgTarget: s.pkgTarget ?? "wasm",
372
+ });
373
+ }
374
+ // `mathx.Vec3` resolves to the same struct info as `Vec3` once linked.
375
+ for (const [key, name] of aliases) {
376
+ if (!this.structTypes.has(key))
377
+ this.structTypes.set(key, this.structTypes.get(name));
378
+ }
379
+
380
+ for (let i = 0; i < rawStructs.length; i++) {
381
+ const s = rawStructs[i];
382
+ const info = this.structTypes.get(s.name);
383
+ const resolvedStruct =
384
+ s.structType ?? this.checker?.types?.get(s.name)?.underlying;
385
+
386
+ const fields = [];
387
+ const fieldIndexMap = new Map();
388
+ const embeds = [];
389
+
390
+ if (s.astFields && s.astFields.length > 0) {
391
+ let fIdx = 0;
392
+ for (const f of s.astFields) {
393
+ const names = f.embedded
394
+ ? [
395
+ (
396
+ f.embeddedName ??
397
+ (f.type?.kind === "PointerType"
398
+ ? f.type.base?.name
399
+ : f.type?.name)
400
+ )
401
+ ?.split(".")
402
+ .pop(),
403
+ ]
404
+ : f.names?.length
405
+ ? f.names
406
+ : f.name
407
+ ? [f.name]
408
+ : [];
409
+ for (const n of names) {
410
+ const wType = toWasmType(f.type, this.checker, this);
411
+ fields.push({
412
+ name: n,
413
+ goType: f.type,
414
+ wType,
415
+ embedded: Boolean(f.embedded),
416
+ });
417
+ fieldIndexMap.set(n, fIdx);
418
+ if (f.embedded) {
419
+ embeds.push({
420
+ name: n,
421
+ fieldIndex: fIdx,
422
+ });
423
+ }
424
+ fIdx++;
425
+ }
426
+ }
427
+ } else if (resolvedStruct?.fields) {
428
+ let fIdx = 0;
429
+ // `fields` also holds promoted fields; the declared fields
430
+ // (`_structFields`) give the real layout of a defined struct.
431
+ const declared = resolvedStruct._structFields
432
+ ? resolvedStruct._structFields.flatMap((sf) => {
433
+ if (!sf.embedded) return sf.names.map((n) => [n, sf.type]);
434
+ const inner = sf.type.kind === "pointer" ? sf.type.base : sf.type;
435
+ const n = (inner?.name ?? "").split(".").pop();
436
+ return n ? [[n, sf.type]] : [];
437
+ })
438
+ : [...resolvedStruct.fields.entries()];
439
+ for (const [fName, fType] of declared) {
440
+ const wType = toWasmType(fType, this.checker, this);
441
+ fields.push({ name: fName, goType: fType, wType });
442
+ fieldIndexMap.set(fName, fIdx);
443
+ fIdx++;
444
+ }
445
+ if (resolvedStruct?._embeds) {
446
+ for (const embed of resolvedStruct._embeds) {
447
+ const inner = embed.kind === "pointer" ? embed.base : embed;
448
+ const embedName = inner?.kind === "named" ? inner.name : null;
449
+ if (embedName && fieldIndexMap.has(embedName)) {
450
+ embeds.push({
451
+ name: embedName,
452
+ fieldIndex: fieldIndexMap.get(embedName),
453
+ });
454
+ }
455
+ }
456
+ }
457
+ }
458
+
459
+ info.fields = fields;
460
+ info.fieldIndexMap = fieldIndexMap;
461
+ info.embeds = embeds;
462
+
463
+ const typeEntry = structEntries[i];
464
+ typeEntry.fields = fields.map((f) => ({ type: f.wType, mutable: true }));
465
+ info.typeEntry = typeEntry;
466
+ }
467
+
468
+ this._openRec = null;
469
+ }
470
+
471
+ _pushType(typeEntry) {
472
+ (this._openRec ?? this.types).push(typeEntry);
473
+ }
474
+
475
+ getStructType(name) {
476
+ return this.structTypes.get(name) ?? null;
477
+ }
478
+
479
+ getAnonStructType(fields) {
480
+ const key = fields.map((f) => `${f.name}:${goTypeKey(f.goType)}`).join(";");
481
+ const name = `__anon_${key.replace(/[^a-zA-Z0-9_]/g, "_")}`;
482
+ if (this.structTypes.has(name)) {
483
+ return this.structTypes.get(name);
484
+ }
485
+ const fieldWTypes = fields.map((f) =>
486
+ toWasmType(f.goType, this.checker, this),
487
+ );
488
+ const typeIndex = this._getTotalTypeCount();
489
+ const typeEntry = {
490
+ form: "struct",
491
+ fields: fieldWTypes.map((t) => ({ type: t, mutable: true })),
492
+ };
493
+ this._pushType(typeEntry);
494
+ const info = {
495
+ name,
496
+ typeIndex,
497
+ typeEntry,
498
+ fields: fields.map((f, i) => ({
499
+ name: f.name,
500
+ wType: fieldWTypes[i],
501
+ goType: f.goType,
502
+ fieldIndex: i,
503
+ })),
504
+ fieldIndexMap: new Map(fields.map((f, i) => [f.name, i])),
505
+ embeds: [],
506
+ pkgName: null,
507
+ pkgTarget: "wasm",
508
+ isAnon: true,
509
+ };
510
+ this.structTypes.set(name, info);
511
+ return info;
512
+ }
513
+
514
+ getComplexType(name = "complex128") {
515
+ const key = name === "complex64" ? "complex64" : "complex128";
516
+ if (this.complexTypes.has(key)) {
517
+ return this.complexTypes.get(key);
518
+ }
519
+ const isF32 = key === "complex64";
520
+ const elemWType = isF32 ? "f32" : "f64";
521
+ const elemGoType = { kind: "basic", name: isF32 ? "float32" : "float64" };
522
+ const typeName = `$${key}`;
523
+ const typeIndex = this._getTotalTypeCount();
524
+ const typeEntry = {
525
+ form: "struct",
526
+ name: typeName,
527
+ fields: [
528
+ { type: elemWType, mutable: false },
529
+ { type: elemWType, mutable: false },
530
+ ],
531
+ };
532
+ this._pushType(typeEntry);
533
+ const fields = [
534
+ { name: "re", fieldIndex: 0, wType: elemWType, goType: elemGoType },
535
+ { name: "im", fieldIndex: 1, wType: elemWType, goType: elemGoType },
536
+ ];
537
+ const info = {
538
+ name: typeName,
539
+ typeIndex,
540
+ typeEntry,
541
+ fields,
542
+ fieldIndexMap: new Map([
543
+ ["re", 0],
544
+ ["im", 1],
545
+ ]),
546
+ embeds: [],
547
+ pkgName: null,
548
+ pkgTarget: "wasm",
549
+ isComplex: true,
550
+ elemWType,
551
+ elemGoType,
552
+ };
553
+ this.complexTypes.set(key, info);
554
+ this.structTypes.set(key, info);
555
+ this.structTypes.set(typeName, info);
556
+ return info;
557
+ }
558
+
559
+ getPtrType(goType) {
560
+ const key = goTypeKey(goType);
561
+ if (this.ptrTypes.has(key)) {
562
+ return this.ptrTypes.get(key);
563
+ }
564
+ const typeIndex = this._getTotalTypeCount();
565
+ const name = `$ptr_${key}`;
566
+ const typeEntry = {
567
+ form: "struct",
568
+ name,
569
+ sub: true,
570
+ fields: [],
571
+ };
572
+ this._pushType(typeEntry);
573
+ const ptrInfo = { key, name, typeIndex, typeEntry, goType };
574
+ this.ptrTypes.set(key, ptrInfo);
575
+ return ptrInfo;
576
+ }
577
+
578
+ getBoxType(goType) {
579
+ const key = goTypeKey(goType);
580
+ if (this.boxTypes.has(key)) {
581
+ return this.boxTypes.get(key);
582
+ }
583
+ const ptrInfo = this.getPtrType(goType);
584
+ const wType = toWasmType(goType, this.checker, this);
585
+ const typeIndex = this._getTotalTypeCount();
586
+ const name = `$box_${key}`;
587
+ const typeEntry = {
588
+ form: "struct",
589
+ name,
590
+ sub: true,
591
+ superType: ptrInfo.name,
592
+ superTypeIndex: ptrInfo.typeIndex,
593
+ fields: [{ type: wType, mutable: true }],
594
+ };
595
+ this._pushType(typeEntry);
596
+ const boxInfo = {
597
+ key,
598
+ name,
599
+ typeIndex,
600
+ typeEntry,
601
+ ptrType: ptrInfo,
602
+ ptrTypeIndex: ptrInfo.typeIndex,
603
+ wType,
604
+ goType,
605
+ };
606
+ this.boxTypes.set(key, boxInfo);
607
+ return boxInfo;
608
+ }
609
+
610
+ getIfaceBoxType(goType) {
611
+ const key = goTypeKey(goType);
612
+ if (this.ifaceBoxTypes.has(key)) {
613
+ return this.ifaceBoxTypes.get(key);
614
+ }
615
+ const wType = toWasmType(goType, this.checker, this);
616
+ const tagIndex = this.ifaceBoxTypes.size;
617
+ const tagTypeIndex = this._getTotalTypeCount();
618
+ const tagFields = [];
619
+ for (let i = 0; i < tagIndex; i++) {
620
+ tagFields.push({ type: "i32", mutable: false });
621
+ }
622
+ this._pushType({
623
+ form: "struct",
624
+ fields: tagFields,
625
+ });
626
+ const typeIndex = this._getTotalTypeCount();
627
+ const typeEntry = {
628
+ form: "struct",
629
+ fields: [
630
+ { type: wType, mutable: true },
631
+ {
632
+ type: {
633
+ kind: "ref",
634
+ nullable: true,
635
+ typeIndex: tagTypeIndex,
636
+ },
637
+ mutable: false,
638
+ },
639
+ ],
640
+ };
641
+ this._pushType(typeEntry);
642
+ const boxInfo = { key, typeIndex, typeEntry, wType, goType, tagTypeIndex };
643
+ this.ifaceBoxTypes.set(key, boxInfo);
644
+ return boxInfo;
645
+ }
646
+
647
+ // The value a nil map, func or `*T` takes inside an interface: a marker
648
+ // object per Go type, so `any(nilMap) != nil` and a type switch still sees
649
+ // the dynamic type. Struct types are structural, so each marker gets a
650
+ // distinct field count to keep them apart.
651
+ getNilBoxType(goType) {
652
+ const key = goTypeKey(goType);
653
+ if (this.nilBoxTypes.has(key)) return this.nilBoxTypes.get(key);
654
+ const n = this.nilBoxTypes.size + 1;
655
+ const typeIndex = this._getTotalTypeCount();
656
+ this._pushType({
657
+ form: "struct",
658
+ fields: Array.from({ length: n }, () => ({
659
+ type: "i32",
660
+ mutable: true,
661
+ })),
662
+ });
663
+ const globalIndex = this.globals.length;
664
+ this.globals.push({
665
+ type: { kind: "ref", nullable: true, typeIndex },
666
+ mutable: false,
667
+ init: [
668
+ ...Array.from({ length: n }, () => ({ op: "i32.const", value: 0 })),
669
+ { op: "struct.new", typeIndex },
670
+ ],
671
+ });
672
+ const info = { key, goType, typeIndex, globalIndex };
673
+ this.nilBoxTypes.set(key, info);
674
+ return info;
675
+ }
676
+
677
+ getArrayType(elemGoType) {
678
+ const elemWType = toWasmType(elemGoType, this.checker, this);
679
+ const key =
680
+ typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
681
+ if (this.arrayTypes.has(key)) {
682
+ return this.arrayTypes.get(key);
683
+ }
684
+ const typeIndex = this._getTotalTypeCount();
685
+ const typeEntry = {
686
+ form: "array",
687
+ elemType: elemWType,
688
+ mutable: true,
689
+ };
690
+ this._pushType(typeEntry);
691
+ const arrInfo = { typeIndex, typeEntry, elemWType, elemGoType };
692
+ this.arrayTypes.set(key, arrInfo);
693
+ return arrInfo;
694
+ }
695
+
696
+ getSliceType(elemGoType) {
697
+ const elemWType = toWasmType(elemGoType, this.checker, this);
698
+ const key =
699
+ typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
700
+ if (this.sliceTypes.has(key)) {
701
+ return this.sliceTypes.get(key);
702
+ }
703
+ const arrInfo = this.getArrayType(elemGoType);
704
+ const typeIndex = this._getTotalTypeCount();
705
+ const typeEntry = {
706
+ form: "struct",
707
+ fields: [
708
+ {
709
+ type: {
710
+ kind: "ref",
711
+ nullable: true,
712
+ typeIndex: arrInfo.typeIndex,
713
+ },
714
+ mutable: false,
715
+ },
716
+ { type: "i32", mutable: false }, // offset
717
+ { type: "i32", mutable: false }, // len
718
+ { type: "i32", mutable: false }, // cap
719
+ ],
720
+ };
721
+ this._pushType(typeEntry);
722
+
723
+ const emptyGlobalIndex = this.globals.length;
724
+ this.globals.push({
725
+ type: {
726
+ kind: "ref",
727
+ nullable: true,
728
+ typeIndex,
729
+ },
730
+ mutable: false,
731
+ init: [
732
+ { op: "ref.null", heapType: arrInfo.typeIndex },
733
+ { op: "i32.const", value: 0 },
734
+ { op: "i32.const", value: 0 },
735
+ { op: "i32.const", value: 0 },
736
+ { op: "struct.new", typeIndex },
737
+ ],
738
+ });
739
+
740
+ const sliceInfo = {
741
+ typeIndex,
742
+ typeEntry,
743
+ arrInfo,
744
+ elemWType,
745
+ elemGoType,
746
+ emptyGlobalIndex,
747
+ };
748
+ this.sliceTypes.set(key, sliceInfo);
749
+ this.sliceTypesByIndex.set(typeIndex, sliceInfo);
750
+ return sliceInfo;
751
+ }
752
+
753
+ getSliceTypeByIndex(typeIndex) {
754
+ return this.sliceTypesByIndex?.get(typeIndex) ?? null;
755
+ }
756
+
757
+ getMapType(keyGoType, valGoType) {
758
+ const keyWType = toWasmType(keyGoType, this.checker, this);
759
+ const valWType = toWasmType(valGoType, this.checker, this);
760
+ // Vector keys share one wasm type but compare differently (floats vs
761
+ // mask words), so the Go type is part of the key.
762
+ const keyPart =
763
+ keyWType === "v128" ? `v128_${goTypeKey(keyGoType)}` : wTypeKey(keyWType);
764
+ const key = `${keyPart}:${wTypeKey(valWType)}`;
765
+ if (this.mapTypes.has(key)) {
766
+ return this.mapTypes.get(key);
767
+ }
768
+
769
+ if (!this.mapBucketsType) {
770
+ const typeIndex = this._getTotalTypeCount();
771
+ this._pushType({
772
+ form: "array",
773
+ elemType: "i32",
774
+ mutable: true,
775
+ });
776
+ this.mapBucketsType = { typeIndex };
777
+ }
778
+
779
+ const entryTypeIndex = this._getTotalTypeCount();
780
+ this._pushType({
781
+ form: "struct",
782
+ fields: [
783
+ { type: keyWType, mutable: true }, // 0: key
784
+ { type: valWType, mutable: true }, // 1: val
785
+ { type: "i32", mutable: true }, // 2: next (bucket chain)
786
+ { type: "i32", mutable: true }, // 3: order_prev (insertion order)
787
+ { type: "i32", mutable: true }, // 4: order_next (insertion order)
788
+ { type: "i32", mutable: true }, // 5: active (1 = active, 0 = deleted)
789
+ ],
790
+ });
791
+
792
+ const entriesTypeIndex = this._getTotalTypeCount();
793
+ this._pushType({
794
+ form: "array",
795
+ elemType: { kind: "ref", nullable: true, typeIndex: entryTypeIndex },
796
+ mutable: true,
797
+ });
798
+
799
+ const mapTypeIndex = this._getTotalTypeCount();
800
+ this._pushType({
801
+ form: "struct",
802
+ fields: [
803
+ {
804
+ type: {
805
+ kind: "ref",
806
+ nullable: true,
807
+ typeIndex: this.mapBucketsType.typeIndex,
808
+ },
809
+ mutable: true,
810
+ }, // 0: buckets
811
+ {
812
+ type: {
813
+ kind: "ref",
814
+ nullable: true,
815
+ typeIndex: entriesTypeIndex,
816
+ },
817
+ mutable: true,
818
+ }, // 1: entries
819
+ { type: "i32", mutable: true }, // 2: len
820
+ { type: "i32", mutable: true }, // 3: cap
821
+ { type: "i32", mutable: true }, // 4: count
822
+ { type: "i32", mutable: true }, // 5: head
823
+ { type: "i32", mutable: true }, // 6: tail
824
+ { type: "i32", mutable: true }, // 7: free_head
825
+ { type: "i32", mutable: true }, // 8: num_buckets
826
+ ],
827
+ });
828
+
829
+ const suffix = key.replace(/[^a-zA-Z0-9_]/g, "_");
830
+ const mapInfo = {
831
+ key,
832
+ suffix,
833
+ typeIndex: mapTypeIndex,
834
+ entryTypeIndex,
835
+ entriesTypeIndex,
836
+ bucketsTypeIndex: this.mapBucketsType.typeIndex,
837
+ keyWType,
838
+ valWType,
839
+ keyGoType,
840
+ valGoType,
841
+ wType: { kind: "ref", nullable: true, typeIndex: mapTypeIndex },
842
+ makeFuncName: `__map_make_${suffix}`,
843
+ getFuncName: `__map_get_${suffix}`,
844
+ getOkFuncName: `__map_get_ok_${suffix}`,
845
+ setFuncName: `__map_set_${suffix}`,
846
+ deleteFuncName: `__map_delete_${suffix}`,
847
+ lenFuncName: `__map_len_${suffix}`,
848
+ clearFuncName: `__map_clear_${suffix}`,
849
+ keysFuncName: `__map_keys_${suffix}`,
850
+ valuesFuncName: `__map_values_${suffix}`,
851
+ cloneFuncName: `__map_clone_${suffix}`,
852
+ };
853
+ this.mapTypes.set(key, mapInfo);
854
+ this.mapTypesByIndex.set(mapTypeIndex, mapInfo);
855
+ return mapInfo;
856
+ }
857
+
858
+ getMapTypeByIndex(typeIndex) {
859
+ return this.mapTypesByIndex?.get(typeIndex) ?? null;
860
+ }
861
+
862
+ getClosureType(goType) {
863
+ const sig = getFuncSignature(goType, this.checker);
864
+ const paramWTypes = sig.params
865
+ .map((p) => toWasmType(p, this.checker, this))
866
+ .filter(Boolean);
867
+ const returnWTypes = sig.returns
868
+ .map((r) => toWasmType(r, this.checker, this))
869
+ .filter(Boolean);
870
+
871
+ const key = `${paramWTypes.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${returnWTypes.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
872
+ if (this.closureTypes.has(key)) {
873
+ return this.closureTypes.get(key);
874
+ }
875
+
876
+ // 1. Function signature type index: (param anyref, ...paramWTypes) -> (...returnWTypes)
877
+ const funcTypeIndex = this.getTypeIndex(
878
+ ["anyref", ...paramWTypes],
879
+ returnWTypes,
880
+ );
881
+
882
+ // 2. Closure struct type index: struct { fn: (ref funcTypeIndex), env: anyref }
883
+ const typeIndex = this._getTotalTypeCount();
884
+ const typeEntry = {
885
+ form: "struct",
886
+ fields: [
887
+ {
888
+ type: {
889
+ kind: "ref",
890
+ nullable: false,
891
+ typeIndex: funcTypeIndex,
892
+ },
893
+ mutable: false,
894
+ },
895
+ {
896
+ type: "anyref",
897
+ mutable: true,
898
+ },
899
+ ],
900
+ };
901
+ this._pushType(typeEntry);
902
+
903
+ const closureInfo = {
904
+ typeIndex,
905
+ typeEntry,
906
+ funcTypeIndex,
907
+ paramWTypes,
908
+ returnWTypes,
909
+ sig,
910
+ };
911
+ this.closureTypes.set(key, closureInfo);
912
+ return closureInfo;
913
+ }
914
+
915
+ getEnvType(fieldTypes) {
916
+ const key = fieldTypes
917
+ .map((f) =>
918
+ typeof f.wType === "object" ? JSON.stringify(f.wType) : f.wType,
919
+ )
920
+ .join(",");
921
+ if (this.envTypes.has(key)) {
922
+ return this.envTypes.get(key);
923
+ }
924
+ const typeIndex = this._getTotalTypeCount();
925
+ const typeEntry = {
926
+ form: "struct",
927
+ fields: fieldTypes.map((f) => ({ type: f.wType, mutable: true })),
928
+ };
929
+ this._pushType(typeEntry);
930
+ const envInfo = { typeIndex, typeEntry, fieldTypes };
931
+ this.envTypes.set(key, envInfo);
932
+ return envInfo;
933
+ }
934
+
935
+ addRefFuncElement(funcIdx) {
936
+ if (!this.elementSet.has(funcIdx)) {
937
+ this.elementSet.add(funcIdx);
938
+ this.elements.push(funcIdx);
939
+ }
940
+ }
941
+
942
+ _getTotalTypeCount() {
943
+ let count = 0;
944
+ for (const entry of this.types) {
945
+ if (entry.form === "rec") count += entry.types.length;
946
+ else count += 1;
947
+ }
948
+ return count;
949
+ }
950
+
951
+ _initRuntimeImports() {
952
+ this._initPanicTag();
953
+ this._initPanicGlobal();
954
+ this.getPanicImportIndex(); // env.panic
955
+ this.internString("runtime error: index out of range");
956
+ this.internString("runtime error: slice bounds out of range");
957
+ this.internString("assignment to entry in nil map");
958
+
959
+ // String built-ins
960
+ this.getStringImportIndex(); // env.str
961
+ this.getStringLenImportIndex(); // env.str_len
962
+ this.getStringConcatImportIndex(); // env.str_concat
963
+ this.getStringCmpImportIndex("=="); // env.str_eq
964
+ this.getStringCmpImportIndex("!="); // env.str_ne
965
+ this.getStringCmpImportIndex("<"); // env.str_lt
966
+ this.getStringCmpImportIndex("<="); // env.str_le
967
+ this.getStringCmpImportIndex(">"); // env.str_gt
968
+ this.getStringCmpImportIndex(">="); // env.str_ge
969
+ this.getStringGetImportIndex(); // env.str_get
970
+ this.getStringSliceImportIndex(); // env.str_slice
971
+ this.getStringFromCodePointImportIndex(); // env.str_from_code_point
972
+ this.getStringCodePointAtImportIndex(); // env.str_code_point_at
973
+ this.getStringHashImportIndex(); // env.str_hash
974
+ this.getStringToUpperImportIndex();
975
+ this.getStringToLowerImportIndex();
976
+ this.getStringTrimSpaceImportIndex();
977
+ this.getStringContainsImportIndex();
978
+ this.getStringHasPrefixImportIndex();
979
+ this.getStringHasSuffixImportIndex();
980
+ this.getStringIndexImportIndex();
981
+ this.getStringLastIndexImportIndex();
982
+ this.getStringRepeatImportIndex();
983
+ this.getStringReplaceAllImportIndex();
984
+ this.getStringEqualFoldImportIndex();
985
+ this.getStringCountImportIndex();
986
+ this.getStringToValidUTF8ImportIndex();
987
+ this.getStrFromI64ImportIndex();
988
+ this.getStrFromI32ImportIndex();
989
+ this.getStrFromF64ImportIndex();
990
+ this.getIsStringImportIndex(); // env.is_string
991
+ this.getStrToBytesImportIndex();
992
+ this.getStrByteAtImportIndex();
993
+ this.getBytesToStrInitImportIndex();
994
+ this.getBytesToStrPushImportIndex();
995
+ this.getBytesToStrFinishImportIndex();
996
+ this.getTestingCallImportIndex();
997
+ this.getTestingNameImportIndex();
998
+ this.getTestingFlagImportIndex();
999
+ for (const t of ["i32", "i64", "f32", "f64", "externref", "anyref"]) {
1000
+ this.getTestingArgImportIndex(t, false);
1001
+ }
1002
+ this.getTestingArgImportIndex("i32", true);
1003
+ this.getTestingArgErrImportIndex();
1004
+ this.getTestingArgSpreadImportIndex();
1005
+ this.getTestingArgU64ImportIndex();
1006
+ this.getFmtArgTypeImportIndex();
1007
+ this.getFmtStructTypeImportIndex();
1008
+ this.getFmtNewArrayIndex();
1009
+ this.getFmtArrayPushIndex();
1010
+ this.getFmtNewMapIndex();
1011
+ this.getFmtMapSetIndex();
1012
+ this.getFmtNewStructIndex();
1013
+ this.getFmtStructSetIndex();
1014
+ this.getFmtBoxI32Index();
1015
+ this.getFmtBoxI64Index();
1016
+ this.getFmtBoxF64Index();
1017
+ this.getFmtBoxBoolIndex();
1018
+ this.getFmtNewBoxIndex();
1019
+ this.getFmtNewStringerIndex();
1020
+ if (this.usesStdlibEnv) {
1021
+ for (const [name, params, results] of STDLIB_IMPORTS) {
1022
+ this.getOrAddFuncImport("env", name, params, results);
1023
+ }
1024
+ }
1025
+
1026
+ // Logging built-ins
1027
+ this.getPrintlnEmptyIndex(); // env.println_empty
1028
+ const logTypes = ["i32", "i64", "f32", "f64", "externref", "anyref"];
1029
+ for (const t of logTypes) {
1030
+ this.getLogImportIndex(t, false, false); // env.print_*
1031
+ this.getLogImportIndex(t, true, false); // env.println_*
1032
+ }
1033
+ this.getLogImportIndex("i32", false, true); // env.print_bool
1034
+ this.getLogImportIndex("i32", true, true); // env.println_bool
1035
+
1036
+ // Math built-ins
1037
+ const unaryMath = [
1038
+ "sin",
1039
+ "cos",
1040
+ "tan",
1041
+ "asin",
1042
+ "acos",
1043
+ "atan",
1044
+ "exp",
1045
+ "log",
1046
+ "log2",
1047
+ "log10",
1048
+ "round",
1049
+ "cbrt",
1050
+ "log1p",
1051
+ "expm1",
1052
+ ];
1053
+ for (const m of unaryMath) {
1054
+ this.getMathImportIndex(m, false);
1055
+ }
1056
+ for (const m of BINARY_MATH_IMPORTS) {
1057
+ this.getMathImportIndex(m, true);
1058
+ }
1059
+ }
1060
+
1061
+ _initPanicTag() {
1062
+ // Type: (externref) -> ()
1063
+ const typeIdx = this.getTypeIndex(["externref"], []);
1064
+ // Tag 0
1065
+ this.tags.push({ typeIndex: typeIdx });
1066
+ // Export tag 0 as "panicTag"
1067
+ this.exports.push({ name: "panicTag", kind: "tag", index: 0 });
1068
+ }
1069
+
1070
+ getPanicNodeTypeIndex() {
1071
+ if (this.panicNodeTypeIndex !== undefined) {
1072
+ return this.panicNodeTypeIndex;
1073
+ }
1074
+ const typeIndex = this._getTotalTypeCount();
1075
+ const typeEntry = {
1076
+ form: "rec",
1077
+ types: [
1078
+ {
1079
+ form: "struct",
1080
+ fields: [
1081
+ {
1082
+ type: "anyref",
1083
+ mutable: true,
1084
+ },
1085
+ {
1086
+ type: "i32",
1087
+ mutable: true,
1088
+ },
1089
+ {
1090
+ type: {
1091
+ kind: "ref",
1092
+ nullable: true,
1093
+ typeIndex,
1094
+ },
1095
+ mutable: true,
1096
+ },
1097
+ ],
1098
+ },
1099
+ ],
1100
+ };
1101
+ this._pushType(typeEntry);
1102
+ this.panicNodeTypeIndex = typeIndex;
1103
+ return typeIndex;
1104
+ }
1105
+
1106
+ _initPanicGlobal() {
1107
+ const pType = this.getPanicNodeTypeIndex();
1108
+ const gIdx = this.globals.length;
1109
+ this.globals.push({
1110
+ type: { kind: "ref", nullable: true, typeIndex: pType },
1111
+ mutable: true,
1112
+ init: [{ op: "ref.null", typeIndex: pType }],
1113
+ });
1114
+ this.panicGlobalIndex = gIdx;
1115
+ this.exports.push({ name: "__panic", kind: "global", index: gIdx });
1116
+ }
1117
+
1118
+ getPanicGlobalIndex() {
1119
+ return this.panicGlobalIndex;
1120
+ }
1121
+
1122
+ // `__deferArmed` is set to 1 immediately before a deferred closure is
1123
+ // invoked and cleared by that closure's prologue. recover() only takes
1124
+ // effect when the current frame observed the flag, which gives Go's
1125
+ // "recover must be called directly by the deferred function" rule.
1126
+ getDeferArmedGlobalIndex() {
1127
+ if (this.deferArmedGlobalIndex !== undefined) {
1128
+ return this.deferArmedGlobalIndex;
1129
+ }
1130
+ const gIdx = this.globals.length;
1131
+ this.globals.push({
1132
+ type: "i32",
1133
+ mutable: true,
1134
+ init: [{ op: "i32.const", value: 0 }],
1135
+ });
1136
+ this.deferArmedGlobalIndex = gIdx;
1137
+ return gIdx;
1138
+ }
1139
+
1140
+ getRandStateGlobalIndex() {
1141
+ if (this.randStateGlobalIndex !== undefined) {
1142
+ return this.randStateGlobalIndex;
1143
+ }
1144
+ const gIdx = this.globals.length;
1145
+ this.globals.push({
1146
+ type: "i64",
1147
+ mutable: true,
1148
+ init: [{ op: "i64.const", value: 1n }],
1149
+ });
1150
+ this.randStateGlobalIndex = gIdx;
1151
+ return gIdx;
1152
+ }
1153
+
1154
+ getStrconvErrGlobalIndex(field) {
1155
+ if (!this.strconvErrGlobals) this.strconvErrGlobals = new Map();
1156
+ if (this.strconvErrGlobals.has(field)) {
1157
+ return this.strconvErrGlobals.get(field);
1158
+ }
1159
+ const gIdx = this.globals.length;
1160
+ this.globals.push({
1161
+ type: "anyref",
1162
+ mutable: true,
1163
+ init: [{ op: "ref.null", heapType: "any" }],
1164
+ });
1165
+ this.strconvErrGlobals.set(field, gIdx);
1166
+ return gIdx;
1167
+ }
1168
+
1169
+ getPathErrGlobalIndex(field) {
1170
+ if (!this.pathErrGlobals) this.pathErrGlobals = new Map();
1171
+ if (this.pathErrGlobals.has(field)) {
1172
+ return this.pathErrGlobals.get(field);
1173
+ }
1174
+ const gIdx = this.globals.length;
1175
+ this.globals.push({
1176
+ type: "anyref",
1177
+ mutable: true,
1178
+ init: [{ op: "ref.null", heapType: "any" }],
1179
+ });
1180
+ this.pathErrGlobals.set(field, gIdx);
1181
+ return gIdx;
1182
+ }
1183
+
1184
+ getIoErrGlobalIndex(field) {
1185
+ if (!this.ioErrGlobals) this.ioErrGlobals = new Map();
1186
+ if (this.ioErrGlobals.has(field)) {
1187
+ return this.ioErrGlobals.get(field);
1188
+ }
1189
+ const gIdx = this.globals.length;
1190
+ this.globals.push({
1191
+ type: "anyref",
1192
+ mutable: true,
1193
+ init: [{ op: "ref.null", heapType: "any" }],
1194
+ });
1195
+ this.ioErrGlobals.set(field, gIdx);
1196
+ return gIdx;
1197
+ }
1198
+
1199
+ getIoDiscardGlobalIndex() {
1200
+ if (this.ioDiscardGlobalIndex !== undefined) {
1201
+ return this.ioDiscardGlobalIndex;
1202
+ }
1203
+ this.getStructType("io.discard");
1204
+ const gIdx = this.globals.length;
1205
+ this.globals.push({
1206
+ type: "anyref",
1207
+ mutable: true,
1208
+ init: [{ op: "ref.null", heapType: "any" }],
1209
+ });
1210
+ this.ioDiscardGlobalIndex = gIdx;
1211
+ return gIdx;
1212
+ }
1213
+
1214
+ getOsGlobalIndex(field) {
1215
+ this.osGlobals ??= new Map();
1216
+ if (this.osGlobals.has(field)) return this.osGlobals.get(field);
1217
+ const gIdx = this.globals.length;
1218
+ this.globals.push({
1219
+ type: "anyref",
1220
+ mutable: true,
1221
+ init: [{ op: "ref.null", heapType: "any" }],
1222
+ });
1223
+ this.osGlobals.set(field, gIdx);
1224
+ return gIdx;
1225
+ }
1226
+
1227
+ getDeferNodeTypeIndex() {
1228
+ if (this.deferNodeTypeIndex !== undefined) {
1229
+ return this.deferNodeTypeIndex;
1230
+ }
1231
+ const deferClosureSig = { kind: "Signature", params: [], results: [] };
1232
+ const deferClosureInfo = this.getClosureType(deferClosureSig);
1233
+ const typeIndex = this._getTotalTypeCount();
1234
+ const typeEntry = {
1235
+ form: "rec",
1236
+ types: [
1237
+ {
1238
+ form: "struct",
1239
+ fields: [
1240
+ {
1241
+ type: {
1242
+ kind: "ref",
1243
+ nullable: true,
1244
+ typeIndex: deferClosureInfo.typeIndex,
1245
+ },
1246
+ mutable: false,
1247
+ },
1248
+ {
1249
+ type: {
1250
+ kind: "ref",
1251
+ nullable: true,
1252
+ typeIndex,
1253
+ },
1254
+ mutable: true,
1255
+ },
1256
+ ],
1257
+ },
1258
+ ],
1259
+ };
1260
+ this._pushType(typeEntry);
1261
+ this.deferNodeTypeIndex = typeIndex;
1262
+ return typeIndex;
1263
+ }
1264
+
1265
+ getTypeIndex(params, results) {
1266
+ const key = `${params.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${results.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
1267
+ if (this.typeCache.has(key)) {
1268
+ return this.typeCache.get(key);
1269
+ }
1270
+ const idx = this._getTotalTypeCount();
1271
+ this._pushType({ form: "func", params, results });
1272
+ this.typeCache.set(key, idx);
1273
+ return idx;
1274
+ }
1275
+
1276
+ internString(str) {
1277
+ if (this.stringCache.has(str)) {
1278
+ return this.stringCache.get(str);
1279
+ }
1280
+ const idx = this.stringTable.length;
1281
+ this.stringTable.push(str);
1282
+ this.stringCache.set(str, idx);
1283
+ return idx;
1284
+ }
1285
+
1286
+ getOrAddFuncImport(module, name, params, results) {
1287
+ const key = `${module}.${name}`;
1288
+ if (this.importCache.has(key)) {
1289
+ return this.importCache.get(key);
1290
+ }
1291
+ if (this._importsLocked) {
1292
+ throw new Error(`Unexpected import added after lock: ${key}`);
1293
+ }
1294
+ const typeIndex = this.getTypeIndex(params, results);
1295
+ const globalFuncIdx = this.imports.filter((i) => i.kind === "func").length;
1296
+ this.imports.push({
1297
+ module,
1298
+ name,
1299
+ kind: "func",
1300
+ typeIndex,
1301
+ });
1302
+ this.importCache.set(key, globalFuncIdx);
1303
+ return globalFuncIdx;
1304
+ }
1305
+
1306
+ getStringImportIndex() {
1307
+ return this.getOrAddFuncImport("env", "str", ["i32"], ["externref"]);
1308
+ }
1309
+
1310
+ // Panics are raised by a JS import that throws a plain `Error`, so wasm
1311
+ // frames unwind without a wasm `throw` and the JS caller needs no
1312
+ // try/catch guard around exports. (V8 cannot inline JS→wasm calls made
1313
+ // inside a try block, and a non-inlined call boxes every float argument.)
1314
+ getPanicImportIndex() {
1315
+ return this.getOrAddFuncImport("env", "panic", ["externref"], []);
1316
+ }
1317
+
1318
+ getBoundsPanicFuncIndex() {
1319
+ return this.resolveFuncIndex("__bounds_panic");
1320
+ }
1321
+
1322
+ getIndexPanicFuncIndex() {
1323
+ return this.resolveFuncIndex("__index_panic");
1324
+ }
1325
+
1326
+ getNilPanicFuncIndex() {
1327
+ return this.resolveFuncIndex("__nil_panic");
1328
+ }
1329
+
1330
+ // `gofront/shared` buffers are `(base i32, len i32)` views over the single
1331
+ // exported linear memory, bump-allocated by `__shared_new` during start.
1332
+ getSharedType() {
1333
+ if (this.sharedType) return this.sharedType;
1334
+ const typeIndex = this._getTotalTypeCount();
1335
+ const typeEntry = {
1336
+ form: "struct",
1337
+ fields: [
1338
+ { type: "i32", mutable: false }, // byte base
1339
+ { type: "i32", mutable: false }, // element count
1340
+ ],
1341
+ };
1342
+ this._pushType(typeEntry);
1343
+ this.memory = { min: 1 };
1344
+ this.exports.push({ name: "memory", kind: "memory", index: 0 });
1345
+ const topGlobalIndex = this.globals.length;
1346
+ this.globals.push({
1347
+ type: "i32",
1348
+ mutable: true,
1349
+ init: [{ op: "i32.const", value: 0 }],
1350
+ });
1351
+ const wType = { kind: "ref", nullable: true, typeIndex };
1352
+ this.sharedType = { typeIndex, typeEntry, wType, topGlobalIndex };
1353
+ return this.sharedType;
1354
+ }
1355
+
1356
+ getSliceBoundsPanicFuncIndex() {
1357
+ return this.resolveFuncIndex("__slice_bounds_panic");
1358
+ }
1359
+
1360
+ getPrintlnEmptyIndex() {
1361
+ return this.getOrAddFuncImport("env", "println_empty", [], []);
1362
+ }
1363
+
1364
+ getFmtNewArrayIndex() {
1365
+ return this.getOrAddFuncImport("env", "fmt_new_array", [], ["externref"]);
1366
+ }
1367
+
1368
+ getFmtArrayPushIndex() {
1369
+ return this.getOrAddFuncImport(
1370
+ "env",
1371
+ "fmt_array_push",
1372
+ ["externref", "externref"],
1373
+ [],
1374
+ );
1375
+ }
1376
+
1377
+ getFmtNewMapIndex() {
1378
+ return this.getOrAddFuncImport("env", "fmt_new_map", [], ["externref"]);
1379
+ }
1380
+
1381
+ getFmtMapSetIndex() {
1382
+ return this.getOrAddFuncImport(
1383
+ "env",
1384
+ "fmt_map_set",
1385
+ ["externref", "externref", "externref"],
1386
+ [],
1387
+ );
1388
+ }
1389
+
1390
+ getFmtNewStringerIndex() {
1391
+ return this.getOrAddFuncImport(
1392
+ "env",
1393
+ "fmt_new_stringer",
1394
+ ["externref", "externref", "i32"],
1395
+ ["externref"],
1396
+ );
1397
+ }
1398
+
1399
+ getFmtNewStructIndex() {
1400
+ return this.getOrAddFuncImport(
1401
+ "env",
1402
+ "fmt_new_struct",
1403
+ ["anyref", "i32"],
1404
+ ["externref"],
1405
+ );
1406
+ }
1407
+
1408
+ // Names the Go type of a struct built for fmt (`%T`, `%#v` of an any).
1409
+ getFmtStructTypeImportIndex() {
1410
+ return this.getOrAddFuncImport(
1411
+ "env",
1412
+ "fmt_struct_type",
1413
+ ["externref", "i32"],
1414
+ [],
1415
+ );
1416
+ }
1417
+
1418
+ getFmtStructSetIndex() {
1419
+ return this.getOrAddFuncImport(
1420
+ "env",
1421
+ "fmt_struct_set",
1422
+ ["externref", "i32", "externref"],
1423
+ [],
1424
+ );
1425
+ }
1426
+
1427
+ getFmtBoxI32Index() {
1428
+ return this.getOrAddFuncImport(
1429
+ "env",
1430
+ "fmt_box_i32",
1431
+ ["i32"],
1432
+ ["externref"],
1433
+ );
1434
+ }
1435
+
1436
+ getFmtBoxI64Index() {
1437
+ return this.getOrAddFuncImport(
1438
+ "env",
1439
+ "fmt_box_i64",
1440
+ ["i64"],
1441
+ ["externref"],
1442
+ );
1443
+ }
1444
+
1445
+ // Registered only for programs whose fmt operands may hold a uint64
1446
+ // (see scanFmtU64), so other modules keep their import table.
1447
+ getFmtBoxU64Index() {
1448
+ return this.getOrAddFuncImport(
1449
+ "env",
1450
+ "fmt_box_u64",
1451
+ ["i64"],
1452
+ ["externref"],
1453
+ );
1454
+ }
1455
+
1456
+ getFmtBoxF64Index() {
1457
+ return this.getOrAddFuncImport(
1458
+ "env",
1459
+ "fmt_box_f64",
1460
+ ["f64"],
1461
+ ["externref"],
1462
+ );
1463
+ }
1464
+
1465
+ getFmtBoxBoolIndex() {
1466
+ return this.getOrAddFuncImport(
1467
+ "env",
1468
+ "fmt_box_bool",
1469
+ ["i32"],
1470
+ ["externref"],
1471
+ );
1472
+ }
1473
+
1474
+ getFmtNewBoxIndex() {
1475
+ return this.getOrAddFuncImport("env", "fmt_new_box", [], ["externref"]);
1476
+ }
1477
+
1478
+ // `*testing.T` method calls: args are pushed one by one to a JS-side
1479
+ // buffer, then `testing_call(t, methodNameStrIdx)` invokes the method.
1480
+ getTestingArgImportIndex(wType, isBool = false) {
1481
+ if (isBool)
1482
+ return this.getOrAddFuncImport("env", "testing_arg_bool", ["i32"], []);
1483
+ const suffix =
1484
+ wType === "externref" ? "str" : wType === "anyref" ? "any" : wType;
1485
+ if (!["i32", "i64", "f32", "f64", "str", "any"].includes(suffix)) {
1486
+ throw new Error(
1487
+ "only primitive, string, error and any arguments are supported for fmt and testing.T calls in wasm packages (format slices/structs field by field)",
1488
+ );
1489
+ }
1490
+ return this.getOrAddFuncImport(
1491
+ "env",
1492
+ `testing_arg_${suffix}`,
1493
+ [suffix === "str" ? "externref" : suffix === "any" ? "anyref" : wType],
1494
+ [],
1495
+ );
1496
+ }
1497
+
1498
+ // Error operands of fmt.Sprint/Print/Append: a non-string for the host.
1499
+ getTestingArgSpreadImportIndex() {
1500
+ return this.getOrAddFuncImport(
1501
+ "env",
1502
+ "testing_arg_spread",
1503
+ ["externref"],
1504
+ [],
1505
+ );
1506
+ }
1507
+
1508
+ // An unsigned 64-bit operand: the host reinterprets the i64 as unsigned.
1509
+ getTestingArgU64ImportIndex() {
1510
+ return this.getOrAddFuncImport("env", "testing_arg_u64", ["i64"], []);
1511
+ }
1512
+
1513
+ // Tags the last operand in the argument buffer with the static type
1514
+ // descriptor (JSON) held in string table entry `i32`, for `%T` and `%#v`.
1515
+ getFmtArgTypeImportIndex() {
1516
+ return this.getOrAddFuncImport("env", "fmt_arg_type", ["i32"], []);
1517
+ }
1518
+
1519
+ getTestingArgErrImportIndex() {
1520
+ return this.getOrAddFuncImport("env", "testing_arg_err", ["externref"], []);
1521
+ }
1522
+
1523
+ getTestingCallImportIndex() {
1524
+ return this.getOrAddFuncImport(
1525
+ "env",
1526
+ "testing_call",
1527
+ ["externref", "i32"],
1528
+ [],
1529
+ );
1530
+ }
1531
+
1532
+ getTestingNameImportIndex() {
1533
+ return this.getOrAddFuncImport(
1534
+ "env",
1535
+ "testing_name",
1536
+ ["externref"],
1537
+ ["externref"],
1538
+ );
1539
+ }
1540
+
1541
+ getTestingFlagImportIndex() {
1542
+ return this.getOrAddFuncImport(
1543
+ "env",
1544
+ "testing_flag",
1545
+ ["externref", "i32"],
1546
+ ["i32"],
1547
+ );
1548
+ }
1549
+
1550
+ getLogImportIndex(wType, isLast = false, isBool = false) {
1551
+ const prefix = isLast ? "println" : "print";
1552
+ if (isBool) {
1553
+ return this.getOrAddFuncImport("env", `${prefix}_bool`, ["i32"], []);
1554
+ }
1555
+ switch (wType) {
1556
+ case "i32":
1557
+ return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
1558
+ case "i64":
1559
+ return this.getOrAddFuncImport("env", `${prefix}_i64`, ["i64"], []);
1560
+ case "f32":
1561
+ return this.getOrAddFuncImport("env", `${prefix}_f32`, ["f32"], []);
1562
+ case "f64":
1563
+ return this.getOrAddFuncImport("env", `${prefix}_f64`, ["f64"], []);
1564
+ case "externref":
1565
+ return this.getOrAddFuncImport(
1566
+ "env",
1567
+ `${prefix}_str`,
1568
+ ["externref"],
1569
+ [],
1570
+ );
1571
+ default:
1572
+ if (typeof wType === "object") {
1573
+ return this.getOrAddFuncImport(
1574
+ "env",
1575
+ `${prefix}_any`,
1576
+ ["anyref"],
1577
+ [],
1578
+ );
1579
+ }
1580
+ return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
1581
+ }
1582
+ }
1583
+
1584
+ getStringLenImportIndex() {
1585
+ return this.getOrAddFuncImport("env", "str_len", ["externref"], ["i32"]);
1586
+ }
1587
+
1588
+ getStringConcatImportIndex() {
1589
+ return this.getOrAddFuncImport(
1590
+ "env",
1591
+ "str_concat",
1592
+ ["externref", "externref"],
1593
+ ["externref"],
1594
+ );
1595
+ }
1596
+
1597
+ getStringCmpImportIndex(op) {
1598
+ const opMap = {
1599
+ "==": "str_eq",
1600
+ "!=": "str_ne",
1601
+ "<": "str_lt",
1602
+ "<=": "str_le",
1603
+ ">": "str_gt",
1604
+ ">=": "str_ge",
1605
+ };
1606
+ const fn = opMap[op] ?? "str_eq";
1607
+ return this.getOrAddFuncImport(
1608
+ "env",
1609
+ fn,
1610
+ ["externref", "externref"],
1611
+ ["i32"],
1612
+ );
1613
+ }
1614
+
1615
+ getStringGetImportIndex() {
1616
+ return this.getOrAddFuncImport(
1617
+ "env",
1618
+ "str_get",
1619
+ ["externref", "i32"],
1620
+ ["i32"],
1621
+ );
1622
+ }
1623
+
1624
+ getStringSliceImportIndex() {
1625
+ return this.getOrAddFuncImport(
1626
+ "env",
1627
+ "str_slice",
1628
+ ["externref", "i32", "i32"],
1629
+ ["externref"],
1630
+ );
1631
+ }
1632
+
1633
+ getStringFromCodePointImportIndex() {
1634
+ return this.getOrAddFuncImport(
1635
+ "env",
1636
+ "str_from_code_point",
1637
+ ["i32"],
1638
+ ["externref"],
1639
+ );
1640
+ }
1641
+
1642
+ getStringCodePointAtImportIndex() {
1643
+ return this.getOrAddFuncImport(
1644
+ "env",
1645
+ "str_code_point_at",
1646
+ ["externref", "i32"],
1647
+ ["i32"],
1648
+ );
1649
+ }
1650
+
1651
+ getIsStringImportIndex() {
1652
+ return this.getOrAddFuncImport("env", "is_string", ["anyref"], ["i32"]);
1653
+ }
1654
+
1655
+ getStringHashImportIndex() {
1656
+ return this.getOrAddFuncImport("env", "str_hash", ["externref"], ["i32"]);
1657
+ }
1658
+
1659
+ getStringToUpperImportIndex() {
1660
+ return this.getOrAddFuncImport(
1661
+ "env",
1662
+ "str_to_upper",
1663
+ ["externref"],
1664
+ ["externref"],
1665
+ );
1666
+ }
1667
+
1668
+ getStringToLowerImportIndex() {
1669
+ return this.getOrAddFuncImport(
1670
+ "env",
1671
+ "str_to_lower",
1672
+ ["externref"],
1673
+ ["externref"],
1674
+ );
1675
+ }
1676
+
1677
+ getStringTrimSpaceImportIndex() {
1678
+ return this.getOrAddFuncImport(
1679
+ "env",
1680
+ "str_trim_space",
1681
+ ["externref"],
1682
+ ["externref"],
1683
+ );
1684
+ }
1685
+
1686
+ getStringContainsImportIndex() {
1687
+ return this.getOrAddFuncImport(
1688
+ "env",
1689
+ "str_contains",
1690
+ ["externref", "externref"],
1691
+ ["i32"],
1692
+ );
1693
+ }
1694
+
1695
+ getStringHasPrefixImportIndex() {
1696
+ return this.getOrAddFuncImport(
1697
+ "env",
1698
+ "str_has_prefix",
1699
+ ["externref", "externref"],
1700
+ ["i32"],
1701
+ );
1702
+ }
1703
+
1704
+ getStringHasSuffixImportIndex() {
1705
+ return this.getOrAddFuncImport(
1706
+ "env",
1707
+ "str_has_suffix",
1708
+ ["externref", "externref"],
1709
+ ["i32"],
1710
+ );
1711
+ }
1712
+
1713
+ getStringIndexImportIndex() {
1714
+ return this.getOrAddFuncImport(
1715
+ "env",
1716
+ "str_index",
1717
+ ["externref", "externref"],
1718
+ ["i32"],
1719
+ );
1720
+ }
1721
+
1722
+ getStringLastIndexImportIndex() {
1723
+ return this.getOrAddFuncImport(
1724
+ "env",
1725
+ "str_last_index",
1726
+ ["externref", "externref"],
1727
+ ["i32"],
1728
+ );
1729
+ }
1730
+
1731
+ getStringRepeatImportIndex() {
1732
+ return this.getOrAddFuncImport(
1733
+ "env",
1734
+ "str_repeat",
1735
+ ["externref", "i32"],
1736
+ ["externref"],
1737
+ );
1738
+ }
1739
+
1740
+ getStringReplaceAllImportIndex() {
1741
+ return this.getOrAddFuncImport(
1742
+ "env",
1743
+ "str_replace_all",
1744
+ ["externref", "externref", "externref"],
1745
+ ["externref"],
1746
+ );
1747
+ }
1748
+
1749
+ getStringEqualFoldImportIndex() {
1750
+ return this.getOrAddFuncImport(
1751
+ "env",
1752
+ "str_equal_fold",
1753
+ ["externref", "externref"],
1754
+ ["i32"],
1755
+ );
1756
+ }
1757
+
1758
+ getStringCountImportIndex() {
1759
+ return this.getOrAddFuncImport(
1760
+ "env",
1761
+ "str_count",
1762
+ ["externref", "externref"],
1763
+ ["i32"],
1764
+ );
1765
+ }
1766
+
1767
+ getStringToValidUTF8ImportIndex() {
1768
+ return this.getOrAddFuncImport(
1769
+ "env",
1770
+ "str_to_valid_utf8",
1771
+ ["externref", "externref"],
1772
+ ["externref"],
1773
+ );
1774
+ }
1775
+
1776
+ getStrFromI64ImportIndex() {
1777
+ return this.getOrAddFuncImport(
1778
+ "env",
1779
+ "str_from_i64",
1780
+ ["i64"],
1781
+ ["externref"],
1782
+ );
1783
+ }
1784
+
1785
+ getStrFromI32ImportIndex() {
1786
+ return this.getOrAddFuncImport(
1787
+ "env",
1788
+ "str_from_i32",
1789
+ ["i32"],
1790
+ ["externref"],
1791
+ );
1792
+ }
1793
+
1794
+ getStrFromF64ImportIndex() {
1795
+ return this.getOrAddFuncImport(
1796
+ "env",
1797
+ "str_from_f64",
1798
+ ["f64"],
1799
+ ["externref"],
1800
+ );
1801
+ }
1802
+
1803
+ getStrToBytesImportIndex() {
1804
+ return this.getOrAddFuncImport(
1805
+ "env",
1806
+ "str_to_bytes",
1807
+ ["externref"],
1808
+ ["i32"],
1809
+ );
1810
+ }
1811
+
1812
+ getStrByteAtImportIndex() {
1813
+ return this.getOrAddFuncImport("env", "str_byte_at", ["i32"], ["i32"]);
1814
+ }
1815
+
1816
+ getBytesToStrInitImportIndex() {
1817
+ return this.getOrAddFuncImport("env", "bytes_to_str_init", ["i32"], []);
1818
+ }
1819
+
1820
+ getBytesToStrPushImportIndex() {
1821
+ return this.getOrAddFuncImport("env", "bytes_to_str_push", ["i32"], []);
1822
+ }
1823
+
1824
+ getBytesToStrFinishImportIndex() {
1825
+ return this.getOrAddFuncImport(
1826
+ "env",
1827
+ "bytes_to_str_finish",
1828
+ [],
1829
+ ["externref"],
1830
+ );
1831
+ }
1832
+
1833
+ getMathImportIndex(jsName, isBinary = false) {
1834
+ const params = isBinary ? ["f64", "f64"] : ["f64"];
1835
+ return this.getOrAddFuncImport("Math", jsName, params, ["f64"]);
1836
+ }
1837
+
1838
+ getMathRandomImportIndex() {
1839
+ return this.getOrAddFuncImport("Math", "random", [], ["f64"]);
1840
+ }
1841
+
1842
+ // Imports from the STDLIB_IMPORTS table (strings/strconv/utf8/fmt/testing.Run).
1843
+ getStdlibImportIndex(name) {
1844
+ const idx = this.importCache.get(`env.${name}`);
1845
+ if (idx === undefined) throw new Error(`Unknown runtime import: ${name}`);
1846
+ return idx;
1847
+ }
1848
+
1849
+ getTimezoneOffsetImportIndex() {
1850
+ return this.getStdlibImportIndex("time_timezone_offset");
1851
+ }
1852
+
1853
+ // Closure type of `func(*testing.T)` (the t.Run body).
1854
+ getTestingRunClosureType() {
1855
+ return this.getClosureType({
1856
+ kind: "func",
1857
+ params: [{ kind: "pointer", base: { kind: "named", name: "testing.T" } }],
1858
+ returns: [],
1859
+ });
1860
+ }
1861
+
1862
+ // Closure type of `func()` (the t.Cleanup body).
1863
+ getTestingCleanupClosureType() {
1864
+ return this.getClosureType({
1865
+ kind: "func",
1866
+ params: [],
1867
+ returns: [],
1868
+ });
1869
+ }
1870
+
1871
+ resolveFuncIndex(name) {
1872
+ return this.funcMap.get(name) ?? null;
1873
+ }
1874
+
1875
+ getFuncParamTypes(name) {
1876
+ return this.funcParamTypes.get(name) ?? [];
1877
+ }
1878
+
1879
+ getFuncParamGoTypes(name) {
1880
+ return this.funcParamGoTypes.get(name) ?? [];
1881
+ }
1882
+
1883
+ resolveGlobal(name) {
1884
+ return this.globalCache.get(name) ?? null;
1885
+ }
1886
+
1887
+ resolveConst(name) {
1888
+ return this.constCache.get(name) ?? null;
1889
+ }
1890
+
1891
+ isNonLiteralConst(name) {
1892
+ return this.nonLiteralConsts.has(name);
1893
+ }
1894
+
1895
+ // Interface method dispatch is a `ref.test` chain over every concrete type
1896
+ // that declares `methodName` (no itabs); the static interface type is not
1897
+ // needed to build the candidate list.
1898
+ findInterfaceCandidates(_ifaceType, methodName) {
1899
+ const candidates = [];
1900
+ const seenKeys = new Set();
1901
+
1902
+ // 1. Struct types
1903
+ for (const [sName, sInfo] of this.structTypes.entries()) {
1904
+ const directMethodName = `${sName}.${methodName}`;
1905
+ if (this.funcMap.has(directMethodName)) {
1906
+ const fIdx = this.resolveFuncIndex(directMethodName);
1907
+ const paramGoTypes = this.getFuncParamGoTypes(directMethodName);
1908
+ const paramWTypes = this.getFuncParamTypes(directMethodName);
1909
+ const recvGoType = paramGoTypes[0];
1910
+ const isPtrRecv = recvGoType?.kind === "pointer";
1911
+
1912
+ if (isPtrRecv) {
1913
+ const key = `ptr:${sName}`;
1914
+ if (!seenKeys.has(key)) {
1915
+ seenKeys.add(key);
1916
+ candidates.push({
1917
+ recvTypeName: sName,
1918
+ testTypeIndex: sInfo.typeIndex,
1919
+ isBoxedValue: false,
1920
+ needsValueDeref: false,
1921
+ embedPath: null,
1922
+ funcIndex: fIdx,
1923
+ targetParamTypes: paramWTypes,
1924
+ structInfo: sInfo,
1925
+ });
1926
+ }
1927
+ } else {
1928
+ // Value receiver: both *sName and sName match
1929
+ const ptrKey = `ptr:${sName}`;
1930
+ if (!seenKeys.has(ptrKey)) {
1931
+ seenKeys.add(ptrKey);
1932
+ candidates.push({
1933
+ recvTypeName: sName,
1934
+ testTypeIndex: sInfo.typeIndex,
1935
+ isBoxedValue: false,
1936
+ needsValueDeref: true,
1937
+ embedPath: null,
1938
+ funcIndex: fIdx,
1939
+ targetParamTypes: paramWTypes,
1940
+ targetRecvWType: paramWTypes[0],
1941
+ structInfo: sInfo,
1942
+ });
1943
+ }
1944
+ const box = this.getIfaceBoxType({ kind: "named", name: sName });
1945
+ const valKey = `val:${sName}`;
1946
+ if (box && !seenKeys.has(valKey)) {
1947
+ seenKeys.add(valKey);
1948
+ candidates.push({
1949
+ recvTypeName: sName,
1950
+ testTypeIndex: box.typeIndex,
1951
+ isBoxedValue: true,
1952
+ needsValueDeref: false,
1953
+ embedPath: null,
1954
+ funcIndex: fIdx,
1955
+ targetParamTypes: paramWTypes,
1956
+ targetRecvWType: paramWTypes[0],
1957
+ structInfo: sInfo,
1958
+ });
1959
+ }
1960
+ }
1961
+ }
1962
+
1963
+ // Promoted methods on sName
1964
+ if (sInfo.embeds && sInfo.embeds.length > 0) {
1965
+ for (const embed of sInfo.embeds) {
1966
+ const embedMethodName = `${embed.name}.${methodName}`;
1967
+ if (this.funcMap.has(embedMethodName)) {
1968
+ const fIdx = this.resolveFuncIndex(embedMethodName);
1969
+ const paramGoTypes = this.getFuncParamGoTypes(embedMethodName);
1970
+ const paramWTypes = this.getFuncParamTypes(embedMethodName);
1971
+ const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
1972
+ const embedSInfo = this.getStructType(embed.name);
1973
+
1974
+ const ptrKey = `ptr:${sName}`;
1975
+ if (!seenKeys.has(ptrKey)) {
1976
+ seenKeys.add(ptrKey);
1977
+ candidates.push({
1978
+ recvTypeName: sName,
1979
+ testTypeIndex: sInfo.typeIndex,
1980
+ isBoxedValue: false,
1981
+ needsValueDeref: !isPtrRecv,
1982
+ embedPath: [
1983
+ {
1984
+ parentTypeIndex: sInfo.typeIndex,
1985
+ fieldIndex: embed.fieldIndex,
1986
+ },
1987
+ ],
1988
+ funcIndex: fIdx,
1989
+ targetParamTypes: paramWTypes,
1990
+ targetRecvWType: paramWTypes[0],
1991
+ structInfo: embedSInfo,
1992
+ });
1993
+ }
1994
+
1995
+ if (!isPtrRecv) {
1996
+ const box = this.getIfaceBoxType({ kind: "named", name: sName });
1997
+ const valKey = `val:${sName}`;
1998
+ if (box && !seenKeys.has(valKey)) {
1999
+ seenKeys.add(valKey);
2000
+ candidates.push({
2001
+ recvTypeName: sName,
2002
+ testTypeIndex: box.typeIndex,
2003
+ isBoxedValue: true,
2004
+ needsValueDeref: false,
2005
+ embedPath: [
2006
+ {
2007
+ parentTypeIndex: sInfo.typeIndex,
2008
+ fieldIndex: embed.fieldIndex,
2009
+ },
2010
+ ],
2011
+ funcIndex: fIdx,
2012
+ targetParamTypes: paramWTypes,
2013
+ targetRecvWType: paramWTypes[0],
2014
+ structInfo: embedSInfo,
2015
+ });
2016
+ }
2017
+ }
2018
+ }
2019
+ }
2020
+ }
2021
+ }
2022
+
2023
+ // 2. Named non-struct types
2024
+ for (const [fnName, fIdx] of this.funcMap.entries()) {
2025
+ if (fnName.endsWith(`.${methodName}`)) {
2026
+ const rName = fnName.slice(0, fnName.length - methodName.length - 1);
2027
+ if (!this.structTypes.has(rName)) {
2028
+ const rType = this.checker?.types?.get(rName) ?? {
2029
+ kind: "named",
2030
+ name: rName,
2031
+ };
2032
+ const paramGoTypes = this.getFuncParamGoTypes(fnName);
2033
+ const paramWTypes = this.getFuncParamTypes(fnName);
2034
+ const isPtrRecv = paramGoTypes[0]?.kind === "pointer";
2035
+ const isSlice = isSliceType(rType, this.checker);
2036
+
2037
+ if (isSlice) {
2038
+ const elemType = getSliceElemType(rType, this.checker);
2039
+ const sliceInfo = this.getSliceType(elemType);
2040
+ const ptrIfaceBox = this.getIfaceBoxType({
2041
+ kind: "pointer",
2042
+ base: rType,
2043
+ });
2044
+ const box = this.getBoxType(rType);
2045
+ if (isPtrRecv) {
2046
+ const ptrKey = `ptr:${rName}`;
2047
+ if (ptrIfaceBox && !seenKeys.has(ptrKey)) {
2048
+ seenKeys.add(ptrKey);
2049
+ candidates.push({
2050
+ recvTypeName: rName,
2051
+ testTypeIndex: ptrIfaceBox.typeIndex,
2052
+ isBoxedValue: true,
2053
+ needsValueDeref: false,
2054
+ embedPath: null,
2055
+ funcIndex: fIdx,
2056
+ targetParamTypes: paramWTypes,
2057
+ });
2058
+ }
2059
+ } else {
2060
+ const ptrKey = `ptr:${rName}`;
2061
+ if (ptrIfaceBox && box && !seenKeys.has(ptrKey)) {
2062
+ seenKeys.add(ptrKey);
2063
+ candidates.push({
2064
+ recvTypeName: rName,
2065
+ testTypeIndex: ptrIfaceBox.typeIndex,
2066
+ isBoxedValue: true,
2067
+ nilPtrPanic: true,
2068
+ needsValueDeref: false,
2069
+ embedPath: [
2070
+ {
2071
+ castTypeIndex: box.typeIndex,
2072
+ parentTypeIndex: box.typeIndex,
2073
+ fieldIndex: 0,
2074
+ },
2075
+ ],
2076
+ funcIndex: fIdx,
2077
+ targetParamTypes: paramWTypes,
2078
+ });
2079
+ }
2080
+ const key = `slice:${rName}`;
2081
+ if (sliceInfo && !seenKeys.has(key)) {
2082
+ seenKeys.add(key);
2083
+ candidates.push({
2084
+ recvTypeName: rName,
2085
+ testTypeIndex: sliceInfo.typeIndex,
2086
+ isBoxedValue: false,
2087
+ needsValueDeref: false,
2088
+ embedPath: null,
2089
+ funcIndex: fIdx,
2090
+ targetParamTypes: paramWTypes,
2091
+ });
2092
+ }
2093
+ }
2094
+ } else {
2095
+ const box = isPtrRecv
2096
+ ? this.getIfaceBoxType({ kind: "pointer", base: rType })
2097
+ : this.getIfaceBoxType(rType);
2098
+ const key = `${isPtrRecv ? "ptr" : "named"}:${rName}`;
2099
+ if (box && !seenKeys.has(key)) {
2100
+ seenKeys.add(key);
2101
+ candidates.push({
2102
+ recvTypeName: rName,
2103
+ testTypeIndex: box.typeIndex,
2104
+ isBoxedValue: true,
2105
+ needsValueDeref: false,
2106
+ embedPath: null,
2107
+ funcIndex: fIdx,
2108
+ targetParamTypes: paramWTypes,
2109
+ });
2110
+ }
2111
+ if (!isPtrRecv)
2112
+ this._addPtrScalarCandidate(
2113
+ candidates,
2114
+ seenKeys,
2115
+ rName,
2116
+ rType,
2117
+ fIdx,
2118
+ paramWTypes,
2119
+ );
2120
+ }
2121
+ }
2122
+ }
2123
+ }
2124
+
2125
+ return candidates;
2126
+ }
2127
+
2128
+ // A value-receiver method of a named scalar is also in the method set of
2129
+ // the pointer: `var g Getter = &x` holds a pointer box whose target is read
2130
+ // before the call.
2131
+ _addPtrScalarCandidate(
2132
+ candidates,
2133
+ seenKeys,
2134
+ rName,
2135
+ rType,
2136
+ fIdx,
2137
+ paramWTypes,
2138
+ ) {
2139
+ if (!isScalarType(rType, this.checker, this)) return;
2140
+ const ptrBox = this.getIfaceBoxType({ kind: "pointer", base: rType });
2141
+ const target = this.getBoxType(rType);
2142
+ const ptrKey = `ptr:${rName}`;
2143
+ if (!ptrBox || seenKeys.has(ptrKey)) return;
2144
+ seenKeys.add(ptrKey);
2145
+ candidates.push({
2146
+ recvTypeName: rName,
2147
+ testTypeIndex: ptrBox.typeIndex,
2148
+ isBoxedValue: true,
2149
+ nilPtrPanic: true,
2150
+ needsValueDeref: false,
2151
+ embedPath: [
2152
+ {
2153
+ castTypeIndex: target.typeIndex,
2154
+ parentTypeIndex: target.typeIndex,
2155
+ fieldIndex: 0,
2156
+ },
2157
+ ],
2158
+ funcIndex: fIdx,
2159
+ targetParamTypes: paramWTypes,
2160
+ });
2161
+ }
2162
+
2163
+ getTypesImplementingInterface(ifaceType) {
2164
+ const candidates = [];
2165
+ const seen = new Set();
2166
+ const ifaceResolved =
2167
+ this.checker?.resolveType?.(
2168
+ ifaceType?.kind === "named" ? ifaceType.underlying : ifaceType,
2169
+ ) ?? ifaceType;
2170
+
2171
+ // 1. Struct types
2172
+ for (const [sName, sInfo] of this.structTypes.entries()) {
2173
+ const namedType = this.checker?.types?.get(sName) ?? {
2174
+ kind: "named",
2175
+ name: sName,
2176
+ };
2177
+ const ptrType = { kind: "pointer", base: namedType };
2178
+
2179
+ // Check pointer to struct
2180
+ if (this.checker?.implements?.(ptrType, ifaceResolved)) {
2181
+ if (!seen.has(sInfo.typeIndex)) {
2182
+ seen.add(sInfo.typeIndex);
2183
+ candidates.push({ name: `*${sName}`, typeIndex: sInfo.typeIndex });
2184
+ }
2185
+ }
2186
+
2187
+ // Check struct value (boxed)
2188
+ if (this.checker?.implements?.(namedType, ifaceResolved)) {
2189
+ const box = this.getIfaceBoxType(namedType);
2190
+ if (box && !seen.has(box.typeIndex)) {
2191
+ seen.add(box.typeIndex);
2192
+ candidates.push({ name: sName, typeIndex: box.typeIndex });
2193
+ }
2194
+ }
2195
+ }
2196
+
2197
+ // 2. Named non-struct types that have methods
2198
+ if (this.checker?.types) {
2199
+ for (const [tName, tVal] of this.checker.types.entries()) {
2200
+ if (
2201
+ tVal?.kind === "named" &&
2202
+ tVal.underlying?.kind !== "struct" &&
2203
+ tVal.underlying?.kind !== "interface"
2204
+ ) {
2205
+ if (this.checker?.implements?.(tVal, ifaceResolved)) {
2206
+ const box = this.getIfaceBoxType(tVal);
2207
+ if (box && !seen.has(box.typeIndex)) {
2208
+ seen.add(box.typeIndex);
2209
+ candidates.push({ name: tName, typeIndex: box.typeIndex });
2210
+ }
2211
+ }
2212
+ this._addPtrNamedImplementor(
2213
+ tVal,
2214
+ tName,
2215
+ ifaceResolved,
2216
+ seen,
2217
+ candidates,
2218
+ );
2219
+ }
2220
+ }
2221
+ }
2222
+
2223
+ return candidates;
2224
+ }
2225
+
2226
+ // `*T` of a named scalar or slice is boxed apart from `T`.
2227
+ _addPtrNamedImplementor(tVal, tName, ifaceResolved, seen, candidates) {
2228
+ const ptrType = { kind: "pointer", base: tVal };
2229
+ if (
2230
+ !isScalarType(tVal, this.checker, this) &&
2231
+ !isSliceType(tVal, this.checker)
2232
+ )
2233
+ return;
2234
+ if (!this.checker?.implements?.(ptrType, ifaceResolved)) return;
2235
+ const box = this.getIfaceBoxType(ptrType);
2236
+ if (!box || seen.has(box.typeIndex)) return;
2237
+ seen.add(box.typeIndex);
2238
+ candidates.push({ name: `*${tName}`, typeIndex: box.typeIndex });
2239
+ }
2240
+ }