@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.
- package/INSTALL.md +6 -1
- package/bin/nish +0 -0
- package/docs/AI.md +9 -2
- package/package.json +1 -1
- package/runtime/nish.d.ts +4612 -430
- package/runtime/vite.d.ts +30 -0
- package/runtime/vite.mjs +150 -0
- package/scripts/build.sh +108 -13
- package/std/base64.ts +34 -5
- package/std/bytes.ts +256 -40
- package/std/cli-args.ts +296 -70
- package/std/cli-flags.ts +184 -19
- package/std/cli.ts +475 -54
- package/std/collections.ts +121 -23
- package/std/compress/checksum.ts +44 -14
- package/std/compress/deflate.ts +224 -111
- package/std/compress/gzip.ts +217 -53
- package/std/compress/zlib.ts +63 -7
- package/std/crypto/aes.ts +80 -9
- package/std/crypto/base64url.ts +22 -0
- package/std/crypto/bcrypt-batch.ts +88 -21
- package/std/crypto/bcrypt.ts +60 -0
- package/std/crypto/chacha20poly1305.ts +53 -0
- package/std/crypto/ct.ts +32 -13
- package/std/crypto/ed25519.ts +47 -0
- package/std/crypto/hkdf.ts +198 -13
- package/std/crypto/hmac.ts +235 -31
- package/std/crypto/jwt.ts +192 -63
- package/std/crypto/p256.ts +159 -16
- package/std/crypto/p384.ts +28 -3
- package/std/crypto/pki-roots.ts +32 -2
- package/std/crypto/pki.ts +81 -15
- package/std/crypto/rsa.ts +33 -3
- package/std/crypto/sha1.ts +3 -0
- package/std/crypto/sha256.ts +24 -1
- package/std/crypto/sha512.ts +99 -14
- package/std/crypto/x25519.ts +152 -14
- package/std/crypto/x509-random.ts +10 -0
- package/std/crypto/x509.ts +67 -7
- package/std/json-equal.ts +19 -0
- package/std/json-format.ts +112 -13
- package/std/json-number.ts +64 -14
- package/std/json-pointer.ts +23 -1
- package/std/json-tokenizer.ts +100 -29
- package/std/json-typed.ts +713 -66
- package/std/json-value.ts +400 -124
- package/std/json.ts +138 -10
- package/std/map.ts +25 -7
- package/std/math.ts +36 -4
- package/std/net/buffer-pool.ts +42 -11
- package/std/net/dns-config.ts +109 -23
- package/std/net/dns-message.ts +62 -7
- package/std/net/dns-resolver.ts +247 -53
- package/std/net/dns.ts +203 -35
- package/std/net/fetch.ts +151 -61
- package/std/net/handler.ts +278 -80
- package/std/net/hpack.ts +262 -52
- package/std/net/http-compress.ts +125 -15
- package/std/net/http-fields.ts +85 -9
- package/std/net/http1-client-codec.ts +100 -19
- package/std/net/http1-codec.ts +235 -27
- package/std/net/http1-server.ts +343 -85
- package/std/net/http2-frame.ts +272 -35
- package/std/net/http2-tls.ts +135 -34
- package/std/net/http2.ts +247 -106
- package/std/net/http3-frame.ts +154 -17
- package/std/net/http3-server.ts +246 -96
- package/std/net/http3.ts +307 -115
- package/std/net/outbound.ts +37 -3
- package/std/net/postgres-carrier.ts +144 -8
- package/std/net/postgres.ts +723 -103
- package/std/net/prefork.ts +164 -31
- package/std/net/qpack.ts +105 -18
- package/std/net/quic-conn-ack.ts +59 -12
- package/std/net/quic-conn-cid.ts +124 -27
- package/std/net/quic-conn-params.ts +70 -10
- package/std/net/quic-datagram.ts +43 -3
- package/std/net/quic-frame.ts +286 -12
- package/std/net/quic-listener.ts +101 -32
- package/std/net/quic-packet.ts +302 -21
- package/std/net/quic-recovery.ts +228 -44
- package/std/net/quic-stream.ts +353 -91
- package/std/net/quic.ts +489 -171
- package/std/net/router.ts +87 -40
- package/std/net/tls/client.ts +166 -51
- package/std/net/tls/codec.ts +413 -56
- package/std/net/tls/record-client.ts +110 -55
- package/std/net/tls/record-server.ts +159 -59
- package/std/net/tls/record.ts +109 -12
- package/std/net/tls/schedule.ts +235 -26
- package/std/net/tls/ticket.ts +159 -40
- package/std/net/tls/verify.ts +49 -3
- package/std/net/tls-tcp.ts +250 -45
- package/std/net/tls.ts +221 -59
- package/std/net/upstream.ts +81 -45
- package/std/net/url.ts +97 -13
- package/std/net/websocket.ts +122 -12
- package/std/net/webtransport.ts +220 -89
- package/std/pair.ts +10 -2
- package/std/random.ts +34 -9
- package/std/regex.ts +352 -198
- package/std/region.ts +70 -15
- package/std/secret.ts +52 -11
- package/std/sort.ts +17 -2
- package/std/sqlite.ts +256 -35
- package/std/sync/ring.ts +205 -35
- package/std/sync/shm.ts +194 -16
- package/std/term-io.ts +137 -64
- package/std/term-pixels.ts +144 -19
- package/std/term-probe.ts +66 -25
- package/std/term-rate.ts +128 -57
- package/std/term.ts +332 -121
- package/std/testing.ts +130 -32
- package/std/text.ts +73 -7
- package/std/threads.ts +229 -41
- package/std/yaml-json.ts +94 -82
- package/std/yaml-lines.ts +47 -15
- package/std/yaml-write.ts +24 -16
- 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;
|
package/runtime/vite.mjs
ADDED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
|
24
|
-
* (`docs/wp26-stdlib.md` §3e)
|
|
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
|
-
/**
|
|
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")
|