gofront 1.7.0 → 1.9.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 (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
@@ -1,2113 +1,327 @@
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";
26
+ import { emitWasmIntrinsic, prepareWasmIntrinsic } from "./natives.js";
19
27
  import { hasNativeWasmOpt, optimizeWasm } from "./optimize.js";
20
- import { BINARY_MATH_IMPORTS } from "./stdlib/index.js";
21
28
  import {
22
- anonIfaceName,
29
+ _scanFuncSigInterfaces,
30
+ _scanImportsInBody,
31
+ _scanInterfaceCallNodes,
32
+ _scanInterfaceTypeDecls,
33
+ _someNode,
34
+ _usesAnyEq,
35
+ } from "./prescan.js";
36
+ import { pruneUnusedFuncs } from "./prune-funcs.js";
37
+ import {
23
38
  ERROR_STRUCT,
24
- getFuncSignature,
25
- getInterfaceMethodList,
26
39
  getMapKeyValTypes,
27
- getReceiverTypeName,
28
- goTypeKey,
29
40
  isInterfaceType,
30
41
  isMapType,
31
- isNonEmptyInterface,
32
- isTestingT,
33
42
  toWasmType,
34
43
  } from "./types.js";
44
+ import { desugarVariadics } from "./variadic.js";
35
45
  import { emitWat } from "./wat.js";
36
46
 
37
47
  export { hasNativeWasmOpt, isGoFrontWasm, optimizeWasm };
38
48
 
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
- );
49
+ // Body of `__index_panic(i i64, n i32)`: builds Go's message with the string
50
+ // imports and raises it like any other runtime panic.
51
+ function emitIndexPanicBody(mod, emitter) {
52
+ const str = (text) => {
53
+ emitter.pushInstruction({ op: "i32.const", value: mod.internString(text) });
54
+ emitter.pushInstruction({
55
+ op: "call",
56
+ funcIndex: mod.getStringImportIndex(),
57
+ });
58
+ };
59
+ const concat = () =>
60
+ emitter.pushInstruction({
61
+ op: "call",
62
+ funcIndex: mod.getStringConcatImportIndex(),
63
+ });
64
+ str("runtime error: index out of range [");
65
+ emitter.pushInstruction({ op: "local.get", index: 0 });
66
+ emitter.pushInstruction({
67
+ op: "call",
68
+ funcIndex: mod.getStrFromI64ImportIndex(),
69
+ });
70
+ concat();
71
+ emitter.pushInstruction({ op: "local.get", index: 0 });
72
+ emitter.pushInstruction({ op: "i64.const", value: 0n });
73
+ emitter.pushInstruction("i64.lt_s");
74
+ emitter.pushInstruction({ op: "if", blockType: "externref" });
75
+ str("]");
76
+ emitter.pushInstruction("else");
77
+ str("] with length ");
78
+ emitter.pushInstruction({ op: "local.get", index: 1 });
79
+ emitter.pushInstruction({
80
+ op: "call",
81
+ funcIndex: mod.getStrFromI32ImportIndex(),
82
+ });
83
+ concat();
84
+ emitter.pushInstruction("end");
85
+ concat();
86
+ emitter.emitRuntimePanicThrow();
98
87
  }
99
88
 
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
- }
89
+ function peepholeOptimize(instructions) {
90
+ let changed = true;
91
+ let current = instructions;
92
+ while (changed) {
93
+ changed = false;
94
+ const next = [];
95
+ for (let i = 0; i < current.length; i++) {
96
+ const c = current[i];
97
+ const n = current[i + 1];
98
+
99
+ // Pattern 1: local.get X; local.set X -> nothing
109
100
  if (
110
- (n.kind === "Ident" ||
111
- n.kind === "TypeName" ||
112
- n.kind === "basic" ||
113
- n.kind === "interface") &&
114
- n.name === "error"
101
+ c?.op === "local.get" &&
102
+ n?.op === "local.set" &&
103
+ c.index === n.index
115
104
  ) {
116
- found = true;
117
- return;
118
- }
119
- for (const k of Object.keys(n)) {
120
- if (!k.startsWith("_")) scan(n[k]);
105
+ changed = true;
106
+ i++; // skip both
107
+ continue;
121
108
  }
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
109
 
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
110
+ // Pattern 2: local.set X; local.get X -> local.tee X
111
+ if (
112
+ c?.op === "local.set" &&
113
+ n?.op === "local.get" &&
114
+ c.index === n.index
115
+ ) {
116
+ changed = true;
117
+ next.push({ op: "local.tee", index: c.index });
118
+ i++; // skip n (local.get)
119
+ continue;
120
+ }
301
121
 
302
- this.exports = []; // export entries
303
- this.stringTable = []; // string literals
304
- this.stringCache = new Map();
122
+ // Pattern 3: local.tee X; local.set X -> local.set X
123
+ if (
124
+ c?.op === "local.tee" &&
125
+ n?.op === "local.set" &&
126
+ c.index === n.index
127
+ ) {
128
+ changed = true;
129
+ next.push({ op: "local.set", index: c.index });
130
+ i++;
131
+ continue;
132
+ }
305
133
 
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);
134
+ // Pattern 4: local.tee X; drop -> local.set X
135
+ if (c?.op === "local.tee" && (n === "drop" || n?.op === "drop")) {
136
+ changed = true;
137
+ next.push({ op: "local.set", index: c.index });
138
+ i++;
139
+ continue;
140
+ }
312
141
 
313
- if (programs && programs.length > 0) {
314
- this.initTypes(programs);
142
+ next.push(c);
315
143
  }
316
-
317
- this._importsLocked = false;
318
- this._initRuntimeImports();
144
+ current = next;
319
145
  }
146
+ return current;
147
+ }
320
148
 
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
149
+ export function compileWasmModule(
150
+ programs,
151
+ checker,
152
+ lowerResult = null,
153
+ options = {},
154
+ ) {
155
+ let progs = Array.isArray(programs) ? programs : [programs];
156
+ let resolvedChecker = checker;
157
+ let resolvedLowerResult = lowerResult;
326
158
 
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
- }
159
+ if (hasGenerics(progs)) {
160
+ const mono = monomorphise(progs, checker);
161
+ progs = mono.programs;
162
+ resolvedChecker = mono.checker;
163
+ resolvedLowerResult = null;
164
+ }
165
+ // Variadic parameters become slices (after monomorphisation: concrete types).
166
+ // Method expressions become method calls or forwarding func literals,
167
+ // before variadics so a variadic method's forwarder packs its arguments.
168
+ const methodExprs = desugarMethodExprs(progs, options.bundledPackages);
169
+ if (desugarVariadics(progs) || methodExprs || !resolvedLowerResult) {
170
+ resolvedLowerResult = lower(progs, resolvedChecker);
171
+ }
172
+ checker = resolvedChecker;
173
+ lowerResult = resolvedLowerResult;
340
174
 
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
- }
175
+ const mod = new ModuleEmitter(resolvedChecker, resolvedLowerResult, progs, {
176
+ bundledPackages: options.bundledPackages,
177
+ natives: options.natives,
178
+ });
356
179
 
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]);
180
+ // 1. Collect all functions, methods, and package-level globals
181
+ const funcDecls = [];
182
+ let initCount = 0;
183
+ const initNames = [];
184
+ for (const p of progs) {
185
+ const pkgName = p.pkg?.name ?? "main";
186
+ const pkgTarget = p.target ?? "wasm";
187
+ for (const d of p.decls ?? []) {
188
+ if (d.kind === "FuncDecl") {
189
+ d._pkgName = pkgName;
190
+ d._pkgTarget = pkgTarget;
191
+ d._srcFile = p._path ?? p._filename ?? null;
192
+ if (d.name === "init" || d.name.startsWith("init$")) {
193
+ const renamed = initCount === 0 ? "init" : `init$${initCount}`;
194
+ d.name = renamed;
195
+ initNames.push(renamed);
196
+ initCount++;
366
197
  }
198
+ funcDecls.push(d);
199
+ } else if (d.kind === "MethodDecl") {
200
+ const recvTypeName =
201
+ d.recvType?.name ??
202
+ (d.recvType?.kind === "TypeName" ? d.recvType.name : "Recv");
203
+ const recvGoType = d.recvPointer
204
+ ? { kind: "pointer", base: { kind: "named", name: recvTypeName } }
205
+ : { kind: "named", name: recvTypeName };
206
+ const normFn = {
207
+ kind: "FuncDecl",
208
+ name: `${recvTypeName}.${d.name}`,
209
+ params: [{ name: d.recvName, type: recvGoType }, ...(d.params ?? [])],
210
+ returnType: d.returnType,
211
+ body: d.body,
212
+ _isMethod: true,
213
+ _recvTypeName: recvTypeName,
214
+ _methodName: d.name,
215
+ _exportName: `${recvTypeName}_${d.name}`,
216
+ _sourceDecl: d,
217
+ _pkgName: pkgName,
218
+ _pkgTarget: pkgTarget,
219
+ _srcFile: p._path ?? p._filename ?? null,
220
+ _line: d._line,
221
+ };
222
+ funcDecls.push(normFn);
367
223
  }
368
224
  }
225
+ }
369
226
 
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;
227
+ // 1b. Scan and lift all closures (FuncLits)
228
+ let closureCounter = 0;
229
+ const closures = [];
382
230
 
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
- });
231
+ function scanClosures(node, rootFn) {
232
+ if (!node || typeof node !== "object") return;
233
+ if (Array.isArray(node)) {
234
+ for (const item of node) scanClosures(item, rootFn);
235
+ return;
394
236
  }
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));
237
+ if (isIteratorRangeFor(node)) {
238
+ const yieldFuncLit = getOrCreateIteratorFuncLit(node);
239
+ scanClosures(yieldFuncLit, rootFn);
240
+ // A func literal ranged over directly is a closure of its own.
241
+ scanClosures(node.cond?.expr ?? node.init?.rhs?.[0]?.expr, rootFn);
242
+ return;
399
243
  }
