gofront 1.8.0 → 1.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/README.md +3 -3
  2. package/package.json +6 -5
  3. package/src/backend/js/expressions.js +1157 -408
  4. package/src/backend/js/index.js +327 -153
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +559 -2552
  7. package/src/backend/js/statements.js +670 -158
  8. package/src/backend/js/stdlib/index.js +45 -93
  9. package/src/backend/js/templ.js +32 -45
  10. package/src/backend/js/type-info.js +298 -19
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +91 -170
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +490 -965
  15. package/src/backend/wasm/emit-builtins.js +884 -256
  16. package/src/backend/wasm/emit-exprs.js +185 -77
  17. package/src/backend/wasm/emit-maps.js +17 -367
  18. package/src/backend/wasm/emit-shared.js +4 -1
  19. package/src/backend/wasm/emit-stmts.js +524 -240
  20. package/src/backend/wasm/emit.js +176 -38
  21. package/src/backend/wasm/globals.js +48 -8
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +44 -530
  24. package/src/backend/wasm/method-expr.js +12 -1
  25. package/src/backend/wasm/module-emitter.js +299 -770
  26. package/src/backend/wasm/monomorph.js +139 -8
  27. package/src/backend/wasm/natives.js +600 -0
  28. package/src/backend/wasm/prescan.js +5 -153
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -32
  31. package/src/backend/wasm/types.js +97 -20
  32. package/src/backend/wasm/variadic.js +9 -4
  33. package/src/cli/cli-core.js +5 -1
  34. package/src/cli/test-runner.js +115 -342
  35. package/src/compiler.js +113 -130
  36. package/src/lexer/lexer.js +106 -11
  37. package/src/lexer/templ-lexer.js +61 -18
  38. package/src/lexer/tokens.js +1 -0
  39. package/src/link/stdlib.js +355 -132
  40. package/src/link/type-names.js +204 -0
  41. package/src/link/wasm.js +30 -6
  42. package/src/lower/bounds.js +153 -0
  43. package/src/lower/captures.js +92 -33
  44. package/src/lower/embedding.js +62 -5
  45. package/src/lower/errors-as.js +196 -0
  46. package/src/lower/fmt-formatters.js +929 -0
  47. package/src/lower/functions.js +225 -7
  48. package/src/lower/gen-types.js +293 -0
  49. package/src/lower/index.js +14 -2
  50. package/src/lower/json-codecs.js +334 -245
  51. package/src/lower/ownership.js +25 -7
  52. package/src/lower/reachability.js +69 -51
  53. package/src/lower/walk.js +12 -0
  54. package/src/parser/declarations.js +30 -5
  55. package/src/parser/expressions.js +111 -57
  56. package/src/parser/statements.js +108 -0
  57. package/src/parser/templ.js +44 -5
  58. package/src/parser/types.js +12 -0
  59. package/src/resolver.js +1 -2
  60. package/src/typechecker/assignability.js +214 -19
  61. package/src/typechecker/const-fold.js +70 -27
  62. package/src/typechecker/constants.js +156 -8
  63. package/src/typechecker/expressions.js +490 -108
  64. package/src/typechecker/index.js +346 -42
  65. package/src/typechecker/init-order.js +136 -40
  66. package/src/typechecker/operand-text.js +41 -0
  67. package/src/typechecker/resolve.js +490 -65
  68. package/src/typechecker/statements.js +288 -27
  69. package/src/typechecker/stdlib/excluded.js +9 -5
  70. package/src/typechecker/stdlib/index.js +25 -9
  71. package/src/typechecker/termination.js +10 -1
  72. package/src/typechecker/types.js +287 -575
  73. package/stdlib/bytes/buffer.go +479 -0
  74. package/stdlib/bytes/bytes.go +1010 -0
  75. package/stdlib/bytes/reader.go +161 -0
  76. package/stdlib/context/context.go +115 -0
  77. package/stdlib/crypto/rand/rand.go +34 -0
  78. package/stdlib/crypto/sha256/sha256.go +249 -0
  79. package/stdlib/encoding/base64/base64.go +331 -0
  80. package/stdlib/encoding/binary/binary.go +332 -0
  81. package/stdlib/encoding/hex/hex.go +140 -0
  82. package/stdlib/encoding/json/json.go +62 -0
  83. package/stdlib/errors/errors.go +174 -0
  84. package/stdlib/fmt/errors.go +110 -0
  85. package/stdlib/fmt/format.go +732 -0
  86. package/stdlib/fmt/print.go +1232 -0
  87. package/stdlib/gom/gom.go +293 -0
  88. package/stdlib/html/html.go +251 -0
  89. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  90. package/stdlib/internal/jsonrt/dec.go +171 -38
  91. package/stdlib/internal/jsonrt/enc.go +186 -36
  92. package/stdlib/internal/jsonrt/scanner.go +17 -8
  93. package/stdlib/io/io.go +157 -1
  94. package/stdlib/maps/maps.go +131 -0
  95. package/stdlib/math/bits/bits.go +507 -0
  96. package/stdlib/math/extra.go +16 -14
  97. package/stdlib/math/math.go +439 -0
  98. package/stdlib/math/rand/rand.go +297 -0
  99. package/stdlib/os/os.go +93 -0
  100. package/stdlib/path/filepath/filepath.go +47 -0
  101. package/stdlib/path/path.go +394 -0
  102. package/stdlib/regexp/regexp.go +231 -0
  103. package/stdlib/slices/slices.go +526 -0
  104. package/stdlib/slices/sort.go +209 -0
  105. package/stdlib/slices/zsort_cmp.go +480 -0
  106. package/stdlib/slices/zsort_ordered.go +482 -0
  107. package/stdlib/sort/sort.go +391 -452
  108. package/stdlib/sort/zsort_func.go +480 -0
  109. package/stdlib/sort/zsort_iface.go +480 -0
  110. package/stdlib/strconv/float.go +1699 -0
  111. package/stdlib/strconv/isprint.go +752 -0
  112. package/stdlib/strconv/quote.go +235 -50
  113. package/stdlib/strconv/strconv.go +616 -0
  114. package/stdlib/strings/builder.go +95 -0
  115. package/stdlib/strings/reader.go +163 -0
  116. package/stdlib/strings/replacer.go +102 -0
  117. package/stdlib/strings/strings.go +807 -0
  118. package/stdlib/sync/sync.go +268 -0
  119. package/stdlib/testing/async_js.go +61 -0
  120. package/stdlib/testing/benchmark.go +297 -0
  121. package/stdlib/testing/testing.go +523 -0
  122. package/stdlib/time/duration.go +233 -0
  123. package/stdlib/time/format.go +745 -0
  124. package/stdlib/time/parse.go +790 -0
  125. package/stdlib/time/sleep_js.go +63 -0
  126. package/stdlib/time/time.go +834 -0
  127. package/stdlib/unicode/unicode.go +141 -3
  128. package/stdlib/unicode/utf8/utf8.go +464 -0
  129. package/src/backend/js/fmt-runtime.js +0 -573
  130. package/src/backend/js/stdlib/base64.js +0 -12
  131. package/src/backend/js/stdlib/binary.js +0 -102
  132. package/src/backend/js/stdlib/bits.js +0 -119
  133. package/src/backend/js/stdlib/builder.js +0 -98
  134. package/src/backend/js/stdlib/bytes.js +0 -368
  135. package/src/backend/js/stdlib/errors.js +0 -45
  136. package/src/backend/js/stdlib/fmt.js +0 -517
  137. package/src/backend/js/stdlib/gom.js +0 -155
  138. package/src/backend/js/stdlib/hex.js +0 -17
  139. package/src/backend/js/stdlib/html.js +0 -20
  140. package/src/backend/js/stdlib/io.js +0 -137
  141. package/src/backend/js/stdlib/json.js +0 -1167
  142. package/src/backend/js/stdlib/maps.js +0 -37
  143. package/src/backend/js/stdlib/math.js +0 -85
  144. package/src/backend/js/stdlib/os.js +0 -13
  145. package/src/backend/js/stdlib/path.js +0 -48
  146. package/src/backend/js/stdlib/rand.js +0 -32
  147. package/src/backend/js/stdlib/regexp.js +0 -54
  148. package/src/backend/js/stdlib/sha256.js +0 -12
  149. package/src/backend/js/stdlib/shared.js +0 -17
  150. package/src/backend/js/stdlib/simd.js +0 -34
  151. package/src/backend/js/stdlib/slices.js +0 -107
  152. package/src/backend/js/stdlib/sort.js +0 -30
  153. package/src/backend/js/stdlib/strconv.js +0 -79
  154. package/src/backend/js/stdlib/strings.js +0 -301
  155. package/src/backend/js/stdlib/sync.js +0 -13
  156. package/src/backend/js/stdlib/testing.js +0 -41
  157. package/src/backend/js/stdlib/time.js +0 -152
  158. package/src/backend/js/stdlib/unicode.js +0 -25
  159. package/src/backend/js/stdlib/utf8.js +0 -26
  160. package/src/backend/wasm/stdlib/bytes.js +0 -6716
  161. package/src/backend/wasm/stdlib/core.js +0 -10278
  162. package/src/backend/wasm/stdlib/io.js +0 -3578
  163. package/src/backend/wasm/stdlib/packages.js +0 -2402
  164. package/src/backend/wasm/stdlib/pure.js +0 -720
  165. package/src/backend/wasm/stdlib/time.js +0 -2229
  166. package/src/typechecker/stdlib/core.js +0 -1079
  167. package/src/typechecker/stdlib/extended.js +0 -1210
  168. package/stdlib/bytes/iter.go +0 -192
  169. package/stdlib/bytes/search.go +0 -240
  170. package/stdlib/bytes/split.go +0 -218
  171. package/stdlib/errors/overlay.go +0 -12
  172. package/stdlib/maps/iter.go +0 -65
  173. package/stdlib/slices/extra.go +0 -73
  174. package/stdlib/slices/iter.go +0 -113
  175. package/stdlib/strings/extra.go +0 -35
  176. package/stdlib/strings/iter.go +0 -122
  177. package/stdlib/sync/extra.go +0 -105
  178. package/stdlib/unicode/utf8/bytes.go +0 -239
