@amritk/nish-aarch64-darwin 0.35.0 → 0.36.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 (119) hide show
  1. package/INSTALL.md +6 -1
  2. package/bin/nish +0 -0
  3. package/docs/AI.md +9 -2
  4. package/package.json +1 -1
  5. package/runtime/nish.d.ts +4612 -430
  6. package/runtime/vite.d.ts +30 -0
  7. package/runtime/vite.mjs +150 -0
  8. package/scripts/build.sh +108 -13
  9. package/std/base64.ts +34 -5
  10. package/std/bytes.ts +256 -40
  11. package/std/cli-args.ts +296 -70
  12. package/std/cli-flags.ts +184 -19
  13. package/std/cli.ts +475 -54
  14. package/std/collections.ts +121 -23
  15. package/std/compress/checksum.ts +44 -14
  16. package/std/compress/deflate.ts +224 -111
  17. package/std/compress/gzip.ts +217 -53
  18. package/std/compress/zlib.ts +63 -7
  19. package/std/crypto/aes.ts +80 -9
  20. package/std/crypto/base64url.ts +22 -0
  21. package/std/crypto/bcrypt-batch.ts +88 -21
  22. package/std/crypto/bcrypt.ts +60 -0
  23. package/std/crypto/chacha20poly1305.ts +53 -0
  24. package/std/crypto/ct.ts +32 -13
  25. package/std/crypto/ed25519.ts +47 -0
  26. package/std/crypto/hkdf.ts +198 -13
  27. package/std/crypto/hmac.ts +235 -31
  28. package/std/crypto/jwt.ts +192 -63
  29. package/std/crypto/p256.ts +159 -16
  30. package/std/crypto/p384.ts +28 -3
  31. package/std/crypto/pki-roots.ts +32 -2
  32. package/std/crypto/pki.ts +81 -15
  33. package/std/crypto/rsa.ts +33 -3
  34. package/std/crypto/sha1.ts +3 -0
  35. package/std/crypto/sha256.ts +24 -1
  36. package/std/crypto/sha512.ts +99 -14
  37. package/std/crypto/x25519.ts +152 -14
  38. package/std/crypto/x509-random.ts +10 -0
  39. package/std/crypto/x509.ts +67 -7
  40. package/std/json-equal.ts +19 -0
  41. package/std/json-format.ts +112 -13
  42. package/std/json-number.ts +64 -14
  43. package/std/json-pointer.ts +23 -1
  44. package/std/json-tokenizer.ts +100 -29
  45. package/std/json-typed.ts +713 -66
  46. package/std/json-value.ts +400 -124
  47. package/std/json.ts +138 -10
  48. package/std/map.ts +25 -7
  49. package/std/math.ts +36 -4
  50. package/std/net/buffer-pool.ts +42 -11
  51. package/std/net/dns-config.ts +109 -23
  52. package/std/net/dns-message.ts +62 -7
  53. package/std/net/dns-resolver.ts +247 -53
  54. package/std/net/dns.ts +203 -35
  55. package/std/net/fetch.ts +151 -61
  56. package/std/net/handler.ts +278 -80
  57. package/std/net/hpack.ts +262 -52
  58. package/std/net/http-compress.ts +125 -15
  59. package/std/net/http-fields.ts +85 -9
  60. package/std/net/http1-client-codec.ts +100 -19
  61. package/std/net/http1-codec.ts +235 -27
  62. package/std/net/http1-server.ts +343 -85
  63. package/std/net/http2-frame.ts +272 -35
  64. package/std/net/http2-tls.ts +135 -34
  65. package/std/net/http2.ts +247 -106
  66. package/std/net/http3-frame.ts +154 -17
  67. package/std/net/http3-server.ts +246 -96
  68. package/std/net/http3.ts +307 -115
  69. package/std/net/outbound.ts +37 -3
  70. package/std/net/postgres-carrier.ts +144 -8
  71. package/std/net/postgres.ts +723 -103
  72. package/std/net/prefork.ts +164 -31
  73. package/std/net/qpack.ts +105 -18
  74. package/std/net/quic-conn-ack.ts +59 -12
  75. package/std/net/quic-conn-cid.ts +124 -27
  76. package/std/net/quic-conn-params.ts +70 -10
  77. package/std/net/quic-datagram.ts +43 -3
  78. package/std/net/quic-frame.ts +286 -12
  79. package/std/net/quic-listener.ts +101 -32
  80. package/std/net/quic-packet.ts +302 -21
  81. package/std/net/quic-recovery.ts +228 -44
  82. package/std/net/quic-stream.ts +353 -91
  83. package/std/net/quic.ts +489 -171
  84. package/std/net/router.ts +87 -40
  85. package/std/net/tls/client.ts +166 -51
  86. package/std/net/tls/codec.ts +413 -56
  87. package/std/net/tls/record-client.ts +110 -55
  88. package/std/net/tls/record-server.ts +159 -59
  89. package/std/net/tls/record.ts +109 -12
  90. package/std/net/tls/schedule.ts +235 -26
  91. package/std/net/tls/ticket.ts +159 -40
  92. package/std/net/tls/verify.ts +49 -3
  93. package/std/net/tls-tcp.ts +250 -45
  94. package/std/net/tls.ts +221 -59
  95. package/std/net/upstream.ts +81 -45
  96. package/std/net/url.ts +97 -13
  97. package/std/net/websocket.ts +122 -12
  98. package/std/net/webtransport.ts +220 -89
  99. package/std/pair.ts +10 -2
  100. package/std/random.ts +34 -9
  101. package/std/regex.ts +352 -198
  102. package/std/region.ts +70 -15
  103. package/std/secret.ts +52 -11
  104. package/std/sort.ts +17 -2
  105. package/std/sqlite.ts +256 -35
  106. package/std/sync/ring.ts +205 -35
  107. package/std/sync/shm.ts +194 -16
  108. package/std/term-io.ts +137 -64
  109. package/std/term-pixels.ts +144 -19
  110. package/std/term-probe.ts +66 -25
  111. package/std/term-rate.ts +128 -57
  112. package/std/term.ts +332 -121
  113. package/std/testing.ts +130 -32
  114. package/std/text.ts +73 -7
  115. package/std/threads.ts +229 -41
  116. package/std/yaml-json.ts +94 -82
  117. package/std/yaml-lines.ts +47 -15
  118. package/std/yaml-write.ts +24 -16
  119. package/std/yaml.ts +764 -384
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `nish/vite`: the Vite plugin that builds a Nish module imported by a page
3
+ * into wasm, inlined and loaded without a fetch (docs/guide/using-nish/interop.md).
4
+ */
5
+
6
+ export interface NishViteOptions {
7
+ /** Modules to build to wasm, paths from the root. Default: none. */
8
+ entries?: string[];
9
+ /**
10
+ * Host modules, paths from the root, each passed to the compiler as
11
+ * `--host`; a root `package.json` `"nish": { "host": [...] }` also names
12
+ * them. The ones an entry imports are passed to its loader. Default: none.
13
+ */
14
+ host?: string[];
15
+ /** The `nish` command. Default: this package's `bin/nish`. */
16
+ compiler?: string;
17
+ /** `scripts/build.sh`. Default: this package's. */
18
+ buildScript?: string;
19
+ }
20
+
21
+ /** The hooks the plugin uses, which Vite and Rollup share. */
22
+ export interface NishVitePlugin {
23
+ name: "nish";
24
+ enforce: "pre";
25
+ configResolved(config: { root?: string; command?: "build" | "serve"; cacheDir?: string }): void;
26
+ resolveId(source: string, importer?: string): Promise<string | null>;
27
+ load(id: string): Promise<string | null>;
28
+ }
29
+
30
+ export default function nish(options?: NishViteOptions): NishVitePlugin;
@@ -0,0 +1,150 @@
1
+ /**
2
+ * `nish/vite` (WP50 stage 1): a Vite plugin that lets a page import a Nish
3
+ * module as the TypeScript it is and get, in a build, the wasm build of it.
4
+ *
5
+ * The entries are the modules compiled to wasm, `nish({ entries })`, paths
6
+ * from the root. (The root package.json's `nish` field cannot name them yet:
7
+ * the compiler refuses a key there other than `noPanic`, `capabilities` and
8
+ * `host`.) In
9
+ * `vite build` an import that resolves to one is answered with a generated
10
+ * module. The plugin compiles the entry with `nish --target wasm32 --emit-dts`,
11
+ * links the IR with `scripts/build.sh --profile wasm-host --inline`, and the
12
+ * generated module calls the loader's `loadSync` on the inlined bytes and
13
+ * re-exports each export by name. The page fetches nothing and awaits nothing.
14
+ * In `vite serve` the import is left to Vite, which serves the TypeScript.
15
+ *
16
+ * An entry that imports host modules (WP50 stage 2) is instantiated with
17
+ * them: the generated module imports each one the `.d.ts` names in `Imports`
18
+ * and passes them as `host`, so the page passes nothing. One seam is left for
19
+ * a later stage of docs/wp50-wasm-host.md: the `.nish` client specifier in
20
+ * `resolveId`.
21
+ *
22
+ * The plugin drives only the hooks Vite and Rollup share (`configResolved`,
23
+ * `resolveId`, `load`), so `tests/run.js` calls them directly, with no Vite.
24
+ */
25
+ import { execFile } from "node:child_process";
26
+ import { mkdirSync, readFileSync } from "node:fs";
27
+ import path from "node:path";
28
+ import { fileURLToPath } from "node:url";
29
+ import { promisify } from "node:util";
30
+
31
+ const run = promisify(execFile);
32
+ const packageDir = path.dirname(path.dirname(fileURLToPath(import.meta.url)));
33
+ const PREFIX = "\0nish-entry:";
34
+
35
+ /**
36
+ * The host modules an entry imports, as absolute paths: the
37
+ * `typeof import("...")` members of the `.d.ts`'s `Imports` type, each a
38
+ * specifier from the `.d.ts`'s directory written without its `.ts`, which is
39
+ * put back so the import names the file. None when there is no `Imports`.
40
+ */
41
+ function hostModules(dts, dir) {
42
+ const line = dts.split("\n").find((l) => l.startsWith("export type Imports = "));
43
+ if (line === undefined) return [];
44
+ return [...line.matchAll(/typeof import\("([^"]+)"\)/g)].map((m) => `${path.resolve(dir, m[1])}.ts`);
45
+ }
46
+
47
+ /**
48
+ * The import lines and the import object an entry is instantiated with: each
49
+ * host module imported whole and passed under `host`, or `{}` with none.
50
+ */
51
+ function importsFor(hosts) {
52
+ const lines = hosts.map((h, i) => `import * as __nishHost${i} from ${JSON.stringify(h)};`);
53
+ if (hosts.length === 0) return { lines, object: "{}" };
54
+ const host = hosts.length === 1 ? "__nishHost0" : `{ ${hosts.map((_, i) => `...__nishHost${i}`).join(", ")} }`;
55
+ return { lines, object: `{ host: ${host} }` };
56
+ }
57
+
58
+ /**
59
+ * The names the loader returns, read from the `Exports` interface of the
60
+ * `.d.ts` the same compile wrote: one member a line, `memory` included.
61
+ */
62
+ function exportNames(dts) {
63
+ const body = dts.slice(dts.indexOf("export interface Exports {"));
64
+ const names = [];
65
+ for (const m of body.slice(0, body.indexOf("\n}")).matchAll(/^ {2}(?:readonly )?([A-Za-z_$][\w$]*)[(:]/gm)) {
66
+ names.push(m[1]);
67
+ }
68
+ return names;
69
+ }
70
+
71
+ /** The plugin. `compiler` and `buildScript` default to the ones in this package. */
72
+ export default function nish(options = {}) {
73
+ const compiler = options.compiler ?? path.join(packageDir, "bin", "nish");
74
+ const buildScript = options.buildScript ?? path.join(packageDir, "scripts", "build.sh");
75
+ const runtimeWasm = path.join(packageDir, "runtime", "runtime-wasm.c");
76
+ let root = process.cwd();
77
+ let building = false;
78
+ let cacheDir = path.join(root, "node_modules", ".vite");
79
+ let entries = new Set();
80
+
81
+ const configure = (config) => {
82
+ root = config.root ?? root;
83
+ building = config.command === "build";
84
+ cacheDir = config.cacheDir ?? path.join(root, "node_modules", ".vite");
85
+ entries = new Set((options.entries ?? []).map((e) => path.resolve(root, e)));
86
+ };
87
+ configure({ root });
88
+
89
+ /** Compile and link one entry; resolves to the paths the generated module imports. */
90
+ const buildEntry = async (entry) => {
91
+ const stem = path.basename(entry, path.extname(entry));
92
+ const dir = path.join(cacheDir, "nish", path.relative(root, entry).replace(/[\\/]/g, "_"));
93
+ mkdirSync(dir, { recursive: true });
94
+ const ll = path.join(dir, `${stem}.ll`);
95
+ const dts = path.join(dir, `${stem}.d.ts`);
96
+ const host = (options.host ?? []).flatMap((h) => ["--host", path.resolve(root, h)]);
97
+ await run(compiler, [entry, "--target", "wasm32", ...host, "--emit-dts", dts, "-o", ll], { cwd: root });
98
+ const loader = path.join(dir, `${stem}.mjs`);
99
+ const ir = readFileSync(ll, "utf8");
100
+ // runtime-wasm.c is linked when the IR needs a runtime function (a weak
101
+ // `extern_weak` one, a panic stub wasm-ld writes itself, does not count)
102
+ // or the loader calls the arena.
103
+ const needsRuntime =
104
+ /^declare (?!extern_weak)[^"\n]*@nish_/m.test(ir) ||
105
+ readFileSync(loader, "utf8").includes("raw.nish_arena_mark(");
106
+ const inline = path.join(dir, `${stem}.bytes.mjs`);
107
+ const inputs = needsRuntime ? [ll, runtimeWasm] : [ll];
108
+ await run("bash", [buildScript, ...inputs, "-o", path.join(dir, `${stem}.wasm`), "--profile", "wasm-host", "--inline", inline], {
109
+ cwd: root,
110
+ });
111
+ const types = readFileSync(dts, "utf8");
112
+ return { loader, inline, names: exportNames(types), hosts: hostModules(types, dir) };
113
+ };
114
+
115
+ return {
116
+ name: "nish",
117
+ enforce: "pre",
118
+ configResolved: configure,
119
+ async resolveId(source, importer) {
120
+ if (!building) return null;
121
+ // The seam for a `./client.nish` specifier (the page's client module of a
122
+ // later stage) is here; stage 1 resolves only the entries.
123
+ const base = importer ? path.dirname(importer) : root;
124
+ const candidates = source.startsWith(".")
125
+ ? [path.resolve(base, source)]
126
+ : path.isAbsolute(source)
127
+ ? [source, path.join(root, source)]
128
+ : [];
129
+ const hit = candidates.find((c) => entries.has(c));
130
+ return hit === undefined ? null : PREFIX + hit;
131
+ },
132
+ async load(id) {
133
+ if (!id.startsWith(PREFIX)) return null;
134
+ const entry = id.slice(PREFIX.length);
135
+ const { loader, inline, names, hosts } = await buildEntry(entry);
136
+ const imports = importsFor(hosts);
137
+ // The module's own bindings are prefixed, so an export named `bytes`,
138
+ // `loadSync` or `exports` does not redeclare one of them.
139
+ const lines = [
140
+ `import { loadSync as __nishLoadSync } from ${JSON.stringify(loader)};`,
141
+ `import { bytes as __nishBytes } from ${JSON.stringify(inline)};`,
142
+ ...imports.lines,
143
+ `const __nishExports = __nishLoadSync(__nishBytes, ${imports.object});`,
144
+ "export default __nishExports;",
145
+ ];
146
+ if (names.length > 0) lines.push(`export const { ${names.join(", ")} } = __nishExports;`);
147
+ return `${lines.join("\n")}\n`;
148
+ },
149
+ };
150
+ }
package/scripts/build.sh CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env bash
2
2
  # Build an Nish .ll module (plus optional C sources) into a native binary.
3
3
  #
4
- # scripts/build.sh <module.ll> [more .ll/.c files...] -o <out> [--profile debug|speed|size|wasm|wasi|napi]
4
+ # scripts/build.sh <module.ll> [more .ll/.c files...] -o <out> [--profile debug|speed|size|wasm|wasm-host|wasi|napi]
5
5
  #
6
6
  # The C runtime is sixteen translation units and is named as one: an input
7
7
  # <dir>/runtime.c also compiles <dir>/runtime-os.c, the half that wraps the
@@ -33,6 +33,28 @@
33
33
  # load it from Node. Add runtime/runtime-wasm.c to the inputs when a
34
34
  # function uses arrays (the arena and the array cold paths, no libc);
35
35
  # strings and I/O still need a WASI runtime and are not available.
36
+ # wasm-host the wasm profile's module for a page (WP50 stage 1): the same
37
+ # inputs, linked with an explicit export list instead of exporting
38
+ # everything -- the program's exported functions, `memory`, and,
39
+ # when runtime-wasm.c is linked, the arena entry points the
40
+ # `--emit-dts` loader calls (nish_arena_mark, nish_arena_release,
41
+ # nish_alloc_array, nish_reset_arena, nish_free_arena). The stack is
42
+ # a fixed 64 KiB (-z stack-size=65536). Runs `wasm-opt -Oz` on the
43
+ # module when wasm-opt (binaryen) is on PATH; the build line says
44
+ # whether it ran. Two flags take this profile alone, and any other
45
+ # refuses them with exit 2:
46
+ # --memory <MiB> memory fixed at <MiB> MiB (1 to 4096), the
47
+ # initial size and the maximum both, so it
48
+ # never grows and `memory.buffer` is one
49
+ # ArrayBuffer for the module's life; an arena
50
+ # that runs out traps. Without it (the
51
+ # default) the memory starts at what the
52
+ # module needs and grows on demand, as under
53
+ # the wasm profile.
54
+ # --inline <file.mjs> also write <file.mjs>, an ES module whose
55
+ # `export const bytes = new Uint8Array([...])`
56
+ # holds the .wasm's bytes, so a page loads the
57
+ # module with `loadSync(bytes)` and no fetch.
36
58
  # wasi wasm32-wasi command module: runtime.c linked against wasi-libc, so
37
59
  # string programs run under any WASI host (`_start` runs `main`;
38
60
  # `node examples/wasi-host.mjs app.wasm args...`). Needs a WASI
@@ -138,6 +160,9 @@ cpu="" # --cpu x86-64-v3: the one CPU level abov
138
160
  sqlite_to="" # --sqlite-objects <dir>: compile runtime/sqlite/ alone, into <dir>
139
161
  sqlite_from="" # --sqlite-from <dir>: link runtime/sqlite/ compiled there
140
162
  modules_at="" # --module-objects <dir>: each X.ll's object is <dir>/X.o
163
+ memory_mib="" # --memory <MiB>: wasm-host's fixed memory (WP50 stage 1)
164
+ wasm_opt="" # what the build line says of wasm-opt; set only by wasm-host
165
+ inline_to="" # --inline <file.mjs>: wasm-host's bytes as an ES module
141
166
  while [ $# -gt 0 ]; do
142
167
  case "$1" in
143
168
  -o) out="$2"; shift 2 ;;
@@ -152,6 +177,12 @@ while [ $# -gt 0 ]; do
152
177
  --sqlite-objects) sqlite_to="$2"; shift 2 ;;
153
178
  --sqlite-from) sqlite_from="$2"; shift 2 ;;
154
179
  --module-objects) modules_at="$2"; shift 2 ;;
180
+ --memory)
181
+ [ -n "${2-}" ] || { echo "error: --memory needs a size in MiB" >&2; exit 2; }
182
+ memory_mib="$2"; shift 2 ;;
183
+ --inline)
184
+ [ -n "${2-}" ] || { echo "error: --inline needs a file to write" >&2; exit 2; }
185
+ inline_to="$2"; shift 2 ;;
155
186
  --pgo-generate) pgo=(-fprofile-generate); shift ;;
