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
@@ -1,1892 +1,91 @@
1
1
  // src/backend/wasm/index.js
2
2
  // WASM Backend compiler entry point: lowered AST + types -> Module IR -> binary / WAT.
3
3
 
4
- import { Lexer } from "../../lexer.js";
5
- import { lower } from "../../lower/index.js";
6
- import { Parser } from "../../parser/index.js";
7
- import { TypeChecker } from "../../typechecker/index.js";
4
+ import { lower, lowerFunction } from "../../lower/index.js";
5
+ import {
6
+ getOrCreateIteratorFuncLit,
7
+ isIteratorRangeFor,
8
+ } from "../../lower/range.js";
8
9
  import {
9
10
  addBoundaryHelpers,
10
11
  collectBoundaryMeta,
11
12
  generateFacade,
12
13
  scanBoundaryImports,
13
- wTypeKey,
14
14
  } from "./boundary.js";
15
- import { foldConstExpr } from "./const-fold.js";
16
15
  import { FunctionEmitter } from "./emit.js";
17
16
  import { encodeModule, isGoFrontWasm } from "./encode.js";
17
+ import {
18
+ _collectPackageConsts,
19
+ _collectPackageGlobals,
20
+ _makeGlobalInitFunc,
21
+ _makeInterfaceDispatcher,
22
+ } from "./globals.js";
23
+ import { desugarMethodExprs } from "./method-expr.js";
24
+ import { ModuleEmitter } from "./module-emitter.js";
18
25
  import { hasGenerics, monomorphise } from "./monomorph.js";
19
26
  import { hasNativeWasmOpt, optimizeWasm } from "./optimize.js";
20
- import { BINARY_MATH_IMPORTS } from "./stdlib/index.js";
21
27
  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";