244
+ if (node.kind === "FuncLit") {
245
+ closureCounter++;
246
+ const closureName = `_closure$${closureCounter}`;
247
+ node._liftedName = closureName;
248
+ node._rootFuncDecl = rootFn;
400
249
 
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;
250
+ const liftedFn = {
251
+ kind: "FuncDecl",
252
+ name: closureName,
253
+ params: [
254
+ { name: "__env", type: { kind: "basic", name: "any" } },
255
+ ...(node.params ?? []),
256
+ ],
257
+ returnType: node.returnType,
258
+ body: node.body,
259
+ _isClosure: true,
260
+ _isYieldClosure: Boolean(node._isYieldClosure),
261
+ _iterStateName: node._iterStateName ?? null,
262
+ _funcLit: node,
263
+ _rootFuncDecl: rootFn,
264
+ _srcFile: rootFn?._srcFile ?? null,
265
+ _line: typeof node._line === "number" ? node._line : rootFn?._line,
266
+ };
267
+ node._liftedFn = liftedFn;
268
+ closures.push(liftedFn);
463
269
 
464
- const typeEntry = structEntries[i];
465
- typeEntry.fields = fields.map((f) => ({ type: f.wType, mutable: true }));
466
- info.typeEntry = typeEntry;
270
+ scanClosures(node.body, rootFn);
271
+ return;
467
272
  }
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);
273
+ for (const key of Object.keys(node)) {
274
+ if (key.startsWith("_")) continue;
275
+ scanClosures(node[key], rootFn);
485
276
  }
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
277
  }
514
278
 
515
- getBoxType(goType) {
516
- const key = goTypeKey(goType);
517
- if (this.boxTypes.has(key)) {
518
- return this.boxTypes.get(key);
279
+ for (const fn of funcDecls) {
280
+ fn._rootFuncDecl = fn;
281
+ if (fn.body) {
282
+ scanClosures(fn.body, fn);
519
283
  }
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
284
  }
285
+ funcDecls.push(...closures);
531
286
 
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 });
287
+ // 1c. Add trampolines for named functions (for when they are passed as function values)
288
+ const topLevelFuncs = new Map();
289
+ for (const fn of funcDecls) {
290
+ if (
291
+ !fn._isClosure &&
292
+ !fn._isMethod &&
293
+ fn.name !== "main" &&
294
+ !fn.name.startsWith("_")
295
+ ) {
296
+ topLevelFuncs.set(fn.name, fn);
543
297
  }
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
298
  }