156
187
  --pgo-use)
157
188
  [ -f "$2" ] || { echo "error: --pgo-use: profile '$2' not found (run the instrumented binary, then llvm-profdata merge)" >&2; exit 2; }
@@ -164,9 +195,19 @@ done
164
195
  if [ -n "$cpu" ]; then
165
196
  [ "$cpu" = x86-64-v3 ] || { echo "error: --cpu takes x86-64-v3, not '$cpu'" >&2; exit 2; }
166
197
  case "$profile" in
167
- wasm|wasi) echo "error: --cpu x86-64-v3 is an x86-64 level, so the $profile profile does not take it" >&2; exit 2 ;;
198
+ wasm|wasm-host|wasi) echo "error: --cpu x86-64-v3 is an x86-64 level, so the $profile profile does not take it" >&2; exit 2 ;;
168
199
  esac
169
200
  fi
201
+ if [ -n "$memory_mib" ] || [ -n "$inline_to" ]; then
202
+ [ "$profile" = wasm-host ] || { echo "error: --memory and --inline take the wasm-host profile, not '$profile'" >&2; exit 2; }
203
+ fi
204
+ if [ -n "$memory_mib" ]; then
205
+ case "$memory_mib" in
206
+ *[!0-9]*|0*) memory_ok=0 ;;
207
+ *) if [ "${#memory_mib}" -le 4 ] && [ "$memory_mib" -le 4096 ]; then memory_ok=1; else memory_ok=0; fi ;;
208
+ esac
209
+ [ "$memory_ok" = 1 ] || { echo "error: --memory takes a whole number of MiB from 1 to 4096, not '$memory_mib'" >&2; exit 2; }
210
+ fi
170
211
  # wasm-ld exports a wasm module's linear memory as `memory`, so under the wasm
