@amritk/nish-x86_64-linux 0.16.0 → 0.18.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 -34
- package/bin/nish +0 -0
- package/package.json +1 -1
- package/runtime/nish.d.ts +117 -8
- package/runtime/nish.h +50 -13
- package/runtime/nish.mjs +71 -7
- package/runtime/runtime-host.c +43 -24
- package/runtime/runtime-net.c +57 -10
- package/runtime/runtime-os.c +62 -20
- package/runtime/runtime-simd.c +203 -0
- package/runtime/runtime-wasm.c +12 -0
- package/runtime/runtime.c +66 -18
- package/runtime/shim.mjs +114 -5
- package/scripts/build.sh +14 -12
- package/std/README.md +168 -36
- package/std/crypto/aes.ts +51 -8
- package/std/crypto/base64url.ts +25 -12
- package/std/crypto/chacha20poly1305.ts +43 -29
- package/std/crypto/ct.ts +6 -1
- package/std/crypto/hkdf.ts +364 -3
- package/std/crypto/hmac.ts +307 -7
- package/std/crypto/p256.ts +618 -34
- package/std/crypto/sha1.ts +147 -0
- package/std/crypto/sha256.ts +3 -2
- package/std/crypto/x25519.ts +88 -36
- package/std/crypto/x509.ts +130 -47
- package/std/json.ts +297 -41
- package/std/net/hpack.ts +1094 -0
- package/std/net/http-fields.ts +604 -0
- package/std/net/http1-server.ts +1389 -0
- package/std/net/http1.ts +1492 -0
- package/std/net/http2-frame.ts +745 -0
- package/std/net/http2-tls.ts +216 -0
- package/std/net/http2.ts +1433 -0
- package/std/net/http3-frame.ts +457 -0
- package/std/net/http3-server.ts +911 -0
- package/std/net/http3.ts +2145 -0
- package/std/net/qpack.ts +1163 -0
- package/std/net/quic-conn-ack.ts +206 -0
- package/std/net/quic-conn-cid.ts +425 -0
- package/std/net/quic-conn-params.ts +462 -0
- package/std/net/quic-datagram.ts +130 -0
- package/std/net/quic-frame.ts +1206 -0
- package/std/net/quic-listener.ts +929 -0
- package/std/net/quic-packet.ts +1698 -0
- package/std/net/quic-recovery.ts +849 -0
- package/std/net/quic-stream.ts +1609 -0
- package/std/net/quic.ts +3578 -0
- package/std/net/tls/codec.ts +1089 -0
- package/std/net/tls/record-server.ts +1173 -0
- package/std/net/tls/record.ts +621 -0
- package/std/net/tls/schedule.ts +429 -0
- package/std/net/tls-tcp.ts +414 -0
- package/std/net/tls.ts +1437 -0
- package/std/net/websocket.ts +823 -0
- package/std/net/webtransport.ts +1334 -0
- package/std/secret.ts +62 -0
- package/std/text.ts +77 -0
package/runtime/runtime.c
CHANGED
|
@@ -79,8 +79,12 @@ _Static_assert(offsetof(struct nish_arena, off) == 8, "the inlined allocator bum
|
|
|
79
79
|
_Static_assert(offsetof(struct nish_arena, cap) == 16, "the inlined allocator reads field 2");
|
|
80
80
|
_Static_assert(offsetof(struct nish_arena, chunks) == 24, "arena layout is ABI");
|
|
81
81
|
|
|
82
|
+
/* `fputs` rather than one `write`: libc's own loop finishes a short write,
|
|
83
|
+
which a bare `write` would cut off (docs/security/runtime.md, RT-11), and
|
|
84
|
+
stderr is unbuffered, so the message leaves before `_exit`. A C host that
|
|
85
|
+
gives stderr a buffer of its own would lose it here. */
|
|
82
86
|
static NISH_COLD void nish_die(const char *msg) {
|
|
83
|
-
(
|
|
87
|
+
fputs(msg, stderr);
|
|
84
88
|
_exit(1);
|
|
85
89
|
}
|
|
86
90
|
/* A function rather than a macro around `nish_die`: seven call sites each
|
|
@@ -214,7 +218,7 @@ nish_str *nish_str_concat(const nish_str *a, const nish_str *b) {
|
|
|
214
218
|
s->len = len;
|
|
215
219
|
memcpy(s->data, a->data, a->len);
|
|
216
220
|
memcpy(s->data + a->len, b->data, b->len);
|
|
217
|
-
s->data[
|
|
221
|
+
s->data[len] = 0;
|
|
218
222
|
return s;
|
|
219
223
|
}
|
|
220
224
|
|
|
@@ -247,24 +251,47 @@ _Bool nish_str_at(const nish_str *s, int64_t at, const nish_str *sub) {
|
|
|
247
251
|
exactly such a header from `examples/strings.ts`, and the interop tests
|
|
248
252
|
caught runtime.c picking it up and inheriting `nish.h` through it. A C host
|
|
249
253
|
would hit the same. A fifth of the time is not worth making the runtime
|
|
250
|
-
sensitive to its includer's `-I` paths.
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
254
|
+
sensitive to its includer's `-I` paths.
|
|
255
|
+
|
|
256
|
+
`s.indexOf(sub, from)` is the same search from byte `from`, and
|
|
257
|
+
`s.indexOf(sub)` is it from 0: one loop, not two. The compiler clamps `from`
|
|
258
|
+
into [0, s.length] first, as JavaScript does, with `llvm.smin`/`smax` where
|
|
259
|
+
LLVM can fold them, so the clamp costs this file nothing; an empty needle
|
|
260
|
+
answers that clamped `from`. The clamp is a precondition: a caller that
|
|
261
|
+
skips it never reads outside the string, but gets -1 for a non-empty needle
|
|
262
|
+
(a negative `from` reads as one past the end) and `from` itself, unchanged,
|
|
263
|
+
for an empty one. */
|
|
264
|
+
int64_t nish_str_index_of_from(const nish_str *s, const nish_str *sub, int64_t from) {
|
|
265
|
+
uint64_t at = (uint64_t)from;
|
|
266
|
+
if (sub->len == 0) return from;
|
|
267
|
+
while (at <= s->len && sub->len <= s->len - at) {
|
|
268
|
+
const char *c = memchr(s->data + at, sub->data[0], s->len - at - sub->len + 1);
|
|
269
|
+
if (!c) break;
|
|
270
|
+
at = (uint64_t)(c - s->data);
|
|
271
|
+
if (memcmp(c + 1, sub->data + 1, sub->len - 1) == 0) return (int64_t)at;
|
|
272
|
+
at++;
|
|
260
273
|
}
|
|
261
274
|
return -1;
|
|
262
275
|
}
|
|
276
|
+
int64_t nish_str_index_of(const nish_str *s, const nish_str *sub) { return nish_str_index_of_from(s, sub, 0); }
|
|
263
277
|
|
|
264
278
|
/* `console.log` / `console.error` / `write` / `writeError` and the message of
|
|
265
|
-
`panic`: fd 1 or 2, with or without the trailing newline.
|
|
279
|
+
`panic`: fd 1 or 2, with or without the trailing newline.
|
|
280
|
+
|
|
281
|
+
A pipe or a socket can take part of a long string and answer the count it
|
|
282
|
+
took: a signal that lands mid-write ends the call early, and a full pipe
|
|
283
|
+
does the same. So the bytes are written until all of them are out, or until
|
|
284
|
+
a write fails (docs/security/runtime.md, RT-11). A failure still ends the
|
|
285
|
+
output, `EAGAIN` on a non-blocking stdout included, since spinning on it
|
|
286
|
+
would burn a core. `EINTR` is not retried here: it needs a handler
|
|
287
|
+
installed without `SA_RESTART`, and the only one this runtime installs
|
|
288
|
+
(runtime-host.c) has it, so an interrupted write either restarts in the
|
|
289
|
+
kernel or comes back short. The newline needs no loop: one byte is never
|
|
290
|
+
written in part. */
|
|
266
291
|
void nish_write(const nish_str *s, int32_t fd, _Bool newline) {
|
|
267
|
-
|
|
292
|
+
uint64_t done = 0;
|
|
293
|
+
ssize_t k;
|
|
294
|
+
while (done < s->len && (k = write(fd, s->data + done, s->len - done)) > 0) done += k;
|
|
268
295
|
if (newline) (void)!write(fd, "\n", 1);
|
|
269
296
|
}
|
|
270
297
|
/* console.log(s) */
|
|
@@ -1203,6 +1230,20 @@ __attribute__((noinline)) void nish_array_grow(nish_array *a, uint64_t elem_size
|
|
|
1203
1230
|
a->cap = cap;
|
|
1204
1231
|
}
|
|
1205
1232
|
|
|
1233
|
+
/* `secureZero(bytes)` (#385): every byte of the array set to zero, in stores
|
|
1234
|
+
* no optimisation may remove. A `memset` here would be a dead store wherever
|
|
1235
|
+
* the buffer's life ends straight after the call -- a key in a frame that
|
|
1236
|
+
* returns, a block that is freed -- and links use `-flto`, which inlines this
|
|
1237
|
+
* body into that caller and lets dead-store elimination see it. Each store goes
|
|
1238
|
+
* through a `volatile` pointer, so each is behaviour the C standard keeps
|
|
1239
|
+
* whatever the optimiser can see, and `noinline` keeps the call itself in the
|
|
1240
|
+
* binary for a reader of the disassembly. A byte at a time, because a secret
|
|
1241
|
+
* is a key or a scalar of tens of bytes, not a buffer worth a vector loop. */
|
|
1242
|
+
__attribute__((noinline)) void nish_wipe(nish_array *bytes) {
|
|
1243
|
+
volatile char *p = bytes->data;
|
|
1244
|
+
for (uint64_t i = 0; i < bytes->len; i++) p[i] = 0;
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1206
1247
|
void nish_panic_index(uint64_t idx, uint64_t len) {
|
|
1207
1248
|
dprintf(2, "index out of range: %" PRIu64 " >= %" PRIu64 "\n", idx, len);
|
|
1208
1249
|
_exit(1);
|
|
@@ -1221,7 +1262,14 @@ void nish_panic_slice(int64_t start, int64_t end, int64_t len) {
|
|
|
1221
1262
|
_exit(1);
|
|
1222
1263
|
}
|
|
1223
1264
|
|
|
1224
|
-
/* ---- Checked
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1265
|
+
/* ---- Checked arithmetic (Rust semantics): the failed-check paths. One table
|
|
1266
|
+
for both panics, because each message is "attempt to <what>" and the two
|
|
1267
|
+
functions then share everything but the index. `nish_panic_overflow`'s `op`
|
|
1268
|
+
is the operator whose `llvm.s*.with.overflow` flagged: 0 add, 1 subtract,
|
|
1269
|
+
2 multiply, 3 negate, the order `src/emit-ops.ts` numbers them in. */
|
|
1270
|
+
static const char *const nish_arith_panics[6] = {
|
|
1271
|
+
"attempt to add with overflow\n", "attempt to subtract with overflow\n",
|
|
1272
|
+
"attempt to multiply with overflow\n", "attempt to negate with overflow\n",
|
|
1273
|
+
"attempt to divide with overflow\n", "attempt to divide by zero\n"};
|
|
1274
|
+
void nish_panic_overflow(uint32_t op) { nish_die(nish_arith_panics[op]); }
|
|
1275
|
+
void nish_panic_div(_Bool by_zero) { nish_panic_overflow(4 + by_zero); }
|
package/runtime/shim.mjs
CHANGED
|
@@ -123,6 +123,52 @@ export function ctEq(a, b) {
|
|
|
123
123
|
return (a ^ b) === 0 ? 0xffffffff : 0;
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* `nish:secret` (docs/LANGUAGE.md, "Secrets"). Natively the compiler holds a
|
|
128
|
+
* `Secret` to its rules and `wipe` is a volatile store of zeros; here the rules
|
|
129
|
+
* have already been checked by the compiler or by nobody, so a `Secret` is a
|
|
130
|
+
* plain wrapper and `wipe` zero-fills what it holds. `runtime/nish.mjs`
|
|
131
|
+
* resolves the `nish:secret` specifier to this module, so these are its
|
|
132
|
+
* exports by name. A zero-fill under Node promises nothing about copies the
|
|
133
|
+
* engine made, which is the native wipe's whole point and is not claimed here.
|
|
134
|
+
*/
|
|
135
|
+
export class Secret {
|
|
136
|
+
constructor(value) {
|
|
137
|
+
this.value = value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function secret(value) {
|
|
142
|
+
return new Secret(value);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function expose(s, f) {
|
|
146
|
+
return f(s.value);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function exposeWith(s, arg, f) {
|
|
150
|
+
return f(s.value, arg);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Zero an array of integers, or every integer field of a record: `0` for a number, `0n` for an `i64` or `u64`. */
|
|
154
|
+
function zeroFill(target) {
|
|
155
|
+
if (Array.isArray(target) || ArrayBuffer.isView(target)) {
|
|
156
|
+
for (let i = 0; i < target.length; i++) {
|
|
157
|
+
target[i] = typeof target[i] === "bigint" ? 0n : 0;
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
for (const key of Object.keys(target)) {
|
|
162
|
+
const field = target[key];
|
|
163
|
+
if (typeof field === "bigint") target[key] = 0n;
|
|
164
|
+
else if (typeof field === "number") target[key] = 0;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export function wipe(target) {
|
|
169
|
+
zeroFill(target instanceof Secret ? target.value : target);
|
|
170
|
+
}
|
|
171
|
+
|
|
126
172
|
/** Wrap a BigInt to the i64 range: every i64 `+ - * /` and unary minus goes through here. */
|
|
127
173
|
export function wrapI64(x) {
|
|
128
174
|
return BigInt.asIntN(64, x);
|
|
@@ -497,9 +543,22 @@ export function readFileBytesSync(path) {
|
|
|
497
543
|
}
|
|
498
544
|
}
|
|
499
545
|
|
|
546
|
+
/**
|
|
547
|
+
* The flags `runtime-os.c` opens a file it writes with, `O_NOFOLLOW` included
|
|
548
|
+
* (docs/security/runtime.md, RT-4): a symbolic link as the last component of
|
|
549
|
+
* the path is refused rather than followed, so `writeFileSync`,
|
|
550
|
+
* `appendFileSync` and `spawnSyncTo` fail here where they fail natively. Node's
|
|
551
|
+
* `"w"` and `"a"` follow the link. A link among the directories on the way is
|
|
552
|
+
* still followed, as it is natively. Windows has no `O_NOFOLLOW`, and there the
|
|
553
|
+
* flag is 0.
|
|
554
|
+
*/
|
|
555
|
+
const { O_WRONLY, O_CREAT, O_TRUNC, O_APPEND, O_NOFOLLOW = 0 } = fs.constants;
|
|
556
|
+
const WRITE_FLAGS = O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW;
|
|
557
|
+
const APPEND_FLAGS = O_WRONLY | O_CREAT | O_APPEND | O_NOFOLLOW;
|
|
558
|
+
|
|
500
559
|
export function writeFileSync(path, data) {
|
|
501
560
|
try {
|
|
502
|
-
fs.writeFileSync(path, data, "utf8");
|
|
561
|
+
fs.writeFileSync(path, data, { encoding: "utf8", flag: WRITE_FLAGS });
|
|
503
562
|
} catch {
|
|
504
563
|
ioFail("write", path);
|
|
505
564
|
}
|
|
@@ -507,7 +566,7 @@ export function writeFileSync(path, data) {
|
|
|
507
566
|
|
|
508
567
|
export function appendFileSync(path, data) {
|
|
509
568
|
try {
|
|
510
|
-
fs.appendFileSync(path, data, "utf8");
|
|
569
|
+
fs.appendFileSync(path, data, { encoding: "utf8", flag: APPEND_FLAGS });
|
|
511
570
|
} catch {
|
|
512
571
|
ioFail("write", path);
|
|
513
572
|
}
|
|
@@ -623,8 +682,9 @@ export function getenv(name) {
|
|
|
623
682
|
*
|
|
624
683
|
* `out` and `err` are paths for the child's stdout and stderr, and an **empty**
|
|
625
684
|
* string leaves that stream inherited. Each file is created or truncated at
|
|
626
|
-
* 0644
|
|
627
|
-
*
|
|
685
|
+
* 0644 with `WRITE_FLAGS`, which is what the runtime's file actions ask for:
|
|
686
|
+
* `O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW`, so a symbolic link at the path
|
|
687
|
+
* is refused and the answer is -1, on both sides. The descriptors opened here are
|
|
628
688
|
* closed again whichever way the child went; natively the child opens them and
|
|
629
689
|
* its exit drops them.
|
|
630
690
|
*/
|
|
@@ -633,7 +693,7 @@ function spawnImpl(argv, out, err) {
|
|
|
633
693
|
const opened = [];
|
|
634
694
|
const stream = (target) => {
|
|
635
695
|
if (target.length === 0) return "inherit";
|
|
636
|
-
const fd = fs.openSync(target,
|
|
696
|
+
const fd = fs.openSync(target, WRITE_FLAGS, 0o644);
|
|
637
697
|
opened.push(fd);
|
|
638
698
|
return fd;
|
|
639
699
|
};
|
|
@@ -696,6 +756,51 @@ export function statMtimeSync(path) {
|
|
|
696
756
|
return st.mtimeMs;
|
|
697
757
|
}
|
|
698
758
|
|
|
759
|
+
/**
|
|
760
|
+
* The RT-9 owner checks (#386): `lstatOwnerModeSync(path)` is the owner's uid
|
|
761
|
+
* in the high 32 bits and `st_mode` in the low 32 of one `lstat`, so a
|
|
762
|
+
* symbolic link answers for itself, or -1 when the path does not resolve or
|
|
763
|
+
* holds a NUL; `geteuid()` is the effective uid; `isExecutableSync(path)` is
|
|
764
|
+
* `access(path, X_OK)`. Each answers what `runtime-host.c` answers, the two
|
|
765
|
+
* `i64`s as BigInts because that is how an `i64` is held on this side. Node's
|
|
766
|
+
* `fs` throws on a NUL in a path where the runtime names nothing, so the
|
|
767
|
+
* `catch` is what makes the two agree there too.
|
|
768
|
+
*/
|
|
769
|
+
export function lstatOwnerModeSync(path) {
|
|
770
|
+
try {
|
|
771
|
+
const st = fs.lstatSync(path);
|
|
772
|
+
return BigInt.asIntN(64, (BigInt(st.uid >>> 0) << 32n) | BigInt(st.mode >>> 0));
|
|
773
|
+
} catch {
|
|
774
|
+
return -1n;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
export function geteuid() {
|
|
779
|
+
return BigInt(process.geteuid());
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
export function isExecutableSync(path) {
|
|
783
|
+
try {
|
|
784
|
+
fs.accessSync(path, fs.constants.X_OK);
|
|
785
|
+
return true;
|
|
786
|
+
} catch {
|
|
787
|
+
return false;
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
/**
|
|
792
|
+
* `secureZero(bytes)` (#385): every element set to zero, which is what the
|
|
793
|
+
* native `nish_wipe` leaves. Nothing here can drop a store to an array the
|
|
794
|
+
* caller still holds, so a loop is the whole of it. What it cannot promise is
|
|
795
|
+
* what the native build does: a collector that moved the array may have left a
|
|
796
|
+
* copy of the old bytes behind, and JavaScript has no way to reach it.
|
|
797
|
+
*/
|
|
798
|
+
export function secureZero(bytes) {
|
|
799
|
+
for (let i = 0; i < bytes.length; i++) {
|
|
800
|
+
bytes[i] = 0;
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
|
|
699
804
|
/**
|
|
700
805
|
* Node's own fill, taken before `runtime/nish.mjs` puts the one below in its
|
|
701
806
|
* place on the same object: `webcrypto` is the global `crypto`.
|
|
@@ -807,3 +912,7 @@ export function arenaUsed() { return 0; }
|
|
|
807
912
|
export function arenaMark() { return 0; }
|
|
808
913
|
export function arenaRelease() {}
|
|
809
914
|
export function arenaReset() {}
|
|
915
|
+
/** `using a = arena()`: a disposable whose disposal does nothing, because there is no arena to release. */
|
|
916
|
+
export function arena() {
|
|
917
|
+
return { [Symbol.dispose]() {} };
|
|
918
|
+
}
|
package/scripts/build.sh
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
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]
|
|
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 six 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
10
|
# threads, <dir>/runtime-host.c, the wall clock, entropy, file times and
|
|
11
|
-
# signals,
|
|
12
|
-
#
|
|
11
|
+
# signals, <dir>/runtime-net.c, the sockets of `nish:net`, and
|
|
12
|
+
# <dir>/runtime-simd.c, the byte searches that read a vector at a time. Each of
|
|
13
|
+
# those files says why they are compiled and measured apart.
|
|
13
14
|
#
|
|
14
15
|
# Profiles:
|
|
15
16
|
# debug clang defaults: no optimisation, symbols kept. The "before" number.
|
|
@@ -20,8 +21,6 @@
|
|
|
20
21
|
# load it from Node. Add runtime/runtime-wasm.c to the inputs when a
|
|
21
22
|
# function uses arrays (the arena and the array cold paths, no libc);
|
|
22
23
|
# strings and I/O still need a WASI runtime and are not available.
|
|
23
|
-
# wasm wasm32 freestanding module exporting every non-internal function
|
|
24
|
-
# (for modules that do not use the C runtime); load it from Node.
|
|
25
24
|
# wasi wasm32-wasi command module: runtime.c linked against wasi-libc, so
|
|
26
25
|
# string programs run under any WASI host (`_start` runs `main`;
|
|
27
26
|
# `node examples/wasi-host.mjs app.wasm args...`). Needs a WASI
|
|
@@ -78,18 +77,21 @@ while [ $# -gt 0 ]; do
|
|
|
78
77
|
--pgo-use)
|
|
79
78
|
[ -f "$2" ] || { echo "error: --pgo-use: profile '$2' not found (run the instrumented binary, then llvm-profdata merge)" >&2; exit 2; }
|
|
80
79
|
pgo=("-fprofile-use=$2"); shift 2 ;;
|
|
81
|
-
-h|--help) sed -n '2,
|
|
80
|
+
-h|--help) sed -n '2,42p' "$0"; exit 0 ;;
|
|
82
81
|
*) inputs+=("$1"); shift ;;
|
|
83
82
|
esac
|
|
84
83
|
done
|
|
85
84
|
[ ${#inputs[@]} -gt 0 ] || { echo "error: no input files" >&2; exit 2; }
|
|
86
85
|
[ -n "$out" ] || { echo "error: -o <out> is required" >&2; exit 2; }
|
|
87
86
|
|
|
88
|
-
# The runtime is
|
|
87
|
+
# The runtime is six translation units, and a caller names one: whoever passes
|
|
89
88
|
# <dir>/runtime.c gets <dir>/runtime-os.c, <dir>/runtime-parallel.c,
|
|
90
|
-
# <dir>/runtime-host.c
|
|
91
|
-
#
|
|
92
|
-
#
|
|
89
|
+
# <dir>/runtime-host.c, <dir>/runtime-net.c and <dir>/runtime-simd.c compiled
|
|
90
|
+
# beside it, on every profile, wasi included: runtime-simd.c takes its scalar
|
|
91
|
+
# path there unless the build adds -msimd128. They were one file until the
|
|
92
|
+
# operating-system half was split out for its own size budget, and the parallel
|
|
93
|
+
# half followed for the same reason (each file's header comment says why), and
|
|
94
|
+
# a link line is where those splits
|
|
93
95
|
# would otherwise leak: `nish --link` builds its command line in
|
|
94
96
|
# src/compile.ts, the published package's recipe in every document and
|
|
95
97
|
# README names runtime.c, and a user's own clang line does too. Pairing them
|
|
@@ -99,7 +101,7 @@ done
|
|
|
99
101
|
for i in ${inputs[@]+"${inputs[@]}"}; do
|
|
100
102
|
case "$i" in
|
|
101
103
|
*/runtime.c|runtime.c)
|
|
102
|
-
for half in runtime-os.c runtime-parallel.c runtime-host.c runtime-net.c; do
|
|
104
|
+
for half in runtime-os.c runtime-parallel.c runtime-host.c runtime-net.c runtime-simd.c; do
|
|
103
105
|
side="${i%runtime.c}$half"
|
|
104
106
|
have=0
|
|
105
107
|
for j in "${inputs[@]}"; do
|