299
+ mod.topLevelFuncMap = topLevelFuncs;
568
300
 
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,
301
+ const trampolines = [];
302
+ for (const [name, targetFn] of topLevelFuncs.entries()) {
303
+ const trampName = `_tramp$${name}`;
304
+ const callExpr = {
305
+ kind: "CallExpr",
306
+ func: { kind: "Ident", name: targetFn.name },
307
+ args: (targetFn.params ?? []).map((p) => ({
308
+ kind: "Ident",
309
+ name: p.name,
310
+ _type: p.type,
311
+ })),
312
+ _type: targetFn.returnType ?? { kind: "basic", name: "void" },
581
313
  };
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,
314
+ const bodyStmts = targetFn.returnType
315
+ ? [
316
+ {
317
+ kind: "ReturnStmt",
318
+ values: [callExpr],
605
319
  },
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
- }
1642
-
1643
- getTimezoneOffsetImportIndex() {
1644
- return this.getStdlibImportIndex("time_timezone_offset");
1645
- }
1646
-
1647
- // Closure type of `func(*testing.T)` (the t.Run body).
1648
- getTestingRunClosureType() {
1649
- return this.getClosureType({
1650
- kind: "func",
1651
- params: [{ kind: "pointer", base: { kind: "named", name: "testing.T" } }],
1652
- returns: [],
1653
- });
1654
- }
1655
-
1656
- resolveFuncIndex(name) {
1657
- return this.funcMap.get(name) ?? null;
1658
- }
1659
-
1660
- getFuncParamTypes(name) {
1661
- return this.funcParamTypes.get(name) ?? [];
1662
- }
1663
-
1664
- getFuncParamGoTypes(name) {
1665
- return this.funcParamGoTypes.get(name) ?? [];
1666
- }
1667
-
1668
- resolveGlobal(name) {
1669
- return this.globalCache.get(name) ?? null;
1670
- }
1671
-
1672
- resolveConst(name) {
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
- }
1890
- }
1891
-
1892
- function peepholeOptimize(instructions) {
1893
- let changed = true;
1894
- let current = instructions;
1895
- while (changed) {
1896
- changed = false;
1897
- const next = [];
1898
- for (let i = 0; i < current.length; i++) {
1899
- const c = current[i];
1900
- const n = current[i + 1];
1901
-
1902
- // Pattern 1: local.get X; local.set X -> nothing
1903
- if (
1904
- c?.op === "local.get" &&
1905
- n?.op === "local.set" &&
1906
- c.index === n.index
1907
- ) {
1908
- changed = true;
1909
- i++; // skip both
1910
- continue;
1911
- }
1912
-
1913
- // Pattern 2: local.set X; local.get X -> local.tee X
1914
- if (
1915
- c?.op === "local.set" &&
1916
- n?.op === "local.get" &&
1917
- c.index === n.index
1918
- ) {
1919
- changed = true;
1920
- next.push({ op: "local.tee", index: c.index });
1921
- i++; // skip n (local.get)
1922
- continue;
1923
- }
1924
-
1925
- // Pattern 3: local.tee X; local.set X -> local.set X
1926
- if (
1927
- c?.op === "local.tee" &&
1928
- n?.op === "local.set" &&
1929
- c.index === n.index
1930
- ) {
1931
- changed = true;
1932
- next.push({ op: "local.set", index: c.index });
1933
- i++;
1934
- continue;
1935
- }
1936
-
1937
- // Pattern 4: local.tee X; drop -> local.set X
1938
- if (c?.op === "local.tee" && (n === "drop" || n?.op === "drop")) {
1939
- changed = true;
1940
- next.push({ op: "local.set", index: c.index });
1941
- i++;
1942
- continue;
1943
- }
1944
-
1945
- next.push(c);
1946
- }
1947
- current = next;
1948
- }
1949
- return current;
1950
- }
1951
-
1952
- export function compileWasmModule(
1953
- programs,
1954
- checker,
1955
- lowerResult = null,
1956
- options = {},
1957
- ) {
1958
- let progs = Array.isArray(programs) ? programs : [programs];
1959
- let resolvedChecker = checker;
1960
- let resolvedLowerResult = lowerResult;
1961
-
1962
- if (hasGenerics(progs)) {
1963
- const mono = monomorphise(progs, checker);
1964
- progs = mono.programs;
1965
- resolvedChecker = mono.checker;
1966
- resolvedLowerResult = lower(progs, resolvedChecker);
1967
- } else if (!resolvedLowerResult) {
1968
- resolvedLowerResult = lower(progs, resolvedChecker);
1969
- }
1970
-
1971
- const mod = new ModuleEmitter(resolvedChecker, resolvedLowerResult, progs, {
1972
- bundledPackages: options.bundledPackages,
1973
- });
1974
-
1975
- // 1. Collect all functions, methods, and package-level globals
1976
- const funcDecls = [];
1977
- let initCount = 0;
1978
- const initNames = [];
1979
- for (const p of progs) {
1980
- const pkgName = p.pkg?.name ?? "main";
1981
- const pkgTarget = p.target ?? "wasm";
1982
- for (const d of p.decls ?? []) {
1983
- if (d.kind === "FuncDecl") {
1984
- d._pkgName = pkgName;
1985
- d._pkgTarget = pkgTarget;
1986
- d._srcFile = p._path ?? p._filename ?? null;
1987
- if (d.name === "init" || d.name.startsWith("init$")) {
1988
- const renamed = initCount === 0 ? "init" : `init$${initCount}`;
1989
- d.name = renamed;
1990
- initNames.push(renamed);
1991
- initCount++;
1992
- }
1993
- funcDecls.push(d);
1994
- } else if (d.kind === "MethodDecl") {
1995
- const recvTypeName =
1996
- d.recvType?.name ??
1997
- (d.recvType?.kind === "TypeName" ? d.recvType.name : "Recv");
1998
- const recvGoType = d.recvPointer
1999
- ? { kind: "pointer", base: { kind: "named", name: recvTypeName } }
2000
- : { kind: "named", name: recvTypeName };
2001
- const normFn = {
2002
- kind: "FuncDecl",
2003
- name: `${recvTypeName}.${d.name}`,
2004
- params: [{ name: d.recvName, type: recvGoType }, ...(d.params ?? [])],
2005
- returnType: d.returnType,
2006
- body: d.body,
2007
- _isMethod: true,
2008
- _recvTypeName: recvTypeName,
2009
- _methodName: d.name,
2010
- _exportName: `${recvTypeName}_${d.name}`,
2011
- _sourceDecl: d,
2012
- _pkgName: pkgName,
2013
- _pkgTarget: pkgTarget,
2014
- _srcFile: p._path ?? p._filename ?? null,
2015
- _line: d._line,
2016
- };
2017
- funcDecls.push(normFn);
2018
- }
2019
- }
2020
- }
2021
-
2022
- // 1b. Scan and lift all closures (FuncLits)
2023
- let closureCounter = 0;
2024
- const closures = [];
2025
-
2026
- function scanClosures(node, rootFn) {
2027
- if (!node || typeof node !== "object") return;
2028
- if (Array.isArray(node)) {
2029
- for (const item of node) scanClosures(item, rootFn);
2030
- return;
2031
- }
2032
- if (node.kind === "FuncLit") {
2033
- closureCounter++;
2034
- const closureName = `_closure$${closureCounter}`;
2035
- node._liftedName = closureName;
2036
- node._rootFuncDecl = rootFn;
2037
-
2038
- const liftedFn = {
2039
- kind: "FuncDecl",
2040
- name: closureName,
2041
- params: [
2042
- { name: "__env", type: { kind: "basic", name: "any" } },
2043
- ...(node.params ?? []),
2044
- ],
2045
- returnType: node.returnType,
2046
- body: node.body,
2047
- _isClosure: true,
2048
- _funcLit: node,
2049
- _rootFuncDecl: rootFn,
2050
- _srcFile: rootFn?._srcFile ?? null,
2051
- _line: typeof node._line === "number" ? node._line : rootFn?._line,
2052
- };
2053
- node._liftedFn = liftedFn;
2054
- closures.push(liftedFn);
2055
-
2056
- scanClosures(node.body, rootFn);
2057
- return;
2058
- }
2059
- for (const key of Object.keys(node)) {
2060
- if (key.startsWith("_")) continue;
2061
- scanClosures(node[key], rootFn);
2062
- }
2063
- }
2064
-
2065
- for (const fn of funcDecls) {
2066
- fn._rootFuncDecl = fn;
2067
- if (fn.body) {
2068
- scanClosures(fn.body, fn);
2069
- }
2070
- }
2071
- funcDecls.push(...closures);
2072
-
2073
- // 1c. Add trampolines for named functions (for when they are passed as function values)
2074
- const topLevelFuncs = new Map();
2075
- for (const fn of funcDecls) {
2076
- if (
2077
- !fn._isClosure &&
2078
- !fn._isMethod &&
2079
- fn.name !== "main" &&
2080
- !fn.name.startsWith("_")
2081
- ) {
2082
- topLevelFuncs.set(fn.name, fn);
2083
- }
2084
- }
2085
- mod.topLevelFuncMap = topLevelFuncs;
2086
-
2087
- const trampolines = [];
2088
- for (const [name, targetFn] of topLevelFuncs.entries()) {
2089
- const trampName = `_tramp$${name}`;
2090
- const callExpr = {
2091
- kind: "CallExpr",
2092
- func: { kind: "Ident", name: targetFn.name },
2093
- args: (targetFn.params ?? []).map((p) => ({
2094
- kind: "Ident",
2095
- name: p.name,
2096
- _type: p.type,
2097
- })),
2098
- _type: targetFn.returnType ?? { kind: "basic", name: "void" },
2099
- };
2100
- const bodyStmts = targetFn.returnType
2101
- ? [
2102
- {
2103
- kind: "ReturnStmt",
2104
- values: [callExpr],
2105
- },
2106
- ]
2107
- : [
2108
- { kind: "ExprStmt", expr: callExpr },
2109
- { kind: "ReturnStmt", values: [] },
2110
- ];
320
+ ]
321
+ : [
322
+ { kind: "ExprStmt", expr: callExpr },
323
+ { kind: "ReturnStmt", values: [] },
324
+ ];
2111
325
 
2112
326
  const trampFn = {
2113
327
  kind: "FuncDecl",
@@ -2148,7 +362,15 @@ export function compileWasmModule(
2148
362
  pendingInits.length > 0 || initNames.length > 0
2149
363
  ? _makeGlobalInitFunc(pendingInits, initNames)
2150
364
  : null;
2151
- if (globalInitFn) funcDecls.push(globalInitFn);
365
+ if (globalInitFn) {
366
+ // Func literals in package-level initializers are lifted like any other.
367
+ globalInitFn._rootFuncDecl = globalInitFn;
368
+ // Captured variables of those literals need the same analysis.
369
+ lowerFunction(lowerResult, globalInitFn);
370
+ const before = closures.length;
371
+ scanClosures(globalInitFn.body, globalInitFn);
372
+ funcDecls.push(globalInitFn, ...closures.slice(before));
373
+ }
2152
374
 
2153
375
  const boundsPanicFn = {
2154
376
  kind: "FuncDecl",
@@ -2160,6 +382,30 @@ export function compileWasmModule(
2160
382
  };
2161
383
  funcDecls.push(boundsPanicFn);
2162
384
 
385
+ // Go's index panic: `index out of range [i] with length n`, or
386
+ // `index out of range [i]` for a negative index.
387
+ funcDecls.push({
388
+ kind: "FuncDecl",
389
+ name: "__index_panic",
390
+ params: [
391
+ { name: "i", type: { kind: "basic", name: "int64" } },
392
+ { name: "n", type: { kind: "basic", name: "int32" } },
393
+ ],
394
+ returnType: null,
395
+ _isIndexPanic: true,
396
+ });
397
+
398
+ // Target of the per-function `nil` block (`_openNilBlock` in emit.js).
399
+ funcDecls.push({
400
+ kind: "FuncDecl",
401
+ name: "__nil_panic",
402
+ params: [],
403
+ returnType: null,
404
+ _isRuntimePanic: true,
405
+ _panicMsg:
406
+ "runtime error: invalid memory address or nil pointer dereference",
407
+ });
408
+
2163
409
  const sliceBoundsPanicFn = {
2164
410
  kind: "FuncDecl",
2165
411
  name: "__slice_bounds_panic",
@@ -2344,18 +590,6 @@ export function compileWasmModule(
2344
590
  _mapInfo: mapInfo,
2345
591
  _rootFuncDecl: null,
2346
592
  });
2347
- // clone
2348
- funcDecls.push({
2349
- kind: "FuncDecl",
2350
- name: mapInfo.cloneFuncName,
2351
- params: [{ name: "m", type: mapInfo.wType }],
2352
- returnType: mapInfo.wType,
2353
- body: { kind: "Block", stmts: [], list: [] },
2354
- _isMapHelper: true,
2355
- _mapHelperKind: "clone",
2356
- _mapInfo: mapInfo,
2357
- _rootFuncDecl: null,
2358
- });
2359
593
  }
2360
594
 
2361
595
  // 1e2. `gofront/shared` linear-memory helpers (only when imported).
@@ -2403,8 +637,7 @@ export function compileWasmModule(
2403
637
  });
2404
638
  }
2405
639
 
2406
- // 1e3. Runtime helpers: error values, interface equality, t.Run callback.
2407
- mod.usesTestingRun = funcDecls.some((fn) => _usesTestingRun(fn.body));
640
+ // 1e3. Runtime helpers: error values and interface equality.
2408
641
  mod.usesAnyEq = _usesAnyEq(funcDecls, mod);
2409
642
  const ANY_T = { kind: "basic", name: "any" };
2410
643
  const STR_T = { kind: "basic", name: "string" };
@@ -2432,6 +665,25 @@ export function compileWasmModule(
2432
665
  ),
2433
666
  );