171
212
  # profile's `--export-all` a function of that name makes a module Node refuses
172
213
  # to compile ("Duplicate export name 'memory'", #643). The wasi module exports
@@ -175,7 +216,7 @@ fi
175
216
  # when it knows the build is wasm, but IR compiled for the host with `-o` alone
176
217
  # reaches these profiles without telling it, so the IR is read here too.
177
218
  case "$profile" in
178
- wasm|wasi)
219
+ wasm|wasm-host|wasi)
179
220
  for i in ${inputs[@]+"${inputs[@]}"}; do
180
221
  case "$i" in
181
222
  *.ll)
@@ -298,8 +339,8 @@ if [ "$sqlite_wanted" = 1 ] && [ -z "$sqlite_dir" ]; then
298
339
  echo "error: the module calls SQLite, and $(dirname "$0")/../runtime/sqlite/ is not there" >&2
299
340
  exit 2
300
341
  fi
301
- if [ "$sqlite_wanted" = 1 ] && [ "$profile" = wasm ]; then
302
- echo "error: the module calls SQLite, which the wasm profile cannot link: it has no libc for runtime/sqlite/sqlite3.c to build on; use --profile wasi" >&2
342
+ if [ "$sqlite_wanted" = 1 ] && { [ "$profile" = wasm ] || [ "$profile" = wasm-host ]; }; then
343
+ echo "error: the module calls SQLite, which the $profile profile cannot link: it has no libc for runtime/sqlite/sqlite3.c to build on; use --profile wasi" >&2
303
344
  exit 2
