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