2434
667
  }
668
+ // `x.(T)` outside comma-ok form panics with the dynamic type's name.
669
+ mod.usesTypeAssertFail = funcDecls.some((fn) =>
670
+ _someNode(fn.body, (n) => n.kind === "TypeAssertExpr" && !n._commaOk),
671
+ );
672
+ if (mod.usesTypeAssertFail) {
673
+ funcDecls.push(
674
+ helper(
675
+ "__ta_fail",
676
+ [
677
+ { name: "v", type: ANY_T },
678
+ { name: "src", type: STR_T },
679
+ { name: "target", type: STR_T },
680
+ { name: "iface", type: BOOL_T },
681
+ ],
682
+ null,
683
+ "ta_fail",
684
+ ),
685
+ );
686
+ }
2435
687
  mod.usesAnyHash = false;
2436
688
  for (const info of mod.mapTypes.values()) {
2437
689
  if (isInterfaceType(info.keyGoType, checker)) {
@@ -2466,72 +718,10 @@ export function compileWasmModule(
2466
718
  _methodName: "Error",
2467
719
  },
2468
720
  ),
2469
- helper(
2470
- `${ERROR_STRUCT}.Unwrap`,
2471
- [{ name: "e", type: errPtr }],
2472
- ANY_T,
2473
- "error_cause",
2474
- {
2475
- _isMethod: true,
2476
- _recvTypeName: ERROR_STRUCT,
2477
- _methodName: "Unwrap",
2478
- },
2479
- ),
2480
- helper(
2481
- "__mk_error",
2482
- [
2483
- { name: "msg", type: STR_T },
2484
- { name: "cause", type: ANY_T },
2485
- ],
2486
- ANY_T,
2487
- "mk_error",
2488
- ),
721
+ helper("__mk_error", [{ name: "msg", type: STR_T }], ANY_T, "mk_error"),
2489
722
  helper("__error_str", [{ name: "e", type: ANY_T }], STR_T, "error_str"),
2490
- {
2491
- kind: "FuncDecl",
2492
- name: "__errors_unwrap",
2493
- params: [{ name: "__recv", type: ANY_T }],
2494
- returnType: ANY_T,
2495
- body: { kind: "Block", stmts: [], list: [] },
2496
- _isInterfaceDispatcher: true,
2497
- _softDispatch: true,
2498
- _ifaceName: "error",
2499
- _ifaceType: null,
2500
- _methodName: "Unwrap",
2501
- _rootFuncDecl: null,
2502
- },
2503
- helper(
2504
- "__errors_is",
2505
- [
2506
- { name: "err", type: ANY_T },
2507
- { name: "target", type: ANY_T },
2508
- ],
2509
- BOOL_T,
2510
- "errors_is",
2511
- ),
2512
- );
2513
- }
2514
- if (mod.usesTestingRun) {
2515
- funcDecls.push(
2516
- helper(
2517
- "__testing_run_cb",
2518
- [
2519
- { name: "fn", type: ANY_T },
2520
- {
2521
- name: "t",
2522
- type: {
2523
- kind: "pointer",
2524
- base: { kind: "named", name: "testing.T" },
2525
- },
2526
- },
2527
- ],
2528
- null,
2529
- "testing_run_cb",
2530
- { _forceExport: true },
2531
- ),
2532
723
  );
2533
724
  }