304
345
  fi
305
346
 
@@ -672,7 +713,7 @@ case "$profile" in
672
713
  speed|size)
673
714
  "$CC" "${common[@]}" "${optimise[@]}" ${lto_parts[@]+"${lto_parts[@]}"} \
674
715
  "${gc[@]}" ${strip_flag[@]+"${strip_flag[@]}"} "${inputs[@]}" -lm -o "$out" ;;
675
- wasm)
716
+ wasm|wasm-host)
676
717
  # A region's chunks come from malloc, and the freestanding wasm runtime
677
718
  # (runtime-wasm.c) has none, so IR that makes or enters one is refused here
678
719
  # rather than failing at the link on an undefined `nish_region_*`. The wasi
@@ -680,7 +721,7 @@ case "$profile" in
680
721
  for i in ${inputs[@]+"${inputs[@]}"}; do
681
722
  case "$i" in
682
723
  *.ll) if grep -q "@nish_region_" "$i"; then
683
- echo "error: $i uses regions, which the wasm profile cannot link (it has no malloc); build it with --profile wasi" >&2
724
+ echo "error: $i uses regions, which the $profile profile cannot link (it has no malloc); build it with --profile wasi" >&2
684
725
  exit 2
685
726
  fi ;;
686
727
  esac
@@ -689,7 +730,7 @@ case "$profile" in
689
730
  # rather than probing PATH so a Homebrew llvm without a PATH entry still works.
