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
@@ -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
+ });