2534
- _registerIoSyntheticMethods(funcDecls, mod, STR_T, progs);
2535
725
 
2536
726
  // 1f. Boundary metadata (exported surface of wasm packages)
2537
727
  const boundaryMeta = options.boundary
@@ -2540,6 +730,7 @@ export function compileWasmModule(
2540
730
 
2541
731
  // 2. Pre-scan for needed imports so import func indices are fixed
2542
732
  for (const fn of funcDecls) {
733
+ if (fn.intrinsic) prepareWasmIntrinsic(mod, fn);
2543
734
  _scanImportsInBody(fn.body, mod);
2544
735
  }
2545
736
  if (boundaryMeta) scanBoundaryImports(mod, boundaryMeta);
@@ -2593,8 +784,11 @@ export function compileWasmModule(
2593
784
  fn._forceExport ||
2594
785
  (!fn._isGlobalInit &&
2595
786
  !fn._isRuntimeHelper &&
2596
- !fn._isIoHelper &&
2597
- !fn._isPhase7Helper &&
787
+ !fn._isMapHelper &&
788
+ !fn._isInterfaceDispatcher &&
789
+ !fn._isSharedHelper &&
790
+ !fn._isRuntimePanic &&
791
+ !fn._isIndexPanic &&
2598
792
  (options.exportAll ||
2599
793
  fn.name === "main" ||
2600
794
  (fn.name[0] >= "A" && fn.name[0] <= "Z") ||
@@ -2626,13 +820,14 @@ export function compileWasmModule(
2626
820
  // 4. Emit function bodies
2627
821
  for (const fn of funcDecls) {
2628
822
  const emitter = new FunctionEmitter(mod, fn, fn._globalFuncIndex);
2629
- if (fn._isRuntimePanic) {
823
+ if (fn._isIndexPanic) {
824
+ emitIndexPanicBody(mod, emitter);
825
+ } else if (fn._isRuntimePanic) {
2630
826
  const strIdx = mod.internString(fn._panicMsg);
2631
827
  const funcIdx = mod.getStringImportIndex();
2632
828
  emitter.pushInstruction({ op: "i32.const", value: strIdx });
2633
829
  emitter.pushInstruction({ op: "call", funcIndex: funcIdx });
2634
- emitter.pushInstruction("any.convert_extern");
2635
- emitter.emitPanicThrow();
830
+ emitter.emitRuntimePanicThrow();
2636
831
  } else if (fn._isPushPanic) {
2637
832
  const panicNodeTypeIndex = mod.getPanicNodeTypeIndex();
2638
833
  const panicGlobal = mod.getPanicGlobalIndex();
@@ -2651,709 +846,96 @@ export function compileWasmModule(
2651
846
  } else if (fn._isMapHelper) {
2652
847
  emitter.emitMapHelper(fn);
2653
848
  } else if (fn._isSharedHelper) {
2654
- emitter.emitSharedHelper(fn);
2655
- } else if (fn._isRuntimeHelper) {
2656
- emitter.emitRuntimeHelper(fn);
2657
- } else if (fn._isIoHelper || fn._isPhase7Helper) {
2658
- emitter.emitIoHelper(fn);
2659
- } else if (fn.body) {
2660
- emitter.emitFunctionBody(fn.body);
2661
- }
2662
- // If last instruction is not return or unreachable, auto-emit return or unreachable
2663
- let body = emitter.body;
2664
- if (
2665
- body.length === 0 ||
2666
- (body[body.length - 1].op !== "return" &&
2667
- body[body.length - 1].op !== "unreachable")
2668
- ) {
2669
- if (emitter.returnTypes.length === 0) {
2670
- body.push({ op: "return" });
2671
- } else {
2672
- body.push({ op: "unreachable" });
2673
- }
2674
- }
2675
-
2676
- body = peepholeOptimize(body);
2677
-
2678
- mod.funcs.push({
2679
- typeIndex: fn._typeIndex,
2680
- locals: emitter.localTypes,
2681
- body,
2682
- src:
2683
- fn._srcFile && typeof fn._line === "number"
2684
- ? { file: fn._srcFile, line: fn._line }
2685
- : null,
2686
- });
2687
- }
2688
-
2689
- // 5. Boundary marshalling helpers + JS facade
2690
- let facade = null;
2691
- if (boundaryMeta) {
2692
- addBoundaryHelpers(mod, boundaryMeta);
2693
- facade = generateFacade(boundaryMeta, {
2694
- stringTable: mod.stringTable,
2695
- callMain: Boolean(options.callMain) && mod.funcMap.has("main"),
2696
- stdlibEnv: mod.usesStdlibEnv,
2697
- });
2698
- }
2699
-
2700
- const moduleIR = {
2701
- types: mod.types,
2702
- imports: mod.imports,
2703
- tags: mod.tags,
2704
- globals: mod.globals,
2705
- elements: mod.elements,
2706
- funcs: mod.funcs,
2707
- exports: mod.exports,
2708
- memory: mod.memory ?? null,
2709
- start: globalInitFn ? globalInitFn._globalFuncIndex : null,
2710
- sourceMappingURL: options.sourceMappingURL ?? null,
2711
- };
2712
-
2713
- const wasmBytes = encodeModule(moduleIR);
2714
- const watText = options.emitWat ? emitWat(moduleIR) : null;
2715
-
2716
- // Function-level source positions: `sources` are the absolute Go files,
2717
- // `mappings` pair each function body's byte offset with its 0-based line.
2718
- const sources = [];
2719
- const sourceIndex = new Map();
2720
- const mappings = [];
2721
- moduleIR.funcs.forEach((fn, i) => {
2722
- if (!fn.src) return;
2723
- let idx = sourceIndex.get(fn.src.file);
2724
- if (idx === undefined) {
2725
- idx = sources.length;
2726
- sources.push(fn.src.file);
2727
- sourceIndex.set(fn.src.file, idx);
2728
- }
2729
- mappings.push({
2730
- offset: moduleIR.codeOffsets[i],
2731
- srcFileIdx: idx,
2732
- srcLine: Math.max(0, fn.src.line - 1),
2733
- });
2734
- });
2735
-
2736
- return {
2737
- wasm: wasmBytes,
2738
- wat: watText,
2739
- stringTable: mod.stringTable,
2740
- moduleIR,
2741
- facade,
2742
- sourceMapInfo: { sources, mappings },
2743
- };
2744
- }
2745
-
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
- }
849
+ emitter.emitSharedHelper(fn);
850
+ } else if (fn._isRuntimeHelper) {
851
+ emitter.emitRuntimeHelper(fn);
852
+ } else if (fn.intrinsic) {
853
+ emitWasmIntrinsic(emitter, fn);
854
+ } else if (fn.body) {
855
+ emitter.emitFunctionBody(fn.body);
856
+ }
857
+ // If last instruction is not return or unreachable, auto-emit return or unreachable
858
+ let body = emitter.body;
859
+ if (
860
+ body.length === 0 ||
861
+ (body[body.length - 1].op !== "return" &&
862
+ body[body.length - 1].op !== "unreachable")
863
+ ) {
864
+ if (emitter.returnTypes.length === 0) {
865
+ body.push({ op: "return" });
866
+ } else {
867
+ body.push({ op: "unreachable" });
3255
868
  }
3256
869
  }
3257
- }
3258
- }
3259
870
 
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
- }
871
+ body = peepholeOptimize(body);
3273
872
 
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
- }
873
+ mod.funcs.push({
874
+ typeIndex: fn._typeIndex,
875
+ locals: emitter.localTypes,
876
+ body,
877
+ src:
878
+ fn._srcFile && typeof fn._line === "number"
879
+ ? { file: fn._srcFile, line: fn._line }
880
+ : null,
881
+ });
3289
882
  }