690
731
  wasm_ld=$("$CC" -print-prog-name=wasm-ld)
691
732
  if [ ! -x "$wasm_ld" ]; then
692
- echo "error: the wasm profile needs wasm-ld (install lld; on macOS: brew install llvm@18)" >&2
733
+ echo "error: the $profile profile needs wasm-ld (install lld; on macOS: brew install llvm@18)" >&2
693
734
  exit 2
694
735
  fi
695
736
  # IR compiled for the host rather than with `--target wasm32` calls std/crypto/aes.ts's
@@ -749,10 +790,64 @@ case "$profile" in
749
790
  fi
750
791
  # -mbulk-memory lowers llvm.memset/memcpy (`new Array<T>(n)`, `push` growth) to the
751
792
  # memory.fill/memory.copy instructions instead of libc calls the freestanding link lacks.
752
- "$CC" -Wno-override-module --target=wasm32-unknown-unknown -Oz -nostdlib -mbulk-memory \
753
- ${tls[@]+"${tls[@]}"} \
754
- -Wl,--no-entry -Wl,--export-all -Wl,--strip-all -Wl,--gc-sections \
755
- "${inputs[@]}" -o "$out" ;;
793
+ wasm_compile=(-Wno-override-module --target=wasm32-unknown-unknown -Oz -nostdlib -mbulk-memory)
794
+ if [ "$profile" = wasm ]; then
795
+ "$CC" "${wasm_compile[@]}" \
796
+ ${tls[@]+"${tls[@]}"} \
797
+ -Wl,--no-entry -Wl,--export-all -Wl,--strip-all -Wl,--gc-sections \
798
+ "${inputs[@]}" -o "$out"
799
+ else
800
+ # The export list. A module's exported functions are its `define` lines
801
+ # with external linkage and default visibility: the compiler gives
802
+ # everything else `internal` or `private`, and the other linkages are
803
+ # listed for IR it did not write. wasm-ld exports `memory` itself.
804
+ exports=()
805
+ runtime_linked=0
806
+ for i in "${inputs[@]}"; do
807
+ case "$i" in
808
+ */runtime-wasm.c|runtime-wasm.c) runtime_linked=1 ;;
809
+ *.ll)
810
+ while IFS= read -r name; do
811
+ exports+=("-Wl,--export=$name")
812
+ done < <(grep -E '^define ' "$i" \
813
+ | grep -Ev '^define (internal|private|linkonce|linkonce_odr|weak|weak_odr|available_externally|extern_weak|common|hidden|protected) ' \
814
+ | sed -nE 's/^[^@]*@"?([^"(]+)"?\(.*/\1/p') ;;
815
+ esac
816
+ done
817
+ if [ "$runtime_linked" = 1 ]; then
818
+ for name in nish_arena_mark nish_arena_release nish_alloc_array nish_reset_arena nish_free_arena; do
819
+ exports+=("-Wl,--export=$name")
820
+ done
821
+ fi
822
+ # shellcheck disable=SC2054 # -Wl,<flag> is one argument: the comma is the linker's
823
+ memory=(-Wl,-z,stack-size=65536)
824
+ if [ -n "$memory_mib" ]; then
825
+ bytes=$((memory_mib * 1048576))
826
+ memory+=("-Wl,--initial-memory=$bytes" "-Wl,--max-memory=$bytes")
827
+ fi
828
+ "$CC" "${wasm_compile[@]}" \
829
+ ${tls[@]+"${tls[@]}"} \
830
+ -Wl,--no-entry ${exports[@]+"${exports[@]}"} "${memory[@]}" -Wl,--strip-all -Wl,--gc-sections \
831
+ "${inputs[@]}" -o "$out"
832
+ # binaryen's size pass is optional: the module is complete without it.
833
+ if command -v wasm-opt >/dev/null 2>&1; then
834
+ wasm-opt -Oz --enable-bulk-memory "$out" -o "$out"
835
+ wasm_opt="wasm-opt -Oz"
836
+ else
837
+ wasm_opt="no wasm-opt"
838
+ fi
839
+ if [ -n "$inline_to" ]; then
840
+ # Decimal, one array literal: of the inline forms the smallest after
841
+ # compression (docs/wp50-wasm-host.md, Q10). od prints each byte as an
842
+ # unsigned decimal, and tr and sed join them with commas.
843
+ {
844
+ printf '// The bytes of %s, from scripts/build.sh --profile wasm-host --inline.\n' "${out##*/}"
845
+ printf 'export const bytes = new Uint8Array(['
846
+ od -An -v -tu1 "$out" | tr -s ' \n' ' ' | sed -E 's/^ //; s/ $//; s/ /,/g' | tr -d '\n'
847
+ printf ']);\n'
848
+ } > "$inline_to"
849
+ fi
850
+ fi ;;
756
851
  wasi)
