@amritk/nish-x86_64-linux 0.18.0 → 0.20.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 +43 -25
- package/bin/nish +0 -0
- package/package.json +2 -2
- package/runtime/nish.d.ts +55 -5
- package/runtime/nish.h +215 -18
- package/runtime/nish.mjs +16 -0
- package/runtime/runtime-aes.c +490 -0
- package/runtime/runtime-cross.c +366 -0
- package/runtime/runtime-host.c +7 -6
- package/runtime/runtime-net.c +93 -34
- package/runtime/runtime-os.c +213 -15
- package/runtime/runtime-parallel.c +590 -107
- package/runtime/runtime-simd.c +4 -4
- package/runtime/runtime-wasm.c +5 -3
- package/runtime/runtime.c +69 -13
- package/runtime/shim.mjs +71 -1
- package/scripts/build.sh +223 -22
- package/std/README.md +81 -51
- package/std/bytes.ts +163 -0
- package/std/crypto/LICENSE-libsecp256k1 +19 -0
- package/std/crypto/aes.ts +238 -76
- package/std/crypto/chacha20poly1305.ts +43 -9
- package/std/crypto/ed25519.ts +840 -0
- package/std/crypto/hkdf.ts +304 -12
- package/std/crypto/hmac.ts +245 -53
- package/std/crypto/p256.ts +932 -61
- package/std/crypto/x25519.ts +55 -37
- package/std/crypto/x509-random.ts +71 -0
- package/std/crypto/x509.ts +9 -9
- package/std/json-tokenizer.ts +1296 -0
- package/std/json-typed.ts +1014 -0
- package/std/json-value.ts +1163 -0
- package/std/json.ts +1120 -34
- package/std/net/buffer-pool.ts +151 -0
- package/std/net/hpack.ts +416 -112
- package/std/net/http-fields.ts +93 -0
- package/std/net/http1-server.ts +417 -55
- package/std/net/http1.ts +31 -6
- package/std/net/http2-tls.ts +112 -24
- package/std/net/http2.ts +333 -75
- package/std/net/http3-server.ts +211 -31
- package/std/net/http3.ts +187 -28
- package/std/net/prefork.ts +412 -0
- package/std/net/quic-listener.ts +88 -7
- package/std/net/quic-packet.ts +243 -82
- package/std/net/quic-recovery.ts +361 -30
- package/std/net/quic-stream.ts +424 -24
- package/std/net/quic.ts +519 -167
- package/std/net/router.ts +676 -0
- package/std/net/tls/codec.ts +426 -27
- package/std/net/tls/record-server.ts +478 -132
- package/std/net/tls/record.ts +133 -42
- package/std/net/tls/schedule.ts +464 -55
- package/std/net/tls/ticket.ts +610 -0
- package/std/net/tls-tcp.ts +196 -53
- package/std/net/tls.ts +1018 -260
- package/std/net/url.ts +571 -0
- package/std/random.ts +94 -0
- package/std/threads.ts +438 -14
package/runtime/runtime-simd.c
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
* bytes at a time (WP38 S1, docs/wp38-simd.md §3.1).
|
|
3
3
|
*
|
|
4
4
|
* A translation unit of its own for the reason runtime-host.c is one: each file
|
|
5
|
-
* carries its own measured `.text*` ceiling in tests/
|
|
6
|
-
* no room left when this arrived. Section GC means a program that
|
|
7
|
-
* kernel links none of this file, and scripts/build.sh pairs it
|
|
8
|
-
* like the other halves, so a link line still names one runtime.
|
|
5
|
+
* carries its own measured `.text*` ceiling in tests/runtime-budgets.js, and
|
|
6
|
+
* runtime.c had no room left when this arrived. Section GC means a program that
|
|
7
|
+
* never calls a kernel links none of this file, and scripts/build.sh pairs it
|
|
8
|
+
* with runtime.c like the other halves, so a link line still names one runtime.
|
|
9
9
|
*
|
|
10
10
|
* Every kernel has up to three paths. The scalar one runs anywhere. The base
|
|
11
11
|
* one uses the vectors every machine of the target has -- SSE2 on x86-64, NEON
|
package/runtime/runtime-wasm.c
CHANGED
|
@@ -133,7 +133,9 @@ void nish_panic_div(_Bool by_zero) { (void)by_zero; __builtin_trap(); }
|
|
|
133
133
|
void nish_panic_overflow(uint32_t op) { (void)op; __builtin_trap(); }
|
|
134
134
|
void nish_exit(int32_t code) { (void)code; __builtin_trap(); }
|
|
135
135
|
/* A panic's message (`panic`, `expect`, a failed range entry) has no stderr to
|
|
136
|
-
* go to here
|
|
137
|
-
*
|
|
138
|
-
*
|
|
136
|
+
* go to here, so `nish_panic` is the trap. An exported function with a ranged
|
|
137
|
+
* parameter checks it on entry (WP31 §9), so this is what lets such a module
|
|
138
|
+
* link; the loader throws a RangeError before the call reaches it. */
|
|
139
|
+
void nish_panic(const void *s) { (void)s; __builtin_trap(); }
|
|
140
|
+
/* Output has nowhere to go either. */
|
|
139
141
|
void nish_write(const void *s, int32_t fd, _Bool newline) { (void)s; (void)fd; (void)newline; }
|
package/runtime/runtime.c
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* both use `-flto`, and the cold paths that lost the inlining are cold.
|
|
19
19
|
*/
|
|
20
20
|
#define _POSIX_C_SOURCE 200809L
|
|
21
|
+
#include <errno.h>
|
|
21
22
|
#include <inttypes.h>
|
|
22
23
|
#include <math.h>
|
|
23
24
|
#include <stddef.h>
|
|
@@ -69,6 +70,11 @@ int __main_argc_argv(int argc, char **argv) { return nish_c_main(argc, argv); }
|
|
|
69
70
|
allocates from compiled code. The asserts below are the contract, checked
|
|
70
71
|
wherever this file is compiled. */
|
|
71
72
|
typedef struct nish_chunk { struct nish_chunk *next; uint64_t cap; } nish_chunk;
|
|
73
|
+
/* runtime-cross.c walks the chunks through a copy of this definition, which
|
|
74
|
+
asserts the same three facts. */
|
|
75
|
+
_Static_assert(sizeof(nish_chunk) == 16, "runtime-cross.c's nish_xchunk: { next, cap }, the bytes after it");
|
|
76
|
+
_Static_assert(offsetof(nish_chunk, next) == 0, "runtime-cross.c's nish_xchunk: next first");
|
|
77
|
+
_Static_assert(offsetof(nish_chunk, cap) == 8, "runtime-cross.c's nish_xchunk: cap second");
|
|
72
78
|
struct nish_arena { char *buf; uint64_t off; uint64_t cap; nish_chunk *chunks; };
|
|
73
79
|
/* One per thread under -DNISH_THREADS, one per process otherwise; the layout is
|
|
74
80
|
the same either way, which is why nothing else in this file has to know. */
|
|
@@ -79,12 +85,45 @@ _Static_assert(offsetof(struct nish_arena, off) == 8, "the inlined allocator bum
|
|
|
79
85
|
_Static_assert(offsetof(struct nish_arena, cap) == 16, "the inlined allocator reads field 2");
|
|
80
86
|
_Static_assert(offsetof(struct nish_arena, chunks) == 24, "arena layout is ABI");
|
|
81
87
|
|
|
82
|
-
/*
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
88
|
+
/* ---- Panics: the first one wins.
|
|
89
|
+
|
|
90
|
+
Every way a program fails -- a failed check, `panic`, out of memory -- calls
|
|
91
|
+
`nish_panic_claim` before it prints. Two tasks of a scope, or two workers of
|
|
92
|
+
a parallel map, can fail at once, and without the claim both messages would
|
|
93
|
+
reach stderr interleaved and both threads would race to the exit. The first
|
|
94
|
+
to claim prints its whole message and exits; any later one parks until
|
|
95
|
+
that exit ends the process, so exactly one message is printed and the exit
|
|
96
|
+
code is the first panic's. How each path exits is unchanged by the claim:
|
|
97
|
+
`panic`, `expect` and a failed range entry (`nish_panic`) call `exit(1)`, as
|
|
98
|
+
they did through `nish_exit`, so atexit handlers run and stdio is flushed;
|
|
99
|
+
a failed check and out of memory call `_exit(1)`, as they always have. Only
|
|
100
|
+
one thread can reach either exit, because the claim comes first. Only a
|
|
101
|
+
threads build (`-DNISH_THREADS`) can have two threads panic, so only it
|
|
102
|
+
pays for the claim; everywhere else it is an empty inline function. Under wasi with threads, the trap is the
|
|
103
|
+
defensive answer: no host there gives a parked thread a way to wait. */
|
|
104
|
+
#ifdef NISH_THREADS
|
|
105
|
+
static int nish_panicked;
|
|
106
|
+
void nish_panic_claim(void) {
|
|
107
|
+
if (__atomic_exchange_n(&nish_panicked, 1, __ATOMIC_ACQ_REL)) {
|
|
108
|
+
#if defined(__wasi__)
|
|
109
|
+
__builtin_trap();
|
|
110
|
+
#else
|
|
111
|
+
for (;;) pause();
|
|
112
|
+
#endif
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
#else
|
|
116
|
+
static inline void nish_panic_claim(void) {}
|
|
117
|
+
#endif
|
|
118
|
+
|
|
119
|
+
/* The message goes out through `nish_write_all`, below, which finishes a
|
|
120
|
+
short write and retries one a signal interrupts (docs/security/runtime.md,
|
|
121
|
+
RT-11); it is unbuffered, so the message leaves before `_exit`. It used
|
|
122
|
+
`fputs`, whose libc loop finishes a short write but gives up on `EINTR`. */
|
|
123
|
+
static void nish_write_all(int fd, const char *p, uint64_t n);
|
|
86
124
|
static NISH_COLD void nish_die(const char *msg) {
|
|
87
|
-
|
|
125
|
+
nish_panic_claim();
|
|
126
|
+
nish_write_all(2, msg, strlen(msg));
|
|
88
127
|
_exit(1);
|
|
89
128
|
}
|
|
90
129
|
/* A function rather than a macro around `nish_die`: seven call sites each
|
|
@@ -283,19 +322,34 @@ int64_t nish_str_index_of(const nish_str *s, const nish_str *sub) { return nish_
|
|
|
283
322
|
does the same. So the bytes are written until all of them are out, or until
|
|
284
323
|
a write fails (docs/security/runtime.md, RT-11). A failure still ends the
|
|
285
324
|
output, `EAGAIN` on a non-blocking stdout included, since spinning on it
|
|
286
|
-
would burn a core. `EINTR` is
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
325
|
+
would burn a core. `EINTR` is retried, the newline's included: the only
|
|
326
|
+
handler this runtime installs (runtime-host.c) has `SA_RESTART`, but a C
|
|
327
|
+
host may install one without it. `panic`'s message, `expect`'s, and the
|
|
328
|
+
arithmetic and out-of-memory panics (`nish_die`) are written here. The
|
|
329
|
+
index and slice panics and the file and entropy failures use `dprintf`, and
|
|
330
|
+
runtime-parallel.c's out of memory one `write`: neither retries `EINTR`. */
|
|
331
|
+
static void nish_write_all(int fd, const char *p, uint64_t n) {
|
|
292
332
|
uint64_t done = 0;
|
|
293
333
|
ssize_t k;
|
|
294
|
-
while (done <
|
|
295
|
-
|
|
334
|
+
while (done < n) {
|
|
335
|
+
k = write(fd, p + done, n - done);
|
|
336
|
+
if (k > 0) done += k;
|
|
337
|
+
else if (k == 0 || errno != EINTR) break;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
void nish_write(const nish_str *s, int32_t fd, _Bool newline) {
|
|
341
|
+
nish_write_all(fd, s->data, s->len);
|
|
342
|
+
if (newline) nish_write_all(fd, "\n", 1);
|
|
296
343
|
}
|
|
297
344
|
/* console.log(s) */
|
|
298
345
|
void nish_print(const nish_str *s) { nish_write(s, 1, 1); }
|
|
346
|
+
/* `panic(message)`, `r.expect(message)` and a failed range entry: the message
|
|
347
|
+
and a newline on stderr, then `exit(1)`, the first panic only. */
|
|
348
|
+
__attribute__((noreturn)) void nish_panic(const nish_str *s) {
|
|
349
|
+
nish_panic_claim();
|
|
350
|
+
nish_write(s, 2, 1);
|
|
351
|
+
exit(1);
|
|
352
|
+
}
|
|
299
353
|
|
|
300
354
|
/* Decimal digits of `u`, with a '-' in front when `neg`. 20 digits is the
|
|
301
355
|
widest a uint64_t can be, plus the sign. */
|
|
@@ -1245,6 +1299,7 @@ __attribute__((noinline)) void nish_wipe(nish_array *bytes) {
|
|
|
1245
1299
|
}
|
|
1246
1300
|
|
|
1247
1301
|
void nish_panic_index(uint64_t idx, uint64_t len) {
|
|
1302
|
+
nish_panic_claim();
|
|
1248
1303
|
dprintf(2, "index out of range: %" PRIu64 " >= %" PRIu64 "\n", idx, len);
|
|
1249
1304
|
_exit(1);
|
|
1250
1305
|
}
|
|
@@ -1258,6 +1313,7 @@ void nish_panic_index(uint64_t idx, uint64_t len) {
|
|
|
1258
1313
|
a reader who wrote `s.slice(i - 1)` wants to be told `-1` rather than
|
|
1259
1314
|
18446744073709551615. The length cannot be negative either way. */
|
|
1260
1315
|
void nish_panic_slice(int64_t start, int64_t end, int64_t len) {
|
|
1316
|
+
nish_panic_claim();
|
|
1261
1317
|
dprintf(2, "slice out of range: [%" PRId64 ", %" PRId64 ") of length %" PRId64 "\n", start, end, len);
|
|
1262
1318
|
_exit(1);
|
|
1263
1319
|
}
|
package/runtime/shim.mjs
CHANGED
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
* `indexOf` answers with one. `slice` cuts on byte offsets too and panics
|
|
23
23
|
* on a range the string does not contain, where JavaScript would clamp and
|
|
24
24
|
* read a negative offset from the end. `String.fromCharCode` builds a
|
|
25
|
-
* one-byte string from the low 8 bits.
|
|
25
|
+
* one-byte string from the low 8 bits, and `String.fromBytes` one such
|
|
26
|
+
* string per byte of its range.
|
|
26
27
|
* - `a[i]` is bounds-checked: out of range prints
|
|
27
28
|
* `index out of range: <i> >= <len>` to stderr and exits 1, and so is
|
|
28
29
|
* `a.pop()` on an empty array, which has no `undefined` to return.
|
|
@@ -347,6 +348,20 @@ export function fromCharCode(code) {
|
|
|
347
348
|
return Buffer.from([toIndex(code) & 0xff]).toString("latin1");
|
|
348
349
|
}
|
|
349
350
|
|
|
351
|
+
/**
|
|
352
|
+
* `String.fromBytes(bytes, start, end)`: the bytes of `[start, end)`, one
|
|
353
|
+
* code unit each as `fromCharCode` builds them, checked as `s.slice`'s range
|
|
354
|
+
* is and failing with its panic.
|
|
355
|
+
*/
|
|
356
|
+
export function fromBytes(bytes, start, end) {
|
|
357
|
+
const from = toIndex(start);
|
|
358
|
+
const to = toIndex(end);
|
|
359
|
+
if (!(from >= 0 && from <= to && to <= bytes.length)) panicSlice(from, to, bytes.length);
|
|
360
|
+
const out = Buffer.allocUnsafe(to - from);
|
|
361
|
+
for (let i = from; i < to; i++) out[i - from] = bytes[i];
|
|
362
|
+
return out.toString("latin1");
|
|
363
|
+
}
|
|
364
|
+
|
|
350
365
|
/** `console.log(x)`: `String(x)` (no `n` suffix for i64) plus a newline, written synchronously. */
|
|
351
366
|
export function log(x) {
|
|
352
367
|
fs.writeSync(1, `${String(x)}\n`);
|
|
@@ -726,6 +741,61 @@ export function spawnSyncTo(argv, out, err) {
|
|
|
726
741
|
return spawnImpl(argv, out, err);
|
|
727
742
|
}
|
|
728
743
|
|
|
744
|
+
/**
|
|
745
|
+
* `spawn(argv)` and `waitPid(pid, flags)` (WP34 N12) have no faithful reading
|
|
746
|
+
* under Node, for `signalFd`'s reason: Node reaps its children itself, on the
|
|
747
|
+
* event loop, and hands their status to an `exit` listener, so no synchronous
|
|
748
|
+
* function here can wait for one, and a child Node started is never there for
|
|
749
|
+
* a wait-less poll to find. Each throws, naming itself.
|
|
750
|
+
*/
|
|
751
|
+
function noProcessReading(name) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
`\`${name}\` has no synchronous reading under Node: a child is reaped by the event loop, which a synchronous wait never returns to (docs/wp33-round-trip.md)`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
export function spawn() {
|
|
758
|
+
return noProcessReading("spawn");
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
export function waitPid() {
|
|
762
|
+
return noProcessReading("waitPid");
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
/**
|
|
766
|
+
* `kill(pid, sig)`: 0, or a negative errno, as `runtime-os.c`'s `nish_kill`
|
|
767
|
+
* answers. Only 0, 2, 9 and 15 are sent and a pid of 0 or below is refused,
|
|
768
|
+
* both -22, before Node is asked; Node's error then names its errno, which
|
|
769
|
+
* `os.constants.errno` turns back into the number (-3 for no such process).
|
|
770
|
+
*/
|
|
771
|
+
export function kill(pid, sig) {
|
|
772
|
+
if (pid <= 0 || !(sig === 0 || sig === 2 || sig === 9 || sig === 15)) return -22;
|
|
773
|
+
try {
|
|
774
|
+
process.kill(pid, sig);
|
|
775
|
+
return 0;
|
|
776
|
+
} catch (e) {
|
|
777
|
+
return -(os.constants.errno[e.code] ?? 22);
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* `availableParallelism()`: Node's own count, which reads the affinity mask as
|
|
783
|
+
* the native one does, lowered by a positive `NISH_NUM_THREADS` the way the
|
|
784
|
+
* native count is. The native count also reads a cgroup v2 CPU quota, which
|
|
785
|
+
* Node does not, so inside a container with a quota the two may differ; both
|
|
786
|
+
* are at least 1.
|
|
787
|
+
*/
|
|
788
|
+
export function availableParallelism() {
|
|
789
|
+
const n = os.availableParallelism();
|
|
790
|
+
const want = /^[0-9]+$/.test(process.env.NISH_NUM_THREADS ?? "") ? Number(process.env.NISH_NUM_THREADS) : 0;
|
|
791
|
+
return want > 0 && want < n ? want : n;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/** `totalMemory()`: `os.totalmem()`, as a BigInt because an `i64` is held as one here. */
|
|
795
|
+
export function totalMemory() {
|
|
796
|
+
return BigInt(os.totalmem());
|
|
797
|
+
}
|
|
798
|
+
|
|
729
799
|
/**
|
|
730
800
|
* `monotonicNanos()`: `process.hrtime.bigint()`, a monotonic clock in
|
|
731
801
|
* nanoseconds (`CLOCK_MONOTONIC` on every platform that has it, which is the
|
package/scripts/build.sh
CHANGED
|
@@ -3,14 +3,16 @@
|
|
|
3
3
|
#
|
|
4
4
|
# scripts/build.sh <module.ll> [more .ll/.c files...] -o <out> [--profile debug|speed|size|wasm|wasi|napi]
|
|
5
5
|
#
|
|
6
|
-
# The C runtime is
|
|
6
|
+
# The C runtime is eight 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
|
|
8
8
|
# system calls (files, directories, subprocesses, the environment, the clock),
|
|
9
9
|
# <dir>/runtime-parallel.c, the half that divides a range of work across
|
|
10
|
-
# threads, <dir>/runtime-
|
|
11
|
-
#
|
|
12
|
-
# <dir>/runtime-
|
|
13
|
-
#
|
|
10
|
+
# threads, <dir>/runtime-cross.c, the copies that carry a string or an array
|
|
11
|
+
# from one thread's arena to another's, <dir>/runtime-host.c, the wall clock, entropy, file times and
|
|
12
|
+
# signals, <dir>/runtime-net.c, the sockets of `nish:net`,
|
|
13
|
+
# <dir>/runtime-simd.c, the byte searches that read a vector at a time, and
|
|
14
|
+
# <dir>/runtime-aes.c, AES-GCM on the CPU's AES instructions. Each of those
|
|
15
|
+
# files says why they are compiled and measured apart.
|
|
14
16
|
#
|
|
15
17
|
# Profiles:
|
|
16
18
|
# debug clang defaults: no optimisation, symbols kept. The "before" number.
|
|
@@ -57,6 +59,29 @@
|
|
|
57
59
|
# put in the .ll (line table, variables) reaches the binary. `nish
|
|
58
60
|
# --link -g` passes it through automatically.
|
|
59
61
|
#
|
|
62
|
+
# CPU level (WP38 S2): `--cpu x86-64-v3` compiles and links every input --
|
|
63
|
+
# the module, the runtime and any other C -- with -march=x86-64-v3
|
|
64
|
+
# -ffp-contract=off, so the code generator may use AVX2, BMI2 and the other
|
|
65
|
+
# v3 instructions and still forms no fused multiply-add the IR did not write.
|
|
66
|
+
# It is the one level taken; any other value is refused with exit 2, and so are
|
|
67
|
+
# the wasm and wasi profiles, which are not x86-64. A binary built with it
|
|
68
|
+
# stops with SIGILL on a CPU without AVX2. `nish --link --cpu x86-64-v3`
|
|
69
|
+
# passes it through.
|
|
70
|
+
#
|
|
71
|
+
# The runtime compiled once (the debug, speed and size profiles only; `nish
|
|
72
|
+
# --link` and `nish run` use these through their runtime cache, src/compile.ts):
|
|
73
|
+
# --runtime-objects <dir> compile the runtime.c input and its seven siblings,
|
|
74
|
+
# one $CC -c each and all at once, into
|
|
75
|
+
# <dir>/<unit>.o with the profile's compile flags,
|
|
76
|
+
# and link nothing. speed and size store LTO bitcode,
|
|
77
|
+
# so the link can still inline the runtime into the
|
|
78
|
+
# program. No -o; prints nothing on success.
|
|
79
|
+
# --runtime-from <dir> link against <dir>/<unit>.o in place of a
|
|
80
|
+
# runtime.c input, after compiling every other input
|
|
81
|
+
# with its own $CC -c, all at once. The link line is
|
|
82
|
+
# the one the profile always uses.
|
|
83
|
+
# Both refuse the wasm, wasi and napi profiles and --pgo-*, with exit 2.
|
|
84
|
+
#
|
|
60
85
|
# Works on Linux (clang + lld preferred, GNU ld tolerated) and macOS (Apple ld64
|
|
61
86
|
# or Homebrew llvm). Set CC to pick a compiler (default: clang on PATH).
|
|
62
87
|
set -euo pipefail
|
|
@@ -67,28 +92,74 @@ inputs=()
|
|
|
67
92
|
pgo=() # -fprofile-generate / -fprofile-use=<file>
|
|
68
93
|
debug=0 # -g: keep DWARF (nish -g emits it in the IR; runtime.c gets it here)
|
|
69
94
|
threads=0 # --threads: -DNISH_THREADS, the thread-local arena (WP20 T0)
|
|
95
|
+
objects_to="" # --runtime-objects <dir>: compile the runtime alone, into <dir>
|
|
96
|
+
objects_from="" # --runtime-from <dir>: link against the runtime compiled there
|
|
97
|
+
cpu="" # --cpu x86-64-v3: the one CPU level above the baseline (WP38 S2)
|
|
70
98
|
while [ $# -gt 0 ]; do
|
|
71
99
|
case "$1" in
|
|
72
100
|
-o) out="$2"; shift 2 ;;
|
|
73
101
|
--profile) profile="$2"; shift 2 ;;
|
|
74
102
|
-g) debug=1; shift ;;
|
|
75
103
|
--threads) threads=1; shift ;;
|
|
104
|
+
--cpu)
|
|
105
|
+
[ -n "${2-}" ] || { echo "error: --cpu needs a level; the one taken is x86-64-v3" >&2; exit 2; }
|
|
106
|
+
cpu="$2"; shift 2 ;;
|
|
107
|
+
--runtime-objects) objects_to="$2"; shift 2 ;;
|
|
108
|
+
--runtime-from) objects_from="$2"; shift 2 ;;
|
|
76
109
|
--pgo-generate) pgo=(-fprofile-generate); shift ;;
|
|
77
110
|
--pgo-use)
|
|
78
111
|
[ -f "$2" ] || { echo "error: --pgo-use: profile '$2' not found (run the instrumented binary, then llvm-profdata merge)" >&2; exit 2; }
|
|
79
112
|
pgo=("-fprofile-use=$2"); shift 2 ;;
|
|
80
|
-
-h|--help) sed -n '2,
|
|
113
|
+
-h|--help) sed -n '2,85p' "$0"; exit 0 ;;
|
|
81
114
|
*) inputs+=("$1"); shift ;;
|
|
82
115
|
esac
|
|
83
116
|
done
|
|
84
117
|
[ ${#inputs[@]} -gt 0 ] || { echo "error: no input files" >&2; exit 2; }
|
|
85
|
-
[ -n "$
|
|
118
|
+
if [ -n "$cpu" ]; then
|
|
119
|
+
[ "$cpu" = x86-64-v3 ] || { echo "error: --cpu takes x86-64-v3, not '$cpu'" >&2; exit 2; }
|
|
120
|
+
case "$profile" in
|
|
121
|
+
wasm|wasi) echo "error: --cpu x86-64-v3 is an x86-64 level, so the $profile profile does not take it" >&2; exit 2 ;;
|
|
122
|
+
esac
|
|
123
|
+
fi
|
|
124
|
+
# wasm-ld exports a wasm module's linear memory as `memory`, so under the wasm
|
|
125
|
+
# profile's `--export-all` a function of that name makes a module Node refuses
|
|
126
|
+
# to compile ("Duplicate export name 'memory'", #643). The wasi module exports
|
|
127
|
+
# only `memory` and `_start`, but the name is reserved there too, so a program
|
|
128
|
+
# that links for wasi also links for wasm. The compiler refuses one (NL3038)
|
|
129
|
+
# when it knows the build is wasm, but IR compiled for the host with `-o` alone
|
|
130
|
+
# reaches these profiles without telling it, so the IR is read here too.
|
|
131
|
+
case "$profile" in
|
|
132
|
+
wasm|wasi)
|
|
133
|
+
for i in ${inputs[@]+"${inputs[@]}"}; do
|
|
134
|
+
case "$i" in
|
|
135
|
+
*.ll)
|
|
136
|
+
# An `internal` or `private` definition is not exported, so it is left alone.
|
|
137
|
+
if grep -E '^define [^@]*@memory\(' "$i" | grep -Evq '^define (internal|private) '; then
|
|
138
|
+
echo "error: $i defines a function named \`memory\`, the name the $profile profile's module exports its linear memory under; rename the function" >&2
|
|
139
|
+
exit 2
|
|
140
|
+
fi ;;
|
|
141
|
+
esac
|
|
142
|
+
done ;;
|
|
143
|
+
esac
|
|
144
|
+
if [ -n "$objects_to" ] || [ -n "$objects_from" ]; then
|
|
145
|
+
# A cached runtime is the profile's ordinary compile, so it exists only for
|
|
146
|
+
# the profiles that compile the runtime with nothing but these flags.
|
|
147
|
+
case "$profile" in
|
|
148
|
+
debug|speed|size) ;;
|
|
149
|
+
*) echo "error: --runtime-objects and --runtime-from take the debug, speed and size profiles, not '$profile'" >&2; exit 2 ;;
|
|
150
|
+
esac
|
|
151
|
+
[ ${#pgo[@]} -eq 0 ] || { echo "error: --runtime-objects and --runtime-from do not take --pgo-*" >&2; exit 2; }
|
|
152
|
+
[ -z "$objects_to" ] || [ -z "$objects_from" ] || { echo "error: --runtime-objects and --runtime-from are two different builds" >&2; exit 2; }
|
|
153
|
+
fi
|
|
154
|
+
[ -n "$out" ] || [ -n "$objects_to" ] || { echo "error: -o <out> is required" >&2; exit 2; }
|
|
86
155
|
|
|
87
|
-
# The runtime is
|
|
156
|
+
# The runtime is eight translation units, and a caller names one: whoever passes
|
|
88
157
|
# <dir>/runtime.c gets <dir>/runtime-os.c, <dir>/runtime-parallel.c,
|
|
89
|
-
# <dir>/runtime-host.c, <dir>/runtime-net.c
|
|
90
|
-
# beside it, on every profile, wasi included:
|
|
91
|
-
# path there unless the build adds -msimd128
|
|
158
|
+
# <dir>/runtime-cross.c, <dir>/runtime-host.c, <dir>/runtime-net.c, <dir>/runtime-simd.c and
|
|
159
|
+
# <dir>/runtime-aes.c compiled beside it, on every profile, wasi included:
|
|
160
|
+
# runtime-simd.c takes its scalar path there unless the build adds -msimd128,
|
|
161
|
+
# and runtime-aes.c compiles to an answer of 0 and stubs that nothing calls,
|
|
162
|
+
# because the compiler emits no call to its kernels for wasm32. They were one file until the
|
|
92
163
|
# operating-system half was split out for its own size budget, and the parallel
|
|
93
164
|
# half followed for the same reason (each file's header comment says why), and
|
|
94
165
|
# a link line is where those splits
|
|
@@ -98,10 +169,11 @@ done
|
|
|
98
169
|
# here keeps every one of those correct, and keeps "the runtime" one thing to
|
|
99
170
|
# name from the outside. A caller that names one itself is left alone, because
|
|
100
171
|
# naming one object twice is a duplicate-symbol error.
|
|
172
|
+
halves="runtime-os.c runtime-parallel.c runtime-cross.c runtime-host.c runtime-net.c runtime-simd.c runtime-aes.c"
|
|
101
173
|
for i in ${inputs[@]+"${inputs[@]}"}; do
|
|
102
174
|
case "$i" in
|
|
103
175
|
*/runtime.c|runtime.c)
|
|
104
|
-
for half in
|
|
176
|
+
for half in $halves; do
|
|
105
177
|
side="${i%runtime.c}$half"
|
|
106
178
|
have=0
|
|
107
179
|
for j in "${inputs[@]}"; do
|
|
@@ -177,12 +249,130 @@ if [ "$debug" = 1 ]; then common+=(-g); strip_flag=(); fi
|
|
|
177
249
|
# command lines, which have no threads to link against, and where
|
|
178
250
|
# runtime-parallel.c compiles to its sequential fallback because it tests
|
|
179
251
|
# __wasi__ and __wasm__ as well as the macro.
|
|
252
|
+
# --cpu: every unit at the one level, so the module, the runtime and the link
|
|
253
|
+
# (LTO generates code there) agree. -ffp-contract=off keeps the meaning fixed:
|
|
254
|
+
# v3 has FMA, and contracting a*b+c into one rounding would print a different
|
|
255
|
+
# float than the baseline build (docs/LANGUAGE.md, "The CPU level").
|
|
256
|
+
if [ -n "$cpu" ]; then common+=(-march=x86-64-v3 -ffp-contract=off); fi
|
|
257
|
+
|
|
180
258
|
tls=()
|
|
181
259
|
if [ "$threads" = 1 ]; then
|
|
182
260
|
common+=(-DNISH_THREADS=1 -ftls-model=initial-exec -pthread)
|
|
183
261
|
tls=(-DNISH_THREADS=1 -ftls-model=initial-exec)
|
|
184
262
|
fi
|
|
185
263
|
|
|
264
|
+
# What the speed and size profiles compile every input with, beside `common`.
|
|
265
|
+
# One list per profile, because the runtime cache below compiles with exactly
|
|
266
|
+
# what the one-command link does, and two copies of a flag set drift.
|
|
267
|
+
optimise=()
|
|
268
|
+
case "$profile" in
|
|
269
|
+
speed)
|
|
270
|
+
optimise=(-O3 -flto -DNDEBUG ${pgo[@]+"${pgo[@]}"}
|
|
271
|
+
-ffunction-sections -fdata-sections -fomit-frame-pointer
|
|
272
|
+
-fno-asynchronous-unwind-tables -fno-unwind-tables ${elf[@]+"${elf[@]}"}) ;;
|
|
273
|
+
size)
|
|
274
|
+
optimise=(-Oz -flto -DNDEBUG ${pgo[@]+"${pgo[@]}"}
|
|
275
|
+
-ffunction-sections -fdata-sections -fomit-frame-pointer
|
|
276
|
+
-fno-asynchronous-unwind-tables -fno-unwind-tables ${elf[@]+"${elf[@]}"}
|
|
277
|
+
-fno-stack-protector -fvisibility=hidden) ;;
|
|
278
|
+
esac
|
|
279
|
+
|
|
280
|
+
# --runtime-objects and --runtime-from: the one-command link split in two, with
|
|
281
|
+
# every compile it held run at once. A `$CC -c` takes `common` and `optimise`,
|
|
282
|
+
# which is what the one command compiles each input with -- less
|
|
283
|
+
# -fuse-ld=lld, which only names the linker -- so a runtime unit compiles to the
|
|
284
|
+
# same object either way: native code for debug, -flto bitcode for speed and
|
|
285
|
+
# size, which the link still optimises together with the program. The link
|
|
286
|
+
# takes the same line the profile always uses, with objects for sources.
|
|
287
|
+
if [ -n "$objects_to" ] || [ -n "$objects_from" ]; then
|
|
288
|
+
compile=()
|
|
289
|
+
for flag in "${common[@]}"; do
|
|
290
|
+
case "$flag" in -fuse-ld=*) ;; *) compile+=("$flag") ;; esac
|
|
291
|
+
done
|
|
292
|
+
compile+=(${optimise[@]+"${optimise[@]}"})
|
|
293
|
+
# Waits for every job, not just the last, and fails when any of them did.
|
|
294
|
+
# bash 3.2 has no `wait -n`, so the jobs are waited for in order.
|
|
295
|
+
pids=()
|
|
296
|
+
join_jobs() {
|
|
297
|
+
local failed=0 pid
|
|
298
|
+
for pid in ${pids[@]+"${pids[@]}"}; do
|
|
299
|
+
wait "$pid" || failed=1
|
|
300
|
+
done
|
|
301
|
+
pids=()
|
|
302
|
+
return "$failed"
|
|
303
|
+
}
|
|
304
|
+
# The compiles are this script's children, so a signal that stops it stops
|
|
305
|
+
# them too, before anything they write into is removed. A finished script
|
|
306
|
+
# has waited for every one of them, so there is nothing left to stop.
|
|
307
|
+
stop_jobs() {
|
|
308
|
+
if [ ${#pids[@]} -gt 0 ]; then
|
|
309
|
+
kill "${pids[@]}" 2>/dev/null || true
|
|
310
|
+
wait "${pids[@]}" 2>/dev/null || true
|
|
311
|
+
pids=()
|
|
312
|
+
fi
|
|
313
|
+
}
|
|
314
|
+
work=""
|
|
315
|
+
trap 'stop_jobs; [ -z "$work" ] || rm -rf -- "$work"' EXIT
|
|
316
|
+
trap 'exit 130' INT
|
|
317
|
+
trap 'exit 143' TERM
|
|
318
|
+
fi
|
|
319
|
+
|
|
320
|
+
if [ -n "$objects_to" ]; then
|
|
321
|
+
runtime=""
|
|
322
|
+
for i in "${inputs[@]}"; do
|
|
323
|
+
case "$i" in
|
|
324
|
+
*/runtime.c|runtime.c) runtime="$i" ;;
|
|
325
|
+
*/runtime-*.c|runtime-*.c) ;; # its siblings, added above
|
|
326
|
+
*) echo "error: --runtime-objects compiles the runtime alone, not $i" >&2; exit 2 ;;
|
|
327
|
+
esac
|
|
328
|
+
done
|
|
329
|
+
[ -n "$runtime" ] || { echo "error: --runtime-objects needs runtime/runtime.c" >&2; exit 2; }
|
|
330
|
+
mkdir -p -- "$objects_to"
|
|
331
|
+
objects_to=$(cd -- "$objects_to" && pwd)
|
|
332
|
+
# Each unit is compiled from inside its own directory, by its bare name, so
|
|
333
|
+
# an object holds no trace of where the compile was started: -g's DWARF names
|
|
334
|
+
# the file `runtime.c` in the runtime's own directory, whichever directory
|
|
335
|
+
# the first link that compiled it was run in.
|
|
336
|
+
runtime_dir=$(dirname -- "$runtime")
|
|
337
|
+
# A CC named by a relative path is named from here, not from there.
|
|
338
|
+
case "$CC" in /*) ;; */*) CC="$(pwd)/$CC" ;; esac
|
|
339
|
+
for unit in runtime.c $halves; do
|
|
340
|
+
(cd -- "$runtime_dir" && exec "$CC" "${compile[@]}" -c "$unit" -o "$objects_to/${unit%.c}.o") &
|
|
341
|
+
pids+=("$!")
|
|
342
|
+
done
|
|
343
|
+
join_jobs || { echo "error: the runtime did not compile" >&2; exit 1; }
|
|
344
|
+
exit 0
|
|
345
|
+
fi
|
|
346
|
+
|
|
347
|
+
if [ -n "$objects_from" ]; then
|
|
348
|
+
for unit in runtime.c $halves; do
|
|
349
|
+
[ -f "$objects_from/${unit%.c}.o" ] || { echo "error: --runtime-from: $objects_from/${unit%.c}.o is missing" >&2; exit 2; }
|
|
350
|
+
done
|
|
351
|
+
# The program's own objects are this link's alone, so they go in a private
|
|
352
|
+
# directory of its own and go with it.
|
|
353
|
+
work=$(mktemp -d "${TMPDIR:-/tmp}/nish-link.XXXXXX")
|
|
354
|
+
objects=()
|
|
355
|
+
n=0
|
|
356
|
+
for i in "${inputs[@]}"; do
|
|
357
|
+
case "$i" in */runtime.c|runtime.c) echo "error: --runtime-from links the runtime in $objects_from, so $i is not an input" >&2; exit 2 ;; esac
|
|
358
|
+
n=$((n + 1))
|
|
359
|
+
"$CC" "${compile[@]}" -c "$i" -o "$work/$n.o" &
|
|
360
|
+
pids+=("$!")
|
|
361
|
+
objects+=("$work/$n.o")
|
|
362
|
+
done
|
|
363
|
+
join_jobs || { echo "error: a module did not compile" >&2; exit 1; }
|
|
364
|
+
# In the order the one-command link names them: the inputs, then runtime.c
|
|
365
|
+
# and its siblings.
|
|
366
|
+
for unit in runtime.c $halves; do
|
|
367
|
+
objects+=("$objects_from/${unit%.c}.o")
|
|
368
|
+
done
|
|
369
|
+
inputs=("${objects[@]}")
|
|
370
|
+
# clang runs dsymutil itself when one command compiles and links with -g on
|
|
371
|
+
# macOS; a link of objects does not, so it is run here, while the program's
|
|
372
|
+
# objects the debug map points at are still there.
|
|
373
|
+
if [ "$debug" = 1 ] && [ "$(uname -s)" = Darwin ]; then dsym=1; else dsym=0; fi
|
|
374
|
+
fi
|
|
375
|
+
|
|
186
376
|
# The ${arr[@]+"${arr[@]}"} spelling below is not a style tic: macOS ships bash
|
|
187
377
|
# 3.2 (Apple will not ship GPLv3), where expanding an empty array as "${arr[@]}"
|
|
188
378
|
# under `set -u` is a fatal "unbound variable" -- bash 4.4 made it legal, which is
|
|
@@ -191,16 +381,8 @@ fi
|
|
|
191
381
|
case "$profile" in
|
|
192
382
|
debug)
|
|
193
383
|
"$CC" "${common[@]}" "${inputs[@]}" -lm -o "$out" ;;
|
|
194
|
-
speed)
|
|
195
|
-
"$CC" "${common[@]}"
|
|
196
|
-
-ffunction-sections -fdata-sections -fomit-frame-pointer \
|
|
197
|
-
-fno-asynchronous-unwind-tables -fno-unwind-tables ${elf[@]+"${elf[@]}"} \
|
|
198
|
-
"${gc[@]}" ${strip_flag[@]+"${strip_flag[@]}"} "${inputs[@]}" -lm -o "$out" ;;
|
|
199
|
-
size)
|
|
200
|
-
"$CC" "${common[@]}" -Oz -flto -DNDEBUG ${pgo[@]+"${pgo[@]}"} \
|
|
201
|
-
-ffunction-sections -fdata-sections -fomit-frame-pointer \
|
|
202
|
-
-fno-asynchronous-unwind-tables -fno-unwind-tables ${elf[@]+"${elf[@]}"} \
|
|
203
|
-
-fno-stack-protector -fvisibility=hidden \
|
|
384
|
+
speed|size)
|
|
385
|
+
"$CC" "${common[@]}" "${optimise[@]}" \
|
|
204
386
|
"${gc[@]}" ${strip_flag[@]+"${strip_flag[@]}"} "${inputs[@]}" -lm -o "$out" ;;
|
|
205
387
|
wasm)
|
|
206
388
|
# clang resolves wasm-ld next to its own binary first, then on PATH; ask it
|
|
@@ -210,6 +392,23 @@ case "$profile" in
|
|
|
210
392
|
echo "error: the wasm profile needs wasm-ld (install lld; on macOS: brew install llvm@18)" >&2
|
|
211
393
|
exit 2
|
|
212
394
|
fi
|
|
395
|
+
# IR compiled for the host rather than with `--target wasm32` calls std/crypto/aes.ts's
|
|
396
|
+
# AES kernels behind `nish_aes_hw` (src/kernels.ts). runtime-aes.c's wasm32 build
|
|
397
|
+
# answers 0 there, so the bitsliced code runs, and it is added only for such IR:
|
|
398
|
+
# every other module links the bytes it always did.
|
|
399
|
+
# A caller that names runtime-aes.c itself is left alone, as `halves` leaves it:
|
|
400
|
+
# one object named twice is a duplicate-symbol error.
|
|
401
|
+
aes_named=0
|
|
402
|
+
aes_wanted=0
|
|
403
|
+
for i in ${inputs[@]+"${inputs[@]}"}; do
|
|
404
|
+
case "$i" in
|
|
405
|
+
*/runtime-aes.c|runtime-aes.c) aes_named=1 ;;
|
|
406
|
+
*.ll) if grep -q '@nish_aes_hw(' "$i"; then aes_wanted=1; fi ;;
|
|
407
|
+
esac
|
|
408
|
+
done
|
|
409
|
+
if [ "$aes_wanted" = 1 ] && [ "$aes_named" = 0 ]; then
|
|
410
|
+
inputs+=("$(cd "$(dirname "$0")/../runtime" && pwd)/runtime-aes.c")
|
|
411
|
+
fi
|
|
213
412
|
# -mbulk-memory lowers llvm.memset/memcpy (`new Array<T>(n)`, `push` growth) to the
|
|
214
413
|
# memory.fill/memory.copy instructions instead of libc calls the freestanding link lacks.
|
|
215
414
|
"$CC" -Wno-override-module --target=wasm32-unknown-unknown -Oz -nostdlib -mbulk-memory \
|
|
@@ -284,5 +483,7 @@ case "$profile" in
|
|
|
284
483
|
*) echo "error: unknown profile '$profile'" >&2; exit 2 ;;
|
|
285
484
|
esac
|
|
286
485
|
|
|
486
|
+
if [ "${dsym:-0}" = 1 ]; then "$("$CC" -print-prog-name=dsymutil)" "$out"; fi
|
|
487
|
+
|
|
287
488
|
# `wc -c` pads with spaces on macOS; strip them so the number is clean.
|
|
288
489
|
printf '%s: %s bytes (%s)\n' "$out" "$(wc -c < "$out" | tr -d ' ')" "$profile"
|