@@ -0,0 +1,600 @@
1
+ // WASM natives: host code that replaces a call to a `//gofront:native` stdlib
2
+ // function. The Go body is the specification and is always emitted when
3
+ // reachable; an implementation either emits the call and returns `true`, or
4
+ // returns `false` having emitted nothing, in which case the call goes to the
5
+ // Go body.
6
+ //
7
+ // An entry is `{ imports?, emit }`:
8
+ // imports `[name, params, results]` of the `env.*` imports `emit` calls.
9
+ // The import table is locked before function bodies are emitted,
10
+ // so the pre-scan registers them for every call site it finds.
11
+ // emit `(emitter, call, typeArgs, targetWasmType) => boolean`; pushes the
12
+ // arguments (`emitter.emitExpr`, in order) and then the
13
+ // instructions or the import call, leaving the call's result.
14
+
15
+ const EXT = "externref";
16
+
17
+ export const WASM_NATIVES = new Map();
18
+
19
+ // Bodies of `//gofront:intrinsic` functions (names: INTRINSICS in
20
+ // typechecker/types.js). An entry is `{ prepare?, emit }`: `prepare(mod)`
21
+ // registers the imports `emit(emitter)` calls (the import table is locked
22
+ // before bodies are emitted), and `emit` leaves the result on the stack.
23
+ const WASM_INTRINSICS = {
24
+ // math/rand: the host's `Math.random`.
25
+ rand_source: {
26
+ prepare: (mod) => mod.getMathRandomImportIndex(),
27
+ emit: (e) =>
28
+ e.pushInstruction({
29
+ op: "call",
30
+ funcIndex: e.mod.getMathRandomImportIndex(),
31
+ }),
32
+ },
33
+ // time: the host clock (`env.time_now_ms`, milliseconds) as nanoseconds.
34
+ time_now: {
35
+ prepare: (mod) => mod.getOrAddFuncImport("env", "time_now_ms", [], ["f64"]),
36
+ emit: (e) => {
37
+ e.pushInstruction({
38
+ op: "call",
39
+ funcIndex: e.mod.getOrAddFuncImport("env", "time_now_ms", [], ["f64"]),
40
+ });
41
+ e.pushInstruction("i64.trunc_sat_f64_s");
42
+ e.pushInstruction({ op: "i64.const", value: 1000000n });
43
+ e.pushInstruction("i64.mul");
44
+ },
45
+ },
46
+ // time: the host zone's offset at a Unix time (`env.time_timezone_offset`
47
+ // takes milliseconds and returns seconds).
48
+ time_zone_offset: {
49
+ prepare: (mod) =>
50
+ mod.getOrAddFuncImport("env", "time_timezone_offset", ["f64"], ["i32"]),
51
+ emit: (e) => {
52
+ e.pushInstruction({ op: "local.get", index: 0 });
53
+ e.pushInstruction("f64.convert_i64_s");
54
+ e.pushInstruction({ op: "f64.const", value: 1000 });
55
+ e.pushInstruction("f64.mul");
56
+ e.pushInstruction({
57
+ op: "call",
58
+ funcIndex: e.mod.getOrAddFuncImport(
59
+ "env",
60
+ "time_timezone_offset",
61
+ ["f64"],
62
+ ["i32"],
63
+ ),
64
+ });
65
+ e.pushInstruction("i64.extend_i32_s");
66
+ },
67
+ },
68
+ // os: the host's output, exit and environment (`env.os_*`, always present
69
+ // in the host table). A file descriptor narrows to an i32.
70
+ os_write: {
71
+ prepare: (mod) =>
72
+ mod.getOrAddFuncImport("env", "os_write", ["i32", EXT], []),
73
+ emit: (e) => {
74
+ e.pushInstruction({ op: "local.get", index: 0 });
75
+ e.pushInstruction("i32.wrap_i64");
76
+ e.pushInstruction({ op: "local.get", index: 1 });
77
+ e.pushInstruction({
78
+ op: "call",
79
+ funcIndex: e.mod.getOrAddFuncImport(
80
+ "env",
81
+ "os_write",
82
+ ["i32", EXT],
83
+ [],
84
+ ),
85
+ });
86
+ },
87
+ },
88
+ os_exit: {
89
+ prepare: (mod) => mod.getOrAddFuncImport("env", "os_exit", ["i32"], []),
90
+ emit: (e) => {
91
+ e.pushInstruction({ op: "local.get", index: 0 });
92
+ e.pushInstruction("i32.wrap_i64");
93
+ e.pushInstruction({
94
+ op: "call",
95
+ funcIndex: e.mod.getOrAddFuncImport("env", "os_exit", ["i32"], []),
96
+ });
97
+ },
98
+ },
99
+ os_getenv: hostStrings("os_getenv", 1, [EXT]),
100
+ os_setenv: hostStrings("os_setenv", 2, []),
101
+ // unicode: the host's class tables and case mappings.
102
+ unicode_is: {
103
+ prepare: (mod) =>
104
+ mod.getOrAddFuncImport("env", "unicode_is", ["i32", "i32"], ["i32"]),
105
+ emit: (e) => {
106
+ e.pushInstruction({ op: "local.get", index: 0 });
107
+ e.pushInstruction({ op: "local.get", index: 1 });
108
+ e.pushInstruction("i32.wrap_i64");
109
+ e.pushInstruction({
110
+ op: "call",
111
+ funcIndex: e.mod.getOrAddFuncImport(
112
+ "env",
113
+ "unicode_is",
114
+ ["i32", "i32"],
115
+ ["i32"],
116
+ ),
117
+ });
118
+ },
119
+ },
120
+ unicode_to_upper: hostRune("unicode_to_upper"),
121
+ unicode_to_lower: hostRune("unicode_to_lower"),
122
+ // crypto/rand: four bytes of the host's secure source.
123
+ crypto_rand_u32: {
124
+ prepare: (mod) =>
125
+ mod.getOrAddFuncImport("env", "crypto_rand_u32", [], ["i32"]),
126
+ emit: (e) =>
127
+ e.pushInstruction({
128
+ op: "call",
129
+ funcIndex: e.mod.getOrAddFuncImport(
130
+ "env",
131
+ "crypto_rand_u32",
132
+ [],
133
+ ["i32"],
134
+ ),
135
+ }),
136
+ },
137
+ // testing: the host clock in nanoseconds (`env.testing_nanotime` returns
138
+ // a double) and the host's regular expressions for -run and -bench.
139
+ testing_nanotime: {
140
+ prepare: (mod) =>
141
+ mod.getOrAddFuncImport("env", "testing_nanotime", [], ["f64"]),
142
+ emit: (e) => {
143
+ e.pushInstruction({
144
+ op: "call",
145
+ funcIndex: e.mod.getOrAddFuncImport(
146
+ "env",
147
+ "testing_nanotime",
148
+ [],
149
+ ["f64"],
150
+ ),
151
+ });
152
+ e.pushInstruction("i64.trunc_sat_f64_s");
153
+ },
154
+ },
155
+ testing_match: {
156
+ prepare: (mod) =>
157
+ mod.getOrAddFuncImport("env", "testing_match", [EXT, EXT], ["i32"]),
158
+ emit: (e) => {
159
+ e.pushInstruction({ op: "local.get", index: 0 });
160
+ e.pushInstruction({ op: "local.get", index: 1 });
161
+ e.pushInstruction({
162
+ op: "call",
163
+ funcIndex: e.mod.getOrAddFuncImport(
164
+ "env",
165
+ "testing_match",
166
+ [EXT, EXT],
167
+ ["i32"],
168
+ ),
169
+ });
170
+ },
171
+ },
172
+ // fmt: the host's identity number for a reference held in an `any`.
173
+ fmt_ptr_id: {
174
+ prepare: (mod) =>
175
+ mod.getOrAddFuncImport("env", "fmt_ptr_id", [EXT], ["i32"]),
176
+ emit: (e) => {
177
+ e.pushInstruction({ op: "local.get", index: 0 });
178
+ e.pushInstruction({
179
+ op: "call",
180
+ funcIndex: e.mod.resolveFuncIndex("__any_ref"),
181
+ });
182
+ e.pushInstruction("extern.convert_any");
183
+ e.pushInstruction({
184
+ op: "call",
185
+ funcIndex: e.mod.getOrAddFuncImport(
186
+ "env",
187
+ "fmt_ptr_id",
188
+ [EXT],
189
+ ["i32"],
190
+ ),
191
+ });
192
+ e.pushInstruction("i64.extend_i32_u");
193
+ },
194
+ },
195
+ // math: IEEE-754 bit casts of the parameter.
196
+ math_float64bits: reinterpret("i64.reinterpret_f64"),
197
+ math_float64frombits: reinterpret("f64.reinterpret_i64"),
198
+ math_float32bits: reinterpret("i32.reinterpret_f32"),
199
+ math_float32frombits: reinterpret("f32.reinterpret_i32"),
200
+ // math: the host's `Math.*` (`mod` is `%`); the import table is locked
201
+ // before bodies are emitted, so `prepare` registers each.
202
+ ...Object.fromEntries(
203
+ [
204
+ ["sin"],
205
+ ["cos"],
206
+ ["tan"],
207
+ ["asin"],
208
+ ["acos"],
209
+ ["atan"],
210
+ ["sinh"],
211
+ ["cosh"],
212
+ ["tanh"],
213
+ ["exp"],
214
+ ["expm1"],
215
+ ["log"],
216
+ ["log1p"],
217
+ ["log2"],
218
+ ["log10"],
219
+ ["cbrt"],
220
+ ["atan2", true],
221
+ ["pow", true],
222
+ ["hypot", true],
223
+ ["mod", true],
224
+ ].map(([f, binary]) => [`math_${f}`, hostMath(f, binary)]),
225
+ ),
226
+ };
227
+
228
+ function hostStrings(name, count, results) {
229
+ const params = Array(count).fill(EXT);
230
+ return {
231
+ prepare: (mod) => mod.getOrAddFuncImport("env", name, params, results),
232
+ emit: (e) => {
233
+ for (let i = 0; i < count; i++)
234
+ e.pushInstruction({ op: "local.get", index: i });
235
+ e.pushInstruction({
236
+ op: "call",
237
+ funcIndex: e.mod.getOrAddFuncImport("env", name, params, results),
238
+ });
239
+ },
240
+ };
241
+ }
242
+
243
+ function hostRune(name) {
244
+ return {
245
+ prepare: (mod) => mod.getOrAddFuncImport("env", name, ["i32"], ["i32"]),
246
+ emit: (e) => {
247
+ e.pushInstruction({ op: "local.get", index: 0 });
248
+ e.pushInstruction({
249
+ op: "call",
250
+ funcIndex: e.mod.getOrAddFuncImport("env", name, ["i32"], ["i32"]),
251
+ });
252
+ },
253
+ };
254
+ }
255
+
256
+ function reinterpret(op) {
257
+ return {
258
+ emit: (e) => {
259
+ e.pushInstruction({ op: "local.get", index: 0 });
260
+ e.pushInstruction(op);
261
+ },
262
+ };
263
+ }
264
+
265
+ function hostMath(name, binary = false) {
266
+ return {
267
+ prepare: (mod) => mod.getMathImportIndex(name, binary),
268
+ emit: (e) => {
269
+ e.pushInstruction({ op: "local.get", index: 0 });
270
+ if (binary) e.pushInstruction({ op: "local.get", index: 1 });
271
+ e.pushInstruction({
272
+ op: "call",
273
+ funcIndex: e.mod.getMathImportIndex(name, binary),
274
+ });
275
+ },
276
+ };
277
+ }
278
+
279
+ function wasmIntrinsic(fn) {
280
+ const entry = WASM_INTRINSICS[fn.intrinsic];
281
+ if (!entry) throw new Error(`no WASM body for intrinsic '${fn.intrinsic}'`);
282
+ return entry;
283
+ }
284
+
285
+ export function prepareWasmIntrinsic(mod, fn) {
286
+ wasmIntrinsic(fn).prepare?.(mod);
287
+ }
288
+
289
+ export function emitWasmIntrinsic(emitter, fn) {
290
+ wasmIntrinsic(fn).emit(emitter);
291
+ emitter.pushInstruction("return");
292
+ }
293
+
294
+ // Registers the imports of the native a call site names, if any.
295
+ export function prepareWasmNative(mod, call) {
296
+ if (!mod.natives || !call._native) return;
297
+ for (const [name, params, results] of WASM_NATIVES.get(call._native)
298
+ ?.imports ?? [])
299
+ mod.getOrAddFuncImport("env", name, params, results);
300
+ }
301
+
302
+ // Emits the call as host code; false (nothing emitted) calls the Go body.
303
+ export function emitWasmNative(emitter, call, targetWasmType) {
304
+ if (!emitter.mod.natives || !call._native || call._noNative) return false;
305
+ if (call.args.some((a) => a._spread)) return false;
306
+ const entry = WASM_NATIVES.get(call._native);
307
+ if (!entry) return false;
308
+ const typeArgs = call._typeArgs ?? call.func?._typeArgs ?? [];
309
+ return entry.emit(emitter, call, typeArgs, targetWasmType);
310
+ }
311
+
312
+ // ── strings ───────────────────────────────────────────────────
313
+ //
314
+ // Each native is one call of an `env.str_*` host import that gives the Go
315
+ // body's answer. The imports live in the always-present part of the host
316
+ // table (`WASM_IMPORTS_JS`), so a Go-source package using one needs no
317
+ // `strings` import of its own. Arguments are strings (`s`, an externref) or
318
+ // a byte or rune (`i`, an i32); results are a bool or int (`int` widens to
319
+ // the Go int) or a string.
320
+
321
+ function hostCall(name, argKinds, resultKind) {
322
+ const params = argKinds.map((k) =>
323
+ k === "s" ? EXT : k === "f" ? "f64" : "i32",
324
+ );
325
+ const results = [
326
+ resultKind === "str" ? EXT : resultKind === "float" ? "f64" : "i32",
327
+ ];
328
+ return {
329
+ imports: [[name, params, results]],
330
+ emit(e, call, _typeArgs, target) {
331
+ call.args.forEach((a, i) => {
332
+ if (argKinds[i] === "s") e.emitExpr(a, EXT);
333
+ else if (argKinds[i] === "f") e.emitExpr(a, "f64");
334
+ else e._emitI32Arg(a);
335
+ });
336
+ e.pushInstruction({
337
+ op: "call",
338
+ funcIndex: e.mod.getOrAddFuncImport("env", name, params, results),
339
+ });
340
+ if (resultKind === "int" && (target ?? "i64") === "i64")
341
+ e.pushInstruction("i64.extend_i32_s");
342
+ return true;
343
+ },
344
+ };
345
+ }
346
+
347
+ // Repeat has no WASM native: a host import cannot raise the recoverable
348
+ // panic of a negative count, so the Go body runs.
349
+ const STRING_NATIVES = {
350
+ "strings.Index": ["str_index", "ss", "int"],
351
+ "strings.LastIndex": ["str_last_index", "ss", "int"],
352
+ "strings.IndexByte": ["str_index_byte", "si", "int"],
353
+ "strings.LastIndexByte": ["str_last_index_byte", "si", "int"],
354
+ "strings.IndexRune": ["str_index_rune", "si", "int"],
355
+ "strings.IndexAny": ["str_index_any", "ss", "int"],
356
+ "strings.LastIndexAny": ["str_last_index_any", "ss", "int"],
357
+ "strings.Contains": ["str_contains", "ss", "bool"],
358
+ "strings.ContainsAny": ["str_contains_any", "ss", "bool"],
359
+ "strings.ContainsRune": ["str_contains_rune", "si", "bool"],
360
+ "strings.HasPrefix": ["str_has_prefix", "ss", "bool"],
361
+ "strings.HasSuffix": ["str_has_suffix", "ss", "bool"],
362
+ "strings.Count": ["str_count", "ss", "int"],
363
+ "strings.Replace": ["str_replace", "sssi", "str"],
364
+ "strings.ReplaceAll": ["str_replace_all", "sss", "str"],
365
+ "strings.ToUpper": ["str_to_upper", "s", "str"],
366
+ "strings.ToLower": ["str_to_lower", "s", "str"],
367
+ "strings.TrimSpace": ["str_trim_space", "s", "str"],
368
+ "strings.Trim": ["str_trim", "ss", "str"],
369
+ "strings.TrimLeft": ["str_trim_left", "ss", "str"],
370
+ "strings.TrimRight": ["str_trim_right", "ss", "str"],
371
+ "strings.TrimPrefix": ["str_trim_prefix", "ss", "str"],
372
+ "strings.TrimSuffix": ["str_trim_suffix", "ss", "str"],
373
+ };
374
+ // strconv: the shortest digits of a float64 (a string) and a decimal's
375
+ // float64 (`f` is an f64 argument).
376
+ STRING_NATIVES["strconv.shortest"] = ["strconv_shortest", "f", "str"];
377
+ STRING_NATIVES["strconv.fixed"] = ["strconv_fixed", "fi", "str"];
378
+ STRING_NATIVES["strconv.parseDecimal"] = [
379
+ "strconv_parse_decimal",
380
+ "s",
381
+ "float",
382
+ ];
383
+ for (const [native, [name, kinds, result]] of Object.entries(STRING_NATIVES))
384
+ WASM_NATIVES.set(native, hostCall(name, [...kinds], result));
385
+
386
+ // ── math/bits ─────────────────────────────────────────────────
387
+ //
388
+ // `clz`, `ctz` and `popcnt` of the operand's width. Operands narrower than 32
389
+ // bits are already zero-extended in their i32; `TrailingZeros` sets a bit
390
+ // above the operand so that a zero input counts its width.
391
+
392
+ function bitsNative(family, width) {
393
+ const wide = width === 64;
394
+ const ty = wide ? "i64" : "i32";
395
+ const constant = (value) => ({
396
+ op: `${ty}.const`,
397
+ value: wide ? BigInt(value) : value,
398
+ });
399
+ return {
400
+ emit(e, call, _typeArgs, target) {
401
+ if (family === "Len") e.pushInstruction(constant(wide ? 64 : 32));
402
+ if (wide) e.emitExpr(call.args[0], "i64");
403
+ else e._emitI32Arg(call.args[0]);
404
+ switch (family) {
405
+ case "Len":
406
+ e.pushInstruction(`${ty}.clz`);
407
+ e.pushInstruction(`${ty}.sub`);
408
+ break;
409
+ case "LeadingZeros":
410
+ e.pushInstruction(`${ty}.clz`);
411
+ if (width < 32) {
412
+ e.pushInstruction(constant(32 - width));
413
+ e.pushInstruction("i32.sub");
414
+ }
415
+ break;
416
+ case "TrailingZeros":
417
+ if (width < 32) {
418
+ e.pushInstruction(constant(1 << width));
419
+ e.pushInstruction("i32.or");
420
+ }
421
+ e.pushInstruction(`${ty}.ctz`);
422
+ break;
423
+ case "OnesCount":
424
+ e.pushInstruction(`${ty}.popcnt`);
425
+ break;
426
+ }
427
+ if (wide) {
428
+ if (target === "i32") e.pushInstruction("i32.wrap_i64");
429
+ } else if ((target ?? "i64") === "i64") {
430
+ e.pushInstruction("i64.extend_i32_u");
431
+ }
432
+ return true;
433
+ },
434
+ };
435
+ }
436
+
437
+ for (const family of ["Len", "LeadingZeros", "TrailingZeros", "OnesCount"]) {
438
+ for (const [suffix, width] of [
439
+ ["", 64],
440
+ ["8", 8],
441
+ ["16", 16],
442
+ ["32", 32],
443
+ ["64", 64],
444
+ ])
445
+ WASM_NATIVES.set(`bits.${family}${suffix}`, bitsNative(family, width));
446
+ }
447
+
448
+ // ── math ──────────────────────────────────────────────────────
449
+ //
450
+ // One f64 instruction where it gives Go's answer. `Round` rounds half away
451
+ // from zero (`f64.nearest` rounds half to even) and `Min`/`Max` return an
452
+ // infinity even beside a NaN (`f64.min`/`f64.max` return the NaN), so those
453
+ // three add the fix-up around the instruction.
454
+
455
+ const f64Unary = (op) => ({
456
+ emit(e, call) {
457
+ e.emitExpr(call.args[0], "f64");
458
+ e.pushInstruction(op);
459
+ return true;
460
+ },
461
+ });
462
+ WASM_NATIVES.set("math.Abs", f64Unary("f64.abs"));
463
+ WASM_NATIVES.set("math.Floor", f64Unary("f64.floor"));
464
+ WASM_NATIVES.set("math.Ceil", f64Unary("f64.ceil"));
465
+ WASM_NATIVES.set("math.Trunc", f64Unary("f64.trunc"));
466
+ WASM_NATIVES.set("math.Sqrt", f64Unary("f64.sqrt"));
467
+ WASM_NATIVES.set("math.Copysign", {
468
+ emit(e, call) {
469
+ e.emitExpr(call.args[0], "f64");
470
+ e.emitExpr(call.args[1], "f64");
471
+ e.pushInstruction("f64.copysign");
472
+ return true;
473
+ },
474
+ });
475
+
476
+ // min/max of two f64, except that `inf` wins over a NaN.
477
+ function infWins(op, inf) {
478
+ return {
479
+ emit(e, call) {
480
+ const a = e.acquireTemp("f64");
481
+ const b = e.acquireTemp("f64");
482
+ e.emitExpr(call.args[0], "f64");
483
+ e.pushInstruction({ op: "local.set", index: a });
484
+ e.emitExpr(call.args[1], "f64");
485
+ e.pushInstruction({ op: "local.set", index: b });
486
+ e.pushInstruction({ op: "f64.const", value: inf });
487
+ e.pushInstruction({ op: "local.get", index: a });
488
+ e.pushInstruction({ op: "local.get", index: b });
489
+ e.pushInstruction(op);
490
+ for (const t of [a, b]) {
491
+ e.pushInstruction({ op: "local.get", index: t });
492
+ e.pushInstruction({ op: "f64.const", value: inf });
493
+ e.pushInstruction("f64.eq");
494
+ }
495
+ e.pushInstruction("i32.or");
496
+ e.pushInstruction("select");
497
+ e.releaseTemp(b, "f64");
498
+ e.releaseTemp(a, "f64");
499
+ return true;
500
+ },
501
+ };
502
+ }
503
+ WASM_NATIVES.set("math.Max", infWins("f64.max", Infinity));
504
+ WASM_NATIVES.set("math.Min", infWins("f64.min", -Infinity));
505
+
506
+ WASM_NATIVES.set("math.Round", {
507
+ emit(e, call) {
508
+ const x = e.acquireTemp("f64");
509
+ const t = e.acquireTemp("f64");
510
+ const get = (i) => e.pushInstruction({ op: "local.get", index: i });
511
+ e.emitExpr(call.args[0], "f64");
512
+ e.pushInstruction({ op: "local.set", index: x });
513
+ get(x);
514
+ e.pushInstruction("f64.trunc");
515
+ e.pushInstruction({ op: "local.set", index: t });
516
+ // t + copysign(1, x) when |x - t| >= 0.5, else t
517
+ get(t);
518
+ e.pushInstruction({ op: "f64.const", value: 1 });
519
+ get(x);
520
+ e.pushInstruction("f64.copysign");
521
+ e.pushInstruction("f64.add");
522
+ get(t);
523
+ get(x);
524
+ get(t);
525
+ e.pushInstruction("f64.sub");
526
+ e.pushInstruction("f64.abs");
527
+ e.pushInstruction({ op: "f64.const", value: 0.5 });
528
+ e.pushInstruction("f64.ge");
529
+ e.pushInstruction("select");
530
+ e.releaseTemp(t, "f64");
531
+ e.releaseTemp(x, "f64");
532
+ return true;
533
+ },
534
+ });
535
+
536
+ WASM_NATIVES.set("math.IsNaN", {
537
+ emit(e, call) {
538
+ const x = e.acquireTemp("f64");
539
+ e.emitExpr(call.args[0], "f64");
540
+ e.pushInstruction({ op: "local.tee", index: x });
541
+ e.pushInstruction({ op: "local.get", index: x });
542
+ e.pushInstruction("f64.ne");
543
+ e.releaseTemp(x, "f64");
544
+ return true;
545
+ },
546
+ });
547
+
548
+ // IsInf(f, sign): (sign >= 0 && f == +Inf) || (sign <= 0 && f == -Inf)
549
+ WASM_NATIVES.set("math.IsInf", {
550
+ emit(e, call) {
551
+ const f = e.acquireTemp("f64");
552
+ const s = e.acquireTemp("i64");
553
+ e.emitExpr(call.args[0], "f64");
554
+ e.pushInstruction({ op: "local.set", index: f });
555
+ e.emitExpr(call.args[1], "i64");
556
+ e.pushInstruction({ op: "local.set", index: s });
557
+ for (const [cmp, inf] of [
558
+ ["i64.ge_s", Infinity],
559
+ ["i64.le_s", -Infinity],
560
+ ]) {
561
+ e.pushInstruction({ op: "local.get", index: s });
562
+ e.pushInstruction({ op: "i64.const", value: 0n });
563
+ e.pushInstruction(cmp);
564
+ e.pushInstruction({ op: "local.get", index: f });
565
+ e.pushInstruction({ op: "f64.const", value: inf });
566
+ e.pushInstruction("f64.eq");
567
+ e.pushInstruction("i32.and");
568
+ }
569
+ e.pushInstruction("i32.or");
570
+ e.releaseTemp(s, "i64");
571
+ e.releaseTemp(f, "f64");
572
+ return true;
573
+ },
574
+ });
575
+
576
+ // Inf(sign): +Inf copysigned with the sign (0 converts to +0.0).
577
+ WASM_NATIVES.set("math.Inf", {
578
+ emit(e, call) {
579
+ e.pushInstruction({ op: "f64.const", value: Infinity });
580
+ e.emitExpr(call.args[0], "i64");
581
+ e.pushInstruction("f64.convert_i64_s");
582
+ e.pushInstruction("f64.copysign");
583
+ return true;
584
+ },
585
+ });
586
+ WASM_NATIVES.set("math.NaN", {
587
+ emit(e) {
588
+ e.pushInstruction({ op: "f64.const", value: NaN });
589
+ return true;
590
+ },
591
+ });
592
+ WASM_NATIVES.set("math.Signbit", {
593
+ emit(e, call) {
594
+ e.emitExpr(call.args[0], "f64");
595
+ e.pushInstruction("i64.reinterpret_f64");
596
+ e.pushInstruction({ op: "i64.const", value: 0n });
597
+ e.pushInstruction("i64.lt_s");
598
+ return true;
599
+ },
600
+ });