757
852
  sysroot=${WASI_SYSROOT:-}
758
853
  for d in /usr/lib/wasi-sysroot /opt/wasi-sdk/share/wasi-sysroot /usr/share/wasi-sysroot; do
@@ -830,4 +925,4 @@ esac
830
925
  if [ "${dsym:-0}" = 1 ]; then "$("$CC" -print-prog-name=dsymutil)" "$out"; fi
831
926
 
832
927
  # `wc -c` pads with spaces on macOS; strip them so the number is clean.
833
- printf '%s: %s bytes (%s)\n' "$out" "$(wc -c < "$out" | tr -d ' ')" "$profile"
928
+ printf '%s: %s bytes (%s%s)\n' "$out" "$(wc -c < "$out" | tr -d ' ')" "$profile" "${wasm_opt:+, $wasm_opt}"
package/std/base64.ts CHANGED
@@ -2,7 +2,17 @@
2
2
  * `nish/base64` — standard base64 (RFC 4648 §4) with `=` padding, the encoding
3
3
  * kitty graphics frames are sent in.
4
4
  *
5
- * The alphabet is `A-Z a-z 0-9 + /`. Every three bytes become four characters,
5
+ * import { base64Encode, base64EncodeInto, base64EncodedLength } from "nish/base64";
6
+ *
7
+ * export const main = (): i32 => {
8
+ * const hi: u8[] = [72, 105]; // "Hi"
9
+ * const text: string = base64Encode(hi); // "SGk="
10
+ * const out: u8[] = new Array<u8>(base64EncodedLength(2) + 1);
11
+ * const end: i32 = base64EncodeInto(hi, 0, 2, out, 1); // out[1..5) is "SGk=", end is 5
12
+ * return end;
13
+ * };
14
+ *
15
+ * **The alphabet is `A-Z a-z 0-9 + /`.** Every three bytes become four characters,
6
16
  * and a final group of one or two bytes becomes two or three characters filled