3290
883
 
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;
884
+ // 5. Boundary marshalling helpers + JS facade
885
+ let facade = null;
886
+ if (boundaryMeta) {
887
+ addBoundaryHelpers(mod, boundaryMeta);
888
+ facade = generateFacade(boundaryMeta, {
889
+ stringTable: mod.stringTable,
890
+ callMain: Boolean(options.callMain) && mod.funcMap.has("main"),
891
+ });
3299
892
  }
3300
893
 
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,
894
+ const moduleIR = {
895
+ types: mod.types,
896
+ imports: mod.imports,
897
+ tags: mod.tags,
898
+ globals: mod.globals,
899
+ elements: mod.elements,
900
+ funcs: mod.funcs,
901
+ exports: mod.exports,
902
+ memory: mod.memory ?? null,
903
+ start: globalInitFn ? globalInitFn._globalFuncIndex : null,
904
+ sourceMappingURL: options.sourceMappingURL ?? null,
3312
905
  };
3313
- }
3314
906
 
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
- }
907
+ pruneUnusedFuncs(moduleIR);
908
+ const wasmBytes = encodeModule(moduleIR);
909
+ const watText = options.emitWat ? emitWat(moduleIR) : null;
910
+
911
+ // Function-level source positions: `sources` are the absolute Go files,
912
+ // `mappings` pair each function body's byte offset with its 0-based line.
913
+ const sources = [];
914
+ const sourceIndex = new Map();
915
+ const mappings = [];
916
+ moduleIR.funcs.forEach((fn, i) => {
917
+ if (!fn.src) return;
918
+ let idx = sourceIndex.get(fn.src.file);
919
+ if (idx === undefined) {
920
+ idx = sources.length;
921
+ sources.push(fn.src.file);
922
+ sourceIndex.set(fn.src.file, idx);
3343
923
  }
924
+ mappings.push({
925
+ offset: moduleIR.codeOffsets[i],
926
+ srcFileIdx: idx,
927
+ srcLine: Math.max(0, fn.src.line - 1),
928
+ });
929
+ });
930
+
931
+ return {
932
+ wasm: wasmBytes,
933
+ wat: watText,
934
+ stringTable: mod.stringTable,
935
+ moduleIR,
936
+ facade,
937
+ sourceMapInfo: { sources, mappings },
3344
938
  };
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
939
  }