35
- import { emitWat } from "./wat.js";
36
-
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
- }
127
-
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 {
261
- constructor(checker, lowerResult = null, programs = [], options = {}) {
262
- this.checker = checker;
263
- this.lowerResult = lowerResult;
264
- // Local package names whose code is linked into this module; selectors
265
- // like `mathx.NewVec3` are de-qualified to `NewVec3` by the emitter.
266
- this.bundledPackages = options.bundledPackages ?? new Set();
267
- this.constCache = new Map(); // name -> BasicLit (package-level literal consts)
268
- this.nonLiteralConsts = new Set(); // package-level consts the backend cannot evaluate yet
269
-
270
- this.types = []; // type entries
271
- this.typeCache = new Map(); // signature string -> index
272
- this._openRec = null; // rec group receiving new types while structs are resolved
273
-
274
- this.structTypes = new Map(); // name -> struct info
275
- this.boxTypes = new Map(); // wType key -> box info
276
- this.ifaceBoxTypes = new Map(); // key -> iface box info
277
- this.arrayTypes = new Map(); // wType key -> array info
278
- this.sliceTypes = new Map(); // wType key -> slice info
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))
283
- this.closureTypes = new Map(); // key -> closure info
284
- this.envTypes = new Map(); // key -> env info
285
-
286
- this.elements = []; // declarative elements for ref.func
287
- this.elementSet = new Set();
288
-
289
- this.imports = []; // import entries
290
- this.importCache = new Map(); // key -> func index
291
-
292
- this.tags = []; // exception tags
293
- this.globals = []; // global entries
294
- this.globalCache = new Map(); // name -> index
295
-
296
- this.funcs = []; // internal func definitions
297
- this.funcMap = new Map(); // name -> global func index
298
- this.funcParamTypes = new Map(); // name -> param types array
299
- this.funcParamGoTypes = new Map(); // name -> param Go types array
300
- this.funcReturnTypesByIdx = new Map(); // global func index -> result wasm types
301
-
302
- this.exports = []; // export entries
303
- this.stringTable = []; // string literals
304
- this.stringCache = new Map();
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
-
313
- if (programs && programs.length > 0) {
314
- this.initTypes(programs);
315
- }
316
-
317
- this._importsLocked = false;
318
- this._initRuntimeImports();
319
- }
320
-
321
- initTypes(programs) {
322
- const progs = Array.isArray(programs) ? programs : [programs];
323
- const rawStructs = [];
324
- const seen = new Set();
325
- const aliases = []; // qualified key (pkg.T) -> unqualified struct name
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
-
341
- for (const p of progs) {
342
- for (const d of p.decls ?? []) {
343
- if (d.kind === "TypeDecl" && d.type?.kind === "StructType") {
344
- if (!seen.has(d.name)) {
345
- seen.add(d.name);
346
- rawStructs.push({
347
- name: d.name,
348
- astFields: d.type.fields ?? [],
349
- pkgName: p.pkg?.name ?? "main",
350
- pkgTarget: p.target ?? "wasm",
351
- });
352
- }
353
- }
354
- }
355
- }
356
-
357
- if (this.checker?.types) {
358
- for (const [key, t] of this.checker.types.entries()) {
359
- if (t.kind === "named" && t.underlying?.kind === "struct") {
360
- const name = t.name ?? key;
361
- if (!seen.has(name)) {
362
- seen.add(name);
363
- rawStructs.push({ name, structType: t.underlying });
364
- }
365
- if (key !== name) aliases.push([key, name]);
366
- }
367
- }
368
- }
369
-
370
- if (rawStructs.length === 0) return;
371
-
372
- // Field resolution may create array/slice/box types; they join this rec
373
- // group so struct indices (base + i) stay valid and may be referenced
374
- // from the auxiliary types in any order.
375
- const base = this._getTotalTypeCount();
376
- const structEntries = rawStructs.map(() => ({
377
- form: "struct",
378
- fields: [],
379
- }));
380
- this.types.push({ form: "rec", types: structEntries });
381
- this._openRec = structEntries;
382
-
383
- for (let i = 0; i < rawStructs.length; i++) {
384
- const s = rawStructs[i];
385
- this.structTypes.set(s.name, {
386
- name: s.name,
387
- typeIndex: base + i,
388
- fields: [],
389
- fieldIndexMap: new Map(),
390
- embeds: [],
391
- pkgName: s.pkgName ?? null,
392
- pkgTarget: s.pkgTarget ?? "wasm",
393
- });
394
- }
395
- // `mathx.Vec3` resolves to the same struct info as `Vec3` once linked.
396
- for (const [key, name] of aliases) {
397
- if (!this.structTypes.has(key))
398
- this.structTypes.set(key, this.structTypes.get(name));
399
- }
400
-
401
- for (let i = 0; i < rawStructs.length; i++) {
402
- const s = rawStructs[i];
403
- const info = this.structTypes.get(s.name);
404
- const resolvedStruct =
405
- s.structType ?? this.checker?.types?.get(s.name)?.underlying;
406
-
407
- const fields = [];
408
- const fieldIndexMap = new Map();
409
- const embeds = [];
410
-
411
- if (s.astFields && s.astFields.length > 0) {
412
- let fIdx = 0;
413
- for (const f of s.astFields) {
414
- const names = f.embedded
415
- ? [f.type.name]
416
- : f.names?.length
417
- ? f.names
418
- : f.name
419
- ? [f.name]
420
- : [];
421
- for (const n of names) {
422
- const wType = toWasmType(f.type, this.checker, this);
423
- fields.push({
424
- name: n,
425
- goType: f.type,
426
- wType,
427
- embedded: Boolean(f.embedded),
428
- });
429
- fieldIndexMap.set(n, fIdx);
430
- if (f.embedded) {
431
- embeds.push({
432
- name: n,
433
- fieldIndex: fIdx,
434
- });
435
- }
436
- fIdx++;
437
- }
438
- }
439
- } else if (resolvedStruct?.fields) {
440
- let fIdx = 0;
441
- for (const [fName, fType] of resolvedStruct.fields.entries()) {
442
- const wType = toWasmType(fType, this.checker, this);
443
- fields.push({ name: fName, goType: fType, wType });
444
- fieldIndexMap.set(fName, fIdx);
445
- fIdx++;
446
- }
447
- if (resolvedStruct?._embeds) {
448
- for (const embed of resolvedStruct._embeds) {
449
- const embedName = embed.kind === "named" ? embed.name : null;
450
- if (embedName && fieldIndexMap.has(embedName)) {
451
- embeds.push({
452
- name: embedName,
453
- fieldIndex: fieldIndexMap.get(embedName),
454
- });
455
- }
456
- }
457
- }
458
- }
459
-
460
- info.fields = fields;
461
- info.fieldIndexMap = fieldIndexMap;
462
- info.embeds = embeds;
463
-
464
- const typeEntry = structEntries[i];
465
- typeEntry.fields = fields.map((f) => ({ type: f.wType, mutable: true }));
466
- info.typeEntry = typeEntry;
467
- }
468
-
469
- this._openRec = null;
470
- }
471
-
472
- _pushType(typeEntry) {
473
- (this._openRec ?? this.types).push(typeEntry);
474
- }
475
-
476
- getStructType(name) {
477
- return this.structTypes.get(name) ?? null;
478
- }
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
-
515
- getBoxType(goType) {
516
- const key = goTypeKey(goType);
517
- if (this.boxTypes.has(key)) {
518
- return this.boxTypes.get(key);
519
- }
520
- const wType = toWasmType(goType, this.checker, this);
521
- const typeIndex = this._getTotalTypeCount();
522
- const typeEntry = {
523
- form: "struct",
524
- fields: [{ type: wType, mutable: true }],
525
- };
526
- this._pushType(typeEntry);
527
- const boxInfo = { key, typeIndex, typeEntry, wType, goType };
528
- this.boxTypes.set(key, boxInfo);
529
- return boxInfo;
530
- }
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
-
569
- getArrayType(elemGoType) {
570
- const elemWType = toWasmType(elemGoType, this.checker, this);
571
- const key =
572
- typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
573
- if (this.arrayTypes.has(key)) {
574
- return this.arrayTypes.get(key);
575
- }
576
- const typeIndex = this._getTotalTypeCount();
577
- const typeEntry = {
578
- form: "array",
579
- elemType: elemWType,
580
- mutable: true,
581
- };
582
- this._pushType(typeEntry);
583
- const arrInfo = { typeIndex, typeEntry, elemWType, elemGoType };
584
- this.arrayTypes.set(key, arrInfo);
585
- return arrInfo;
586
- }
587
-
588
- getSliceType(elemGoType) {
589
- const elemWType = toWasmType(elemGoType, this.checker, this);
590
- const key =
591
- typeof elemWType === "string" ? elemWType : JSON.stringify(elemWType);
592
- if (this.sliceTypes.has(key)) {
593
- return this.sliceTypes.get(key);
594
- }
595
- const arrInfo = this.getArrayType(elemGoType);
596
- const typeIndex = this._getTotalTypeCount();
597
- const typeEntry = {
598
- form: "struct",
599
- fields: [
600
- {
601
- type: {
602
- kind: "ref",
603
- nullable: true,
604
- typeIndex: arrInfo.typeIndex,
605
- },
606
- mutable: false,
607
- },
608
- { type: "i32", mutable: false }, // offset
609
- { type: "i32", mutable: false }, // len
610
- { type: "i32", mutable: false }, // cap
611
- ],
612
- };
613
- this._pushType(typeEntry);
614
-
615
- const emptyGlobalIndex = this.globals.length;
616
- this.globals.push({
617
- type: {
618
- kind: "ref",
619
- nullable: true,
620
- typeIndex,
621
- },
622
- mutable: false,
623
- init: [
624
- { op: "ref.null", heapType: arrInfo.typeIndex },
625
- { op: "i32.const", value: 0 },
626
- { op: "i32.const", value: 0 },
627
- { op: "i32.const", value: 0 },
628
- { op: "struct.new", typeIndex },
629
- ],
630
- });
631
-
632
- const sliceInfo = {
633
- typeIndex,
634
- typeEntry,
635
- arrInfo,
636
- elemWType,
637
- elemGoType,
638
- emptyGlobalIndex,
639
- };
640
- this.sliceTypes.set(key, sliceInfo);
641
- this.sliceTypesByIndex.set(typeIndex, sliceInfo);
642
- return sliceInfo;
643
- }
644
-
645
- getSliceTypeByIndex(typeIndex) {
646
- return this.sliceTypesByIndex?.get(typeIndex) ?? null;
647
- }
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
-
750
- getClosureType(goType) {
751
- const sig = getFuncSignature(goType, this.checker);
752
- const paramWTypes = sig.params
753
- .map((p) => toWasmType(p, this.checker, this))
754
- .filter(Boolean);
755
- const returnWTypes = sig.returns
756
- .map((r) => toWasmType(r, this.checker, this))
757
- .filter(Boolean);
758
-
759
- const key = `${paramWTypes.map((p) => (typeof p === "object" ? JSON.stringify(p) : p)).join(",")}=>${returnWTypes.map((r) => (typeof r === "object" ? JSON.stringify(r) : r)).join(",")}`;
760
- if (this.closureTypes.has(key)) {
761
- return this.closureTypes.get(key);
762
- }
763
-
764
- // 1. Function signature type index: (param anyref, ...paramWTypes) -> (...returnWTypes)
765
- const funcTypeIndex = this.getTypeIndex(
766
- ["anyref", ...paramWTypes],
767
- returnWTypes,
768
- );
769
-
770
- // 2. Closure struct type index: struct { fn: (ref funcTypeIndex), env: anyref }
771
- const typeIndex = this._getTotalTypeCount();
772
- const typeEntry = {
773
- form: "struct",
774
- fields: [
775
- {
776
- type: {
777
- kind: "ref",
778
- nullable: false,
779
- typeIndex: funcTypeIndex,
780
- },
781
- mutable: false,
782
- },
783
- {
784
- type: "anyref",
785
- mutable: true,
786
- },
787
- ],
788
- };
789
- this._pushType(typeEntry);
790
-
791
- const closureInfo = {
792
- typeIndex,
793
- typeEntry,
794
- funcTypeIndex,
795
- paramWTypes,
796
- returnWTypes,
797
- sig,
798
- };
799
- this.closureTypes.set(key, closureInfo);
800
- return closureInfo;
801
- }
802
-
803
- getEnvType(fieldTypes) {
804
- const key = fieldTypes
805
- .map((f) =>
806
- typeof f.wType === "object" ? JSON.stringify(f.wType) : f.wType,
807
- )
808
- .join(",");
809
- if (this.envTypes.has(key)) {
810
- return this.envTypes.get(key);
811
- }
812
- const typeIndex = this._getTotalTypeCount();
813
- const typeEntry = {
814
- form: "struct",
815
- fields: fieldTypes.map((f) => ({ type: f.wType, mutable: true })),
816
- };
817
- this._pushType(typeEntry);
818
- const envInfo = { typeIndex, typeEntry, fieldTypes };
819
- this.envTypes.set(key, envInfo);
820
- return envInfo;
821
- }
822
-
823
- addRefFuncElement(funcIdx) {
824
- if (!this.elementSet.has(funcIdx)) {
825
- this.elementSet.add(funcIdx);
826
- this.elements.push(funcIdx);
827
- }
828
- }
829
-
830
- _getTotalTypeCount() {
831
- let count = 0;
832
- for (const entry of this.types) {
833
- if (entry.form === "rec") count += entry.types.length;
834
- else count += 1;
835
- }
836
- return count;
837
- }
838
-
839
- _initRuntimeImports() {
840
- this._initPanicTag();
841
- this._initPanicGlobal();
842
- this.getPanicImportIndex(); // env.panic
843
- this.internString("runtime error: index out of range");
844
- this.internString("runtime error: slice bounds out of range");
845
- this.internString("assignment to entry in nil map");
846
-
847
- // String built-ins
848
- this.getStringImportIndex(); // env.str
849
- this.getStringLenImportIndex(); // env.str_len
850
- this.getStringConcatImportIndex(); // env.str_concat
851
- this.getStringCmpImportIndex("=="); // env.str_eq
852
- this.getStringCmpImportIndex("!="); // env.str_ne
853
- this.getStringCmpImportIndex("<"); // env.str_lt
854
- this.getStringCmpImportIndex("<="); // env.str_le
855
- this.getStringCmpImportIndex(">"); // env.str_gt
856
- this.getStringCmpImportIndex(">="); // env.str_ge
857
- this.getStringGetImportIndex(); // env.str_get
858
- this.getStringSliceImportIndex(); // env.str_slice
859
- this.getStringFromCodePointImportIndex(); // env.str_from_code_point
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();
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
- }
906
-
907
- // Logging built-ins
908
- this.getPrintlnEmptyIndex(); // env.println_empty
909
- const logTypes = ["i32", "i64", "f32", "f64", "externref", "anyref"];
910
- for (const t of logTypes) {
911
- this.getLogImportIndex(t, false, false); // env.print_*
912
- this.getLogImportIndex(t, true, false); // env.println_*
913
- }
914
- this.getLogImportIndex("i32", false, true); // env.print_bool
915
- this.getLogImportIndex("i32", true, true); // env.println_bool
916
-
917
- // Math built-ins
918
- const unaryMath = [
919
- "sin",
920
- "cos",
921
- "tan",
922
- "asin",
923
- "acos",
924
- "atan",
925
- "exp",
926
- "log",
927
- "log2",
928
- "log10",
929
- "round",
930
- "cbrt",
931
- ];
932
- for (const m of unaryMath) {
933
- this.getMathImportIndex(m, false);
934
- }
935
- for (const m of BINARY_MATH_IMPORTS) {
936
- this.getMathImportIndex(m, true);
937
- }
938
- }
939
-
940
- _initPanicTag() {
941
- // Type: (externref) -> ()
942
- const typeIdx = this.getTypeIndex(["externref"], []);
943
- // Tag 0
944
- this.tags.push({ typeIndex: typeIdx });
945
- // Export tag 0 as "panicTag"
946
- this.exports.push({ name: "panicTag", kind: "tag", index: 0 });
947
- }
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
-
1131
- getTypeIndex(params, results) {
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(",")}`;
1133
- if (this.typeCache.has(key)) {
1134
- return this.typeCache.get(key);
1135
- }
1136
- const idx = this._getTotalTypeCount();
1137
- this._pushType({ form: "func", params, results });
1138
- this.typeCache.set(key, idx);
1139
- return idx;
1140
- }
1141
-
1142
- internString(str) {
1143
- if (this.stringCache.has(str)) {
1144
- return this.stringCache.get(str);
1145
- }
1146
- const idx = this.stringTable.length;
1147
- this.stringTable.push(str);
1148
- this.stringCache.set(str, idx);
1149
- return idx;
1150
- }
1151
-
1152
- getOrAddFuncImport(module, name, params, results) {
1153
- const key = `${module}.${name}`;
1154
- if (this.importCache.has(key)) {
1155
- return this.importCache.get(key);
1156
- }
1157
- if (this._importsLocked) {
1158
- throw new Error(`Unexpected import added after lock: ${key}`);
1159
- }
1160
- const typeIndex = this.getTypeIndex(params, results);
1161
- const globalFuncIdx = this.imports.filter((i) => i.kind === "func").length;
1162
- this.imports.push({
1163
- module,
1164
- name,
1165
- kind: "func",
1166
- typeIndex,
1167
- });
1168
- this.importCache.set(key, globalFuncIdx);
1169
- return globalFuncIdx;
1170
- }
1171
-
1172
- getStringImportIndex() {
1173
- return this.getOrAddFuncImport("env", "str", ["i32"], ["externref"]);
1174
- }
1175
-
1176
- // Panics are raised by a JS import that throws a plain `Error`, so wasm
1177
- // frames unwind without a wasm `throw` and the JS caller needs no
1178
- // try/catch guard around exports. (V8 cannot inline JS→wasm calls made
1179
- // inside a try block, and a non-inlined call boxes every float argument.)
1180
- getPanicImportIndex() {
1181
- return this.getOrAddFuncImport("env", "panic", ["externref"], []);
1182
- }
1183
-
1184
- getBoundsPanicFuncIndex() {
1185
- return this.resolveFuncIndex("__bounds_panic");
1186
- }
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
-
1214
- getSliceBoundsPanicFuncIndex() {
1215
- return this.resolveFuncIndex("__slice_bounds_panic");
1216
- }
1217
-
1218
- getPrintlnEmptyIndex() {
1219
- return this.getOrAddFuncImport("env", "println_empty", [], []);
1220
- }
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
-
1306
- // `*testing.T` method calls: args are pushed one by one to a JS-side
1307
- // buffer, then `testing_call(t, methodNameStrIdx)` invokes the method.
1308
- getTestingArgImportIndex(wType, isBool = false) {
1309
- if (isBool)
1310
- return this.getOrAddFuncImport("env", "testing_arg_bool", ["i32"], []);
1311
- const suffix =
1312
- wType === "externref" ? "str" : wType === "anyref" ? "any" : wType;
1313
- if (!["i32", "i64", "f32", "f64", "str", "any"].includes(suffix)) {
1314
- throw new Error(
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)",
1316
- );
1317
- }
1318
- return this.getOrAddFuncImport(
1319
- "env",
1320
- `testing_arg_${suffix}`,
1321
- [suffix === "str" ? "externref" : suffix === "any" ? "anyref" : wType],
1322
- [],
1323
- );
1324
- }
1325
-
1326
- getTestingCallImportIndex() {
1327
- return this.getOrAddFuncImport(
1328
- "env",
1329
- "testing_call",
1330
- ["externref", "i32"],
1331
- [],
1332
- );
1333
- }
1334
-
1335
- getTestingNameImportIndex() {
1336
- return this.getOrAddFuncImport(
1337
- "env",
1338
- "testing_name",
1339
- ["externref"],
1340
- ["externref"],
1341
- );
1342
- }
1343
-
1344
- getTestingFlagImportIndex() {
1345
- return this.getOrAddFuncImport(
1346
- "env",
1347
- "testing_flag",
1348
- ["externref", "i32"],
1349
- ["i32"],
1350
- );
1351
- }
1352
-
1353
- getLogImportIndex(wType, isLast = false, isBool = false) {
1354
- const prefix = isLast ? "println" : "print";
1355
- if (isBool) {
1356
- return this.getOrAddFuncImport("env", `${prefix}_bool`, ["i32"], []);
1357
- }
1358
- switch (wType) {
1359
- case "i32":
1360
- return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
1361
- case "i64":
1362
- return this.getOrAddFuncImport("env", `${prefix}_i64`, ["i64"], []);
1363
- case "f32":
1364
- return this.getOrAddFuncImport("env", `${prefix}_f32`, ["f32"], []);
1365
- case "f64":
1366
- return this.getOrAddFuncImport("env", `${prefix}_f64`, ["f64"], []);
1367
- case "externref":
1368
- return this.getOrAddFuncImport(
1369
- "env",
1370
- `${prefix}_str`,
1371
- ["externref"],
1372
- [],
1373
- );
1374
- default:
1375
- if (typeof wType === "object") {
1376
- return this.getOrAddFuncImport(
1377
- "env",
1378
- `${prefix}_any`,
1379
- ["anyref"],
1380
- [],
1381
- );
1382
- }
1383
- return this.getOrAddFuncImport("env", `${prefix}_i32`, ["i32"], []);
1384
- }
1385
- }
1386
-
1387
- getStringLenImportIndex() {
1388
- return this.getOrAddFuncImport("env", "str_len", ["externref"], ["i32"]);
1389
- }
1390
-
1391
- getStringConcatImportIndex() {
1392
- return this.getOrAddFuncImport(
1393
- "env",
1394
- "str_concat",
1395
- ["externref", "externref"],
1396
- ["externref"],
1397
- );
1398
- }
1399
-
1400
- getStringCmpImportIndex(op) {
1401
- const opMap = {
1402
- "==": "str_eq",
1403
- "!=": "str_ne",
1404
- "<": "str_lt",
1405
- "<=": "str_le",
1406
- ">": "str_gt",
1407
- ">=": "str_ge",
1408
- };
1409
- const fn = opMap[op] ?? "str_eq";
1410
- return this.getOrAddFuncImport(
1411
- "env",
1412
- fn,
1413
- ["externref", "externref"],
1414
- ["i32"],
1415
- );
1416
- }
1417
-
1418
- getStringGetImportIndex() {
1419
- return this.getOrAddFuncImport(
1420
- "env",
1421
- "str_get",
1422
- ["externref", "i32"],
1423
- ["i32"],
1424
- );
1425
- }
1426
-
1427
- getStringSliceImportIndex() {
1428
- return this.getOrAddFuncImport(
1429
- "env",
1430
- "str_slice",
1431
- ["externref", "i32", "i32"],
1432
- ["externref"],
1433
- );
1434
- }
1435
-
1436
- getStringFromCodePointImportIndex() {
1437
- return this.getOrAddFuncImport(
1438
- "env",
1439
- "str_from_code_point",
1440
- ["i32"],
1441
- ["externref"],
1442
- );
1443
- }
1444
-
1445
- getStringCodePointAtImportIndex() {
1446
- return this.getOrAddFuncImport(
1447
- "env",
1448
- "str_code_point_at",
1449
- ["externref", "i32"],
1450
- ["i32"],
1451
- );
1452
- }
1453
-
1454
- getIsStringImportIndex() {
1455
- return this.getOrAddFuncImport("env", "is_string", ["anyref"], ["i32"]);
1456
- }
1457
-
1458
- getStringHashImportIndex() {
1459
- return this.getOrAddFuncImport("env", "str_hash", ["externref"], ["i32"]);
1460
- }
1461
-
1462
- getStringToUpperImportIndex() {
1463
- return this.getOrAddFuncImport(
1464
- "env",
1465
- "str_to_upper",
1466
- ["externref"],
1467
- ["externref"],
1468
- );
1469
- }
1470
-
1471
- getStringToLowerImportIndex() {
1472
- return this.getOrAddFuncImport(
1473
- "env",
1474
- "str_to_lower",
1475
- ["externref"],
1476
- ["externref"],
1477
- );
1478
- }
1479
-
1480
- getStringTrimSpaceImportIndex() {
1481
- return this.getOrAddFuncImport(
1482
- "env",
1483
- "str_trim_space",
1484
- ["externref"],
1485
- ["externref"],
1486
- );
1487
- }
1488
-
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
- }
28
+ _scanFuncSigInterfaces,
29
+ _scanImportsInBody,
30
+ _scanInterfaceCallNodes,
31
+ _scanInterfaceTypeDecls,
32
+ _usesAnyEq,
33
+ _usesFmtAny,
34
+ _usesTestingCleanup,
35
+ _usesTestingRun,
36
+ importsAnyOf,
37
+ } from "./prescan.js";
38
+ import {
39
+ ERROR_STRUCT,
40
+ getMapKeyValTypes,
41
+ isInterfaceType,
42
+ isMapType,
43
+ toWasmType,
44
+ } from "./types.js";
45
+ import { desugarVariadics } from "./variadic.js";
46
+ import { emitWat } from "./wat.js";
1642
47
 
1643
- getTimezoneOffsetImportIndex() {
1644
- return this.getStdlibImportIndex("time_timezone_offset");
1645
- }
48
+ export { hasNativeWasmOpt, isGoFrontWasm, optimizeWasm };
1646
49
 
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: [],
50
+ // Body of `__index_panic(i i64, n i32)`: builds Go's message with the string
51
+ // imports and raises it like any other runtime panic.
52
+ function emitIndexPanicBody(mod, emitter) {
53
+ const str = (text) => {
54
+ emitter.pushInstruction({ op: "i32.const", value: mod.internString(text) });
55
+ emitter.pushInstruction({
56
+ op: "call",
57
+ funcIndex: mod.getStringImportIndex(),
1653
58
  });
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) {
1673
- return this.constCache.get(name) ?? null;
1674
- }
1675
-
1676
- isNonLiteralConst(name) {
1677
- return this.nonLiteralConsts.has(name);
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
- }
59
+ };
60
+ const concat = () =>
61
+ emitter.pushInstruction({
62
+ op: "call",
63
+ funcIndex: mod.getStringConcatImportIndex(),
64
+ });
65
+ str("runtime error: index out of range [");
66
+ emitter.pushInstruction({ op: "local.get", index: 0 });
67
+ emitter.pushInstruction({
68
+ op: "call",
69
+ funcIndex: mod.getStrFromI64ImportIndex(),
70
+ });
71
+ concat();
72
+ emitter.pushInstruction({ op: "local.get", index: 0 });
73
+ emitter.pushInstruction({ op: "i64.const", value: 0n });
74
+ emitter.pushInstruction("i64.lt_s");
75
+ emitter.pushInstruction({ op: "if", blockType: "externref" });
76
+ str("]");
77
+ emitter.pushInstruction("else");
78
+ str("] with length ");
79
+ emitter.pushInstruction({ op: "local.get", index: 1 });
80
+ emitter.pushInstruction({
81
+ op: "call",
82
+ funcIndex: mod.getStrFromI32ImportIndex(),
83
+ });
84
+ concat();
85
+ emitter.pushInstruction("end");
86
+ concat();
87
+ emitter.pushInstruction("any.convert_extern");
88
+ emitter.emitPanicThrow();
1890
89
  }
1891
90
 
1892
91
  function peepholeOptimize(instructions) {
@@ -1963,10 +162,17 @@ export function compileWasmModule(
1963
162
  const mono = monomorphise(progs, checker);
1964
163
  progs = mono.programs;
1965
164
  resolvedChecker = mono.checker;
1966
- resolvedLowerResult = lower(progs, resolvedChecker);
1967
- } else if (!resolvedLowerResult) {
165
+ resolvedLowerResult = null;
166
+ }
167
+ // Variadic parameters become slices (after monomorphisation: concrete types).
168
+ // Method expressions become method calls or forwarding func literals,
169
+ // before variadics so a variadic method's forwarder packs its arguments.
170
+ const methodExprs = desugarMethodExprs(progs, options.bundledPackages);
171
+ if (desugarVariadics(progs) || methodExprs || !resolvedLowerResult) {
1968
172
  resolvedLowerResult = lower(progs, resolvedChecker);
1969
173
  }
174
+ checker = resolvedChecker;
175
+ lowerResult = resolvedLowerResult;
1970
176
 
1971
177
  const mod = new ModuleEmitter(resolvedChecker, resolvedLowerResult, progs, {
1972
178
  bundledPackages: options.bundledPackages,
@@ -2029,6 +235,13 @@ export function compileWasmModule(
2029
235
  for (const item of node) scanClosures(item, rootFn);
2030
236
  return;
2031
237
  }
238
+ if (isIteratorRangeFor(node)) {
239
+ const yieldFuncLit = getOrCreateIteratorFuncLit(node);
240
+ scanClosures(yieldFuncLit, rootFn);
241
+ // A func literal ranged over directly is a closure of its own.
242
+ scanClosures(node.cond?.expr ?? node.init?.rhs?.[0]?.expr, rootFn);
243
+ return;
244
+ }
2032
245
  if (node.kind === "FuncLit") {
2033
246
  closureCounter++;
2034
247
  const closureName = `_closure$${closureCounter}`;
@@ -2045,6 +258,8 @@ export function compileWasmModule(
2045
258
  returnType: node.returnType,
2046
259
  body: node.body,
2047
260
  _isClosure: true,
261
+ _isYieldClosure: Boolean(node._isYieldClosure),
262
+ _iterStateName: node._iterStateName ?? null,
2048
263
  _funcLit: node,
2049
264
  _rootFuncDecl: rootFn,
2050
265
  _srcFile: rootFn?._srcFile ?? null,
@@ -2148,7 +363,15 @@ export function compileWasmModule(
2148
363
  pendingInits.length > 0 || initNames.length > 0
2149
364
  ? _makeGlobalInitFunc(pendingInits, initNames)
2150
365
  : null;
2151
- if (globalInitFn) funcDecls.push(globalInitFn);
366
+ if (globalInitFn) {
367
+ // Func literals in package-level initializers are lifted like any other.
368
+ globalInitFn._rootFuncDecl = globalInitFn;
369
+ // Captured variables of those literals need the same analysis.
370
+ lowerFunction(lowerResult, globalInitFn);
371
+ const before = closures.length;
372
+ scanClosures(globalInitFn.body, globalInitFn);
373
+ funcDecls.push(globalInitFn, ...closures.slice(before));
374
+ }
2152
375
 
2153
376
  const boundsPanicFn = {
2154
377
  kind: "FuncDecl",
@@ -2160,6 +383,30 @@ export function compileWasmModule(
2160
383
  };
2161
384
  funcDecls.push(boundsPanicFn);
2162
385
 
386
+ // Go's index panic: `index out of range [i] with length n`, or
387
+ // `index out of range [i]` for a negative index.
388
+ funcDecls.push({
389
+ kind: "FuncDecl",
390
+ name: "__index_panic",
391
+ params: [
392
+ { name: "i", type: { kind: "basic", name: "int64" } },
393
+ { name: "n", type: { kind: "basic", name: "int32" } },
394
+ ],
395
+ returnType: null,
396
+ _isIndexPanic: true,
397
+ });
398
+
399
+ // Target of the per-function `nil` block (`_openNilBlock` in emit.js).
400
+ funcDecls.push({
401
+ kind: "FuncDecl",
402
+ name: "__nil_panic",
403
+ params: [],
404
+ returnType: null,
405
+ _isRuntimePanic: true,
406
+ _panicMsg:
407
+ "runtime error: invalid memory address or nil pointer dereference",
408
+ });
409
+
2163
410
  const sliceBoundsPanicFn = {
2164
411
  kind: "FuncDecl",
2165
412
  name: "__slice_bounds_panic",
@@ -2405,6 +652,7 @@ export function compileWasmModule(
2405
652
 
2406
653
  // 1e3. Runtime helpers: error values, interface equality, t.Run callback.
2407
654
  mod.usesTestingRun = funcDecls.some((fn) => _usesTestingRun(fn.body));
655
+ mod.usesTestingCleanup = funcDecls.some((fn) => _usesTestingCleanup(fn.body));
2408
656
  mod.usesAnyEq = _usesAnyEq(funcDecls, mod);
2409
657
  const ANY_T = { kind: "basic", name: "any" };
2410
658
  const STR_T = { kind: "basic", name: "string" };
@@ -2531,7 +779,25 @@ export function compileWasmModule(
2531
779
  ),
2532
780
  );
2533
781
  }
782
+ if (mod.usesTestingCleanup) {
783
+ funcDecls.push(
784
+ helper(
785
+ "__testing_cleanup_cb",
786
+ [{ name: "fn", type: ANY_T }],
787
+ null,
788
+ "testing_cleanup_cb",
789
+ { _forceExport: true },
790
+ ),
791
+ );
792
+ }
2534
793
  _registerIoSyntheticMethods(funcDecls, mod, STR_T, progs);
794
+ // `__any_fmt` turns an `any` operand of fmt into the host value; its body
795
+ // is emitted last-but-one so every type registered by user code is seen.
796
+ if (_usesFmtAny(funcDecls, mod)) {
797
+ funcDecls.push(
798
+ helper("__any_fmt", [{ name: "a", type: ANY_T }], STR_T, "any_fmt"),
799
+ );
800
+ }
2535
801
 
2536
802
  // 1f. Boundary metadata (exported surface of wasm packages)
2537
803
  const boundaryMeta = options.boundary
@@ -2626,7 +892,9 @@ export function compileWasmModule(
2626
892
  // 4. Emit function bodies
2627
893
  for (const fn of funcDecls) {
2628
894
  const emitter = new FunctionEmitter(mod, fn, fn._globalFuncIndex);
2629
- if (fn._isRuntimePanic) {
895
+ if (fn._isIndexPanic) {
896
+ emitIndexPanicBody(mod, emitter);
897
+ } else if (fn._isRuntimePanic) {
2630
898
  const strIdx = mod.internString(fn._panicMsg);
2631
899
  const funcIdx = mod.getStringImportIndex();
2632
900
  emitter.pushInstruction({ op: "i32.const", value: strIdx });
@@ -2743,619 +1011,6 @@ export function compileWasmModule(
2743
1011
  };
2744
1012
  }
2745
1013
 
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 = [];
2752
- for (const p of progs) {
2753
- for (const d of p.decls ?? []) {
2754
- if (d.kind !== "ConstDecl") continue;
2755
- for (const spec of d.decls ?? []) {
2756
- for (let i = 0; i < spec.names.length; 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
- });
2764
- }
2765
- }
2766
- }
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
- );
2852
- }
2853
-
2854
- function _scanImportsInBody(node, mod) {
2855
- if (!node || typeof node !== "object") return;
2856
-
2857
- if (node.kind === "CallExpr") {
2858
- const { func, args } = node;
2859
- if (
2860
- func.kind === "Ident" &&
2861
- (func.name === "print" || func.name === "println")
2862
- ) {
2863
- const isPrintln = func.name === "println";
2864
- if (args.length === 0 && isPrintln) {
2865
- mod.getPrintlnEmptyIndex();
2866
- }
2867
- for (let i = 0; i < args.length; i++) {
2868
- const arg = args[i];
2869
- const isLast = i === args.length - 1 && isPrintln;
2870
- const isBool =
2871
- arg._type?.name === "bool" ||
2872
- (arg.kind === "BasicLit" && arg.litKind === "BOOL");
2873
- const wType = toWasmType(arg._type, mod.checker);
2874
- mod.getLogImportIndex(wType, isLast, isBool);
2875
- }
2876
- } else if (
2877
- func.kind === "SelectorExpr" &&
2878
- func.expr?.name === "math" &&
2879
- func.field
2880
- ) {
2881
- const jsMathMap = {
2882
- Sin: "sin",
2883
- Cos: "cos",
2884
- Tan: "tan",
2885
- Asin: "asin",
2886
- Acos: "acos",
2887
- Atan: "atan",
2888
- Atan2: "atan2",
2889
- Pow: "pow",
2890
- Mod: "mod",
2891
- Hypot: "hypot",
2892
- Cbrt: "cbrt",
2893
- Exp: "exp",
2894
- Log: "log",
2895
- Log2: "log2",
2896
- Log10: "log10",
2897
- Round: "round",
2898
- Sinh: "sinh",
2899
- Cosh: "cosh",
2900
- Tanh: "tanh",
2901
- };
2902
- if (jsMathMap[func.field]) {
2903
- const isBinary = BINARY_MATH_IMPORTS.has(jsMathMap[func.field]);
2904
- mod.getMathImportIndex(jsMathMap[func.field], isBinary);
2905
- }
2906
- } else if (
2907
- func.kind === "SelectorExpr" &&
2908
- isTestingT(func.expr?._type) &&
2909
- func.field
2910
- ) {
2911
- mod.internString(func.field);
2912
- if (func.field === "Name") mod.getTestingNameImportIndex();
2913
- else if (func.field === "Failed" || func.field === "Skipped")
2914
- mod.getTestingFlagImportIndex();
2915
- else if (func.field === "Run") {
2916
- // closure type for func(*testing.T) must exist before bodies are emitted
2917
- mod.getTestingRunClosureType();
2918
- } else {
2919
- mod.getTestingCallImportIndex();
2920
- for (const arg of args) {
2921
- mod.getTestingArgImportIndex(
2922
- toWasmType(arg._type, mod.checker),
2923
- arg._type?.name === "bool" ||
2924
- (arg.kind === "BasicLit" && arg.litKind === "BOOL"),
2925
- );
2926
- }
2927
- }
2928
- }
2929
- }
2930
-
2931
- if (node.kind === "BasicLit" && node.litKind === "STRING") {
2932
- mod.getStringImportIndex();
2933
- const strVal = String(node.value ?? "");
2934
- mod.internString(strVal);
2935
- }
2936
-
2937
- // Recurse child properties
2938
- for (const key of Object.keys(node)) {
2939
- if (key.startsWith("_")) continue;
2940
- const child = node[key];
2941
- if (Array.isArray(child)) {
2942
- for (const item of child) _scanImportsInBody(item, mod);
2943
- } else if (child && typeof child === "object") {
2944
- _scanImportsInBody(child, mod);
2945
- }
2946
- }
2947
- }
2948
-
2949
- export function compileWasm(source, options = {}) {
2950
- const filename = options.filename ?? "main.go";
2951
- const tokens = new Lexer(source, filename).tokenize();
2952
- const ast = new Parser(tokens, filename, source).parse();
2953
-
2954
- const checker = new TypeChecker();
2955
- checker.target = "wasm";
2956
- checker.pkgName = ast.pkg?.name ?? "main";
2957
-
2958
- const errors = checker.check(ast);
2959
- if (errors.length > 0) {
2960
- return { wasm: null, errors };
2961
- }
2962
-
2963
- const lowerRes = lower([ast], checker);
2964
- const res = compileWasmModule([ast], checker, lowerRes, {
2965
- ...options,
2966
- exportAll: options.exportAll ?? true,
2967
- });
2968
-
2969
- if (checker.errors?.length > 0) {
2970
- return { wasm: null, errors: checker.errors };
2971
- }
2972
-
2973
- return { ...res, errors: [] };
2974
- }
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
-
3016
- function _collectPackageGlobals(progs, mod, checker) {
3017
- const pending = []; // non-constant initializers, run by the start function
3018
- const pendingConsts = []; // unfoldable consts; they run before any var
3019
- for (const p of progs) {
3020
- for (const d of p.decls ?? []) {
3021
- const isConst = d.kind === "ConstDecl";
3022
- if (d.kind !== "VarDecl" && !isConst) continue;
3023
- for (const spec of d.decls ?? d.specs ?? [d]) {
3024
- const names = spec.names ?? (spec.name ? [spec.name] : []);
3025
- const values = spec.value ?? (spec.init ? [spec.init] : []);
3026
- for (let i = 0; i < names.length; i++) {
3027
- const name = names[i];
3028
- // Foldable consts live in `constCache`, not in a global.
3029
- if (isConst && !mod.isNonLiteralConst(name)) continue;
3030
- const rawType = spec.type ?? values[i]?._type;
3031
- const wType = toWasmType(rawType, checker, mod);
3032
- const gIdx = mod.globals.length;
3033
- let initInsts = [{ op: `${wType}.const`, value: 0 }];
3034
- if (wType === "i64") initInsts = [{ op: "i64.const", value: 0n }];
3035
- else if (wType === "f32" || wType === "f64")
3036
- initInsts = [{ op: `${wType}.const`, value: 0.0 }];
3037
- else if (
3038
- wType === "externref" ||
3039
- wType === "anyref" ||
3040
- typeof wType === "object"
3041
- ) {
3042
- const sliceInfo =
3043
- typeof wType === "object" && typeof wType.typeIndex === "number"
3044
- ? mod.getSliceTypeByIndex(wType.typeIndex)
3045
- : null;
3046
- if (sliceInfo) {
3047
- initInsts = [
3048
- { op: "global.get", index: sliceInfo.emptyGlobalIndex },
3049
- ];
3050
- } else if (
3051
- typeof wType === "object" &&
3052
- typeof wType.typeIndex === "number" &&
3053
- rawType?.kind !== "pointer"
3054
- ) {
3055
- const structInfo = Array.from(mod.structTypes.values()).find(
3056
- (s) => s.typeIndex === wType.typeIndex,
3057
- );
3058
- if (structInfo) {
3059
- initInsts = _zeroStructConst(structInfo, mod);
3060
- } else {
3061
- initInsts = [
3062
- { op: "ref.null", heapType: wType.typeIndex ?? "any" },
3063
- ];
3064
- }
3065
- } else {
3066
- initInsts = [
3067
- {
3068
- op: "ref.null",
3069
- heapType:
3070
- typeof wType === "object"
3071
- ? (wType.heapType ?? wType.typeIndex ?? "any")
3072
- : wType.replace("ref", ""),
3073
- },
3074
- ];
3075
- }
3076
- }
3077
-
3078
- let litNode = values?.[i];
3079
- let sign = 1;
3080
- if (
3081
- litNode &&
3082
- litNode.kind === "UnaryExpr" &&
3083
- litNode.op === "-" &&
3084
- litNode.operand?.kind === "BasicLit"
3085
- ) {
3086
- litNode = litNode.operand;
3087
- sign = -1;
3088
- }
3089
-
3090
- let isConstInit = false;
3091
- if (litNode && litNode.kind === "BasicLit") {
3092
- if (litNode.litKind === "INT") {
3093
- isConstInit = true;
3094
- const s = String(litNode.value);
3095
- const clean = /^0[0-7]+$/.test(s) ? `0o${s.slice(1)}` : s;
3096
- initInsts = [
3097
- wType === "i64"
3098
- ? {
3099
- op: "i64.const",
3100
- value: BigInt(sign) * BigInt(clean),
3101
- }
3102
- : {
3103
- op: "i32.const",
3104
- value: (sign * Number(clean)) | 0,
3105
- },
3106
- ];
3107
- } else if (litNode.litKind === "FLOAT") {
3108
- isConstInit = true;
3109
- initInsts = [
3110
- {
3111
- op: `${wType}.const`,
3112
- value: sign * Number(litNode.value),
3113
- },
3114
- ];
3115
- } else if (litNode.litKind === "BOOL") {
3116
- isConstInit = true;
3117
- initInsts = [
3118
- {
3119
- op: "i32.const",
3120
- value: litNode.value === "true" ? 1 : 0,
3121
- },
3122
- ];
3123
- }
3124
- }
3125
- if (values[i] && !isConstInit && names.length === values.length) {
3126
- (isConst ? pendingConsts : pending).push({
3127
- name,
3128
- expr: values[i],
3129
- });
3130
- }
3131
- mod.globals.push({
3132
- type: wType,
3133
- mutable: true,
3134
- init: initInsts,
3135
- });
3136
- mod.globalCache.set(name, {
3137
- index: gIdx,
3138
- type: wType,
3139
- goType: rawType,
3140
- });
3141
- }
3142
- }
3143
- }
3144
- }
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;
3165
- }
3166
-
3167
- // Synthesises `__init_globals()` assigning every non-constant package-level
3168
- // initializer in declaration order and invoking package `init()` functions;
3169
- // registered as the module start function.
3170
- function _makeGlobalInitFunc(pending, initNames = []) {
3171
- const stmts = pending.map(({ name, expr }) => ({
3172
- kind: "AssignStmt",
3173
- lhs: [{ kind: "Ident", name, _type: expr._type }],
3174
- op: "=",
3175
- rhs: [expr],
3176
- }));
3177
- for (const initName of initNames) {
3178
- stmts.push({
3179
- kind: "ExprStmt",
3180
- expr: {
3181
- kind: "CallExpr",
3182
- func: { kind: "Ident", name: initName },
3183
- args: [],
3184
- },
3185
- });
3186
- }
3187
- stmts.push({ kind: "ReturnStmt", values: [] });
3188
- return {
3189
- kind: "FuncDecl",
3190
- name: "__init_globals",
3191
- params: [],
3192
- returnType: null,
3193
- body: { kind: "Block", stmts, list: stmts },
3194
- _isGlobalInit: true,
3195
- };
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
1014
  function _collectInterfaceDispatchers(progs, funcDecls, mod, checker) {
3360
1015
  const interfaceCalls = new Map();
3361
1016
  for (const fn of funcDecls) {
@@ -3686,6 +1341,56 @@ function _registerIoSyntheticMethods(funcDecls, mod, strType, programs) {
3686
1341
  ),
3687
1342
  );
3688
1343
  }
1344
+ const hasOs = importsAnyOf(progs, ["os"]);
1345
+ if (hasOs && mod.structTypes.has("os.File")) {
1346
+ const fPtr = {
1347
+ kind: "pointer",
1348
+ base: { kind: "named", name: "os.File" },
1349
+ };
1350
+ funcDecls.push(
1351
+ ioHelper(
1352
+ "os.File.Write",
1353
+ [
1354
+ { name: "f", type: fPtr },
1355
+ { name: "p", type: BYTE_SLICE_T },
1356
+ ],
1357
+ INT_ERR_RET,
1358
+ "os_file_write",
1359
+ { _isMethod: true, _recvTypeName: "os.File", _methodName: "Write" },
1360
+ ),
1361
+ ioHelper(
1362
+ "os.File.WriteString",
1363
+ [
1364
+ { name: "f", type: fPtr },
1365
+ { name: "s", type: strType },
1366
+ ],
1367
+ INT_ERR_RET,
1368
+ "os_file_write_string",
1369
+ {
1370
+ _isMethod: true,
1371
+ _recvTypeName: "os.File",
1372
+ _methodName: "WriteString",
1373
+ },
1374
+ ),
1375
+ ioHelper(
1376
+ "os.File.Read",
1377
+ [
1378
+ { name: "f", type: fPtr },
1379
+ { name: "p", type: BYTE_SLICE_T },
1380
+ ],
1381
+ INT_ERR_RET,
1382
+ "os_file_read",
1383
+ { _isMethod: true, _recvTypeName: "os.File", _methodName: "Read" },
1384
+ ),
1385
+ ioHelper(
1386
+ "os.File.Close",
1387
+ [{ name: "f", type: fPtr }],
1388
+ ERR_T,
1389
+ "os_file_close",
1390
+ { _isMethod: true, _recvTypeName: "os.File", _methodName: "Close" },
1391
+ ),
1392
+ );
1393
+ }
3689
1394
  const hasSha256 = importsAnyOf(progs, ["crypto/sha256", "sha256"]);
3690
1395
  if (
3691
1396
  hasSha256 &&