7
17
  * out to four with `=` (RFC 4648 §4, the padding rules of §3.2), so the text is
8
18
  * always a multiple of four long.
@@ -13,15 +23,15 @@
13
23
  * can be encoded chunk by chunk straight into its output buffer.
14
24
  * - `base64Encode(bytes)` is the whole array as a string.
15
25
  *
16
- * This module only encodes: what it is for writes base64 and never reads it
26
+ * **This module only encodes**: what it is for writes base64 and never reads it
17
27
  * back. It is not the strict decoder of `nish/crypto/base64url`, which is a
18
28
  * different alphabet without padding and is written not to branch on the data;
19
29
  * what goes through here is pixels, not secrets, so a sextet becomes its
20
30
  * character by indexing the alphabet. The output is ASCII, so the string is
21
31
  * the same bytes natively and under Node.
22
32
  *
23
- * Private helpers share the importing program's flat symbol namespace
24
- * (`docs/wp26-stdlib.md` §3e), which is why each one carries the module's name.
33
+ * **Private helpers carry the module's name** because they share the
34
+ * importing program's flat symbol namespace (`docs/wp26-stdlib.md` §3e).
25
35
  */
26
36
 
27
37
  /** The 64 characters of RFC 4648 §4's Table 1, in value order. */
@@ -34,6 +44,10 @@ const BASE64_PAD: i32 = 61
34
44
  * The characters `n` bytes encode to: four for every three, a short final