3358
940
 
3359
941
  function _collectInterfaceDispatchers(progs, funcDecls, mod, checker) {
@@ -3382,378 +964,7 @@ function _collectInterfaceDispatchers(progs, funcDecls, mod, checker) {
3382
964
  });
3383
965
  }
3384
966
 
3385
- const hasIo = importsAnyOf(progs, [
3386
- "io",
3387
- "fmt",
3388
- "strings",
3389
- "bytes",
3390
- "crypto/sha256",
3391
- "sha256",
3392
- "encoding/binary",
3393
- "binary",
3394
- "encoding/hex",
3395
- "hex",
3396
- "encoding/base64",
3397
- "base64",
3398
- ]);
3399
- if (hasIo) {
3400
- const byteSliceType = {
3401
- kind: "slice",
3402
- elem: { kind: "basic", name: "byte" },
3403
- };
3404
- const intErrReturns = [
3405
- { kind: "basic", name: "int" },
3406
- { kind: "interface", name: "error" },
3407
- ];
3408
- if (!interfaceCalls.has("io.Reader.Read")) {
3409
- interfaceCalls.set("io.Reader.Read", {
3410
- ifaceName: "io.Reader",
3411
- ifaceType: checker?.types?.get("io.Reader"),
3412
- methodName: "Read",
3413
- methodSig: {
3414
- kind: "func",
3415
- params: [byteSliceType],
3416
- returns: intErrReturns,
3417
- },
3418
- });
3419
- }
3420
- if (!interfaceCalls.has("io.Writer.Write")) {
3421
- interfaceCalls.set("io.Writer.Write", {
3422
- ifaceName: "io.Writer",
3423
- ifaceType: checker?.types?.get("io.Writer"),
3424
- methodName: "Write",
3425
- methodSig: {
3426
- kind: "func",
3427
- params: [byteSliceType],
3428
- returns: intErrReturns,
3429
- },
3430
- });
3431
- }
3432
- }
3433
-
3434
967
  for (const info of interfaceCalls.values()) {
3435
968
  funcDecls.push(_makeInterfaceDispatcher(info, checker));
3436
969
  }
3437
970
  }
3438
-
3439
- function _registerIoSyntheticMethods(funcDecls, mod, strType, programs) {
3440
- const progs = Array.isArray(programs) ? programs : [programs];
3441
- const hasStrings = importsAnyOf(progs, ["strings"]);
3442
- const hasBytes = importsAnyOf(progs, ["bytes"]);
3443
- const hasIo = importsAnyOf(progs, ["io"]);
3444
-
3445
- const BYTE_SLICE_T = {
3446
- kind: "slice",
3447
- elem: { kind: "basic", name: "byte" },
3448
- };
3449
- const INT_T = { kind: "basic", name: "int" };
3450
- const ERR_T = { kind: "interface", name: "error" };
3451
- const INT_ERR_RET = { kind: "tuple", types: [INT_T, ERR_T] };
3452
- const ioHelper = (name, params, returnType, kind, extra = {}) => ({
3453
- kind: "FuncDecl",
3454
- name,
3455
- params,
3456
- returnType,
3457
- body: { kind: "Block", stmts: [], list: [] },
3458
- _isIoHelper: kind,
3459
- _rootFuncDecl: null,
3460
- ...extra,
3461
- });
3462
-
3463
- if (hasStrings && mod.structTypes.has("strings.Builder")) {
3464
- const bPtr = {
3465
- kind: "pointer",
3466
- base: { kind: "named", name: "strings.Builder" },
3467
- };
3468
- funcDecls.push(
3469
- ioHelper(
3470
- "strings.Builder.Write",
3471
- [
3472
- { name: "b", type: bPtr },
3473
- { name: "p", type: BYTE_SLICE_T },
3474
- ],
3475
- INT_ERR_RET,
3476
- "builder_write",
3477
- {
3478
- _isMethod: true,
3479
- _recvTypeName: "strings.Builder",
3480
- _methodName: "Write",
3481
- },
3482
- ),
3483
- ioHelper(
3484
- "strings.Builder.WriteString",
3485
- [
3486
- { name: "b", type: bPtr },
3487
- { name: "s", type: strType },
3488
- ],
3489
- INT_ERR_RET,
3490
- "builder_write_string",
3491
- {
3492
- _isMethod: true,
3493
- _recvTypeName: "strings.Builder",
3494
- _methodName: "WriteString",
3495
- },
3496
- ),
3497
- );
3498
- }
3499
- if (hasBytes && mod.structTypes.has("bytes.Buffer")) {
3500
- const bPtr = {
3501
- kind: "pointer",
3502
- base: { kind: "named", name: "bytes.Buffer" },
3503
- };
3504
- funcDecls.push(
3505
- ioHelper(
3506
- "bytes.Buffer.Write",
3507
- [
3508
- { name: "b", type: bPtr },
3509
- { name: "p", type: BYTE_SLICE_T },
3510
- ],
3511
- INT_ERR_RET,
3512
- "buffer_write",
3513
- {
3514
- _isMethod: true,
3515
- _recvTypeName: "bytes.Buffer",
3516
- _methodName: "Write",
3517
- },
3518
- ),
3519
- ioHelper(
3520
- "bytes.Buffer.WriteString",
3521
- [
3522
- { name: "b", type: bPtr },
3523
- { name: "s", type: strType },
3524
- ],
3525
- INT_ERR_RET,
3526
- "buffer_write_string",
3527
- {
3528
- _isMethod: true,
3529
- _recvTypeName: "bytes.Buffer",
3530
- _methodName: "WriteString",
3531
- },
3532
- ),
3533
- ioHelper(
3534
- "bytes.Buffer.Read",
3535
- [
3536
- { name: "b", type: bPtr },
3537
- { name: "p", type: BYTE_SLICE_T },
3538
- ],
3539
- INT_ERR_RET,
3540
- "buffer_read",
3541
- {
3542
- _isMethod: true,
3543
- _recvTypeName: "bytes.Buffer",
3544
- _methodName: "Read",
3545
- },
3546
- ),
3547
- );
3548
- }
3549
- if (hasStrings && mod.structTypes.has("strings.Reader")) {
3550
- const rPtr = {
3551
- kind: "pointer",
3552
- base: { kind: "named", name: "strings.Reader" },
3553
- };
3554
- funcDecls.push(
3555
- ioHelper(
3556
- "strings.Reader.Read",
3557
- [
3558
- { name: "r", type: rPtr },
3559
- { name: "p", type: BYTE_SLICE_T },
3560
- ],
3561
- INT_ERR_RET,
3562
- "strings_reader_read",
3563
- {
3564
- _isMethod: true,
3565
- _recvTypeName: "strings.Reader",
3566
- _methodName: "Read",
3567
- },
3568
- ),
3569
- ioHelper(
3570
- "strings.Reader.Len",
3571
- [{ name: "r", type: rPtr }],
3572
- INT_T,
3573
- "strings_reader_len",
3574
- {
3575
- _isMethod: true,
3576
- _recvTypeName: "strings.Reader",
3577
- _methodName: "Len",
3578
- },
3579
- ),
3580
- ioHelper(
3581
- "strings.Reader.Reset",
3582
- [
3583
- { name: "r", type: rPtr },
3584
- { name: "s", type: strType },
3585
- ],
3586
- null,
3587
- "strings_reader_reset",
3588
- {
3589
- _isMethod: true,
3590
- _recvTypeName: "strings.Reader",
3591
- _methodName: "Reset",
3592
- },
3593
- ),
3594
- );
3595
- }
3596
- if (hasBytes && mod.structTypes.has("bytes.Reader")) {
3597
- const rPtr = {
3598
- kind: "pointer",
3599
- base: { kind: "named", name: "bytes.Reader" },
3600
- };
3601
- funcDecls.push(
3602
- ioHelper(
3603
- "bytes.Reader.Read",
3604
- [
3605
- { name: "r", type: rPtr },
3606
- { name: "p", type: BYTE_SLICE_T },
3607
- ],
3608
- INT_ERR_RET,
3609
- "bytes_reader_read",
3610
- { _isMethod: true, _recvTypeName: "bytes.Reader", _methodName: "Read" },
3611
- ),
3612
- ioHelper(
3613
- "bytes.Reader.Len",
3614
- [{ name: "r", type: rPtr }],
3615
- INT_T,
3616
- "bytes_reader_len",
3617
- { _isMethod: true, _recvTypeName: "bytes.Reader", _methodName: "Len" },
3618
- ),
3619
- ioHelper(
3620
- "bytes.Reader.Reset",
3621
- [
3622
- { name: "r", type: rPtr },
3623
- { name: "b", type: BYTE_SLICE_T },
3624
- ],
3625
- null,
3626
- "bytes_reader_reset",
3627
- {
3628
- _isMethod: true,
3629
- _recvTypeName: "bytes.Reader",
3630
- _methodName: "Reset",
3631
- },
3632
- ),
3633
- );
3634
- }
3635
- if (hasIo && mod.structTypes.has("io.discard")) {
3636
- const dPtr = {
3637
- kind: "pointer",
3638
- base: { kind: "named", name: "io.discard" },
3639
- };
3640
- funcDecls.push(
3641
- ioHelper(
3642
- "io.discard.Write",
3643
- [
3644
- { name: "d", type: dPtr },
3645
- { name: "p", type: BYTE_SLICE_T },
3646
- ],
3647
- INT_ERR_RET,
3648
- "discard_write",
3649
- { _isMethod: true, _recvTypeName: "io.discard", _methodName: "Write" },
3650
- ),
3651
- ioHelper(
3652
- "io.discard.WriteString",
3653
- [
3654
- { name: "d", type: dPtr },
3655
- { name: "s", type: strType },
3656
- ],
3657
- INT_ERR_RET,
3658
- "discard_write_string",
3659
- {
3660
- _isMethod: true,
3661
- _recvTypeName: "io.discard",
3662
- _methodName: "WriteString",
3663
- },
3664
- ),
3665
- );
3666
- }
3667
- if (hasIo && mod.structTypes.has("io.LimitedReader")) {
3668
- const lrPtr = {
3669
- kind: "pointer",
3670
- base: { kind: "named", name: "io.LimitedReader" },
3671
- };
3672
- funcDecls.push(
3673
- ioHelper(
3674
- "io.LimitedReader.Read",
3675
- [
3676
- { name: "l", type: lrPtr },
3677
- { name: "p", type: BYTE_SLICE_T },
3678
- ],
3679
- INT_ERR_RET,
3680
- "limited_reader_read",
3681
- {
3682
- _isMethod: true,
3683
- _recvTypeName: "io.LimitedReader",
3684
- _methodName: "Read",
3685
- },
3686
- ),
3687
- );
3688
- }
3689
- const hasSha256 = importsAnyOf(progs, ["crypto/sha256", "sha256"]);
3690
- if (
3691
- hasSha256 &&
3692
- (mod.structTypes.has("sha256.digest") ||
3693
- mod.structTypes.has("crypto/sha256.digest"))
3694
- ) {
3695
- const dPtr = {
3696
- kind: "pointer",
3697
- base: { kind: "named", name: "sha256.digest" },
3698
- };
3699
- funcDecls.push(
3700
- ioHelper(
3701
- "sha256.digest.Write",
3702
- [
3703
- { name: "d", type: dPtr },
3704
- { name: "p", type: BYTE_SLICE_T },
3705
- ],
3706
- INT_ERR_RET,
3707
- "sha256_write",
3708
- {
3709
- _isMethod: true,
3710
- _recvTypeName: "sha256.digest",
3711
- _methodName: "Write",
3712
- },
3713
- ),
3714
- ioHelper(
3715
- "sha256.digest.Sum",
3716
- [
3717
- { name: "d", type: dPtr },
3718
- { name: "b", type: BYTE_SLICE_T },
3719
- ],
3720
- BYTE_SLICE_T,
3721
- "sha256_sum",
3722
- { _isMethod: true, _recvTypeName: "sha256.digest", _methodName: "Sum" },
3723
- ),
3724
- ioHelper(
3725
- "sha256.digest.Reset",
3726
- [{ name: "d", type: dPtr }],
3727
- null,
3728
- "sha256_reset",
3729
- {
3730
- _isMethod: true,
3731
- _recvTypeName: "sha256.digest",
3732
- _methodName: "Reset",
3733
- },
3734
- ),
3735
- ioHelper(
3736
- "sha256.digest.Size",
3737
- [{ name: "d", type: dPtr }],
3738
- INT_T,
3739
- "sha256_size",
3740
- {
3741
- _isMethod: true,
3742
- _recvTypeName: "sha256.digest",
3743
- _methodName: "Size",
3744
- },
3745
- ),
3746
- ioHelper(
3747
- "sha256.digest.BlockSize",
3748
- [{ name: "d", type: dPtr }],
3749
- INT_T,
3750
- "sha256_block_size",
3751
- {
3752
- _isMethod: true,
3753
- _recvTypeName: "sha256.digest",
3754
- _methodName: "BlockSize",
3755
- },
3756
- ),
3757
- );
3758
- }
3759
- }