35
45
  * group counted as a whole one. Panics when `n` is negative or the length
36
46
  * would pass 2^31 - 1, which `n` above 1,610,612,733 does.
47
+ *
48
+ * @param n How many bytes are to be encoded; zero answers zero.
49
+ * @returns The number of base64 characters, padding included, that `n` bytes
50
+ * encode to: always a multiple of four.
37
51
  */
38
52
  export const base64EncodedLength = (n: i32): i32 => {
39
53
  if (n < 0) {
@@ -55,6 +69,15 @@ const base64CharOf = (v: i32): u8 => toU8(BASE64_ALPHABET.charCodeAt(v))
55
69
  * `at + base64EncodedLength(len)`. Nothing is allocated. Panics when the source
56
70
  * range is not inside `src` or the encoding does not fit in `dst` from `at`;
57
71
  * both are checked before anything is written.
72
+ *
73
+ * @param src The bytes to encode from; only `src[off..off+len)` is read.
74
+ * @param off Where in `src` the bytes to encode start.
75
+ * @param len How many bytes of `src` to encode; zero writes nothing and
76
+ * answers `at`.
77
+ * @param dst The buffer the characters are written into, one ASCII byte per
78
+ * character; bytes outside the written range are left as they were.
79
+ * @param at Where in `dst` the first character goes.
80
+ * @returns The index in `dst` just past the last character written.
58
81
  */
59
82
  export const base64EncodeInto = (src: u8[], off: i32, len: i32, dst: u8[], at: i32): i32 => {
60
83
  // Refused before `toI32`, which saturates a longer length under
@@ -104,7 +127,13 @@ export const base64EncodeInto = (src: u8[], off: i32, len: i32, dst: u8[], at: i
104
127
  return o
105
128
  }
106
129
 
107
- /** `bytes` as padded base64. Panics past 1,610,612,733 bytes, whose encoding would not fit a string. */
130
+ /**
131
+ * `bytes` as padded base64. Panics past 1,610,612,733 bytes, whose encoding
132
+ * would not fit a string.
133
+ *
134
+ * @param bytes The bytes to encode, all of them; an empty array answers `""`.
135
+ * @returns A fresh string holding the padded encoding.
136
+ */
108
137
  export const base64Encode = (bytes: u8[]): string => {
109
138
  if (bytes.length > 2147483647) {
110
139
  panic("base64Encode: more than 2^31 - 1 bytes")