@amritk/nish-x86_64-linux 0.23.1 → 0.25.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 (51) hide show
  1. package/bin/nish +0 -0
  2. package/docs/AI.md +50 -23
  3. package/package.json +1 -1
  4. package/runtime/browser.mjs +4 -3
  5. package/runtime/core/arrays.d.ts +0 -2
  6. package/runtime/core/arrays.mjs +0 -2
  7. package/runtime/core/host.d.ts +8 -0
  8. package/runtime/core/host.mjs +50 -2
  9. package/runtime/core/numbers.mjs +2 -3
  10. package/runtime/nish.d.ts +100 -4
  11. package/runtime/nish.h +34 -0
  12. package/runtime/nish.mjs +23 -5
  13. package/runtime/node.mjs +57 -6
  14. package/runtime/runtime-net.c +45 -4
  15. package/runtime/runtime-os.c +115 -12
  16. package/runtime/runtime-term.c +2 -2
  17. package/runtime/runtime.c +1 -1
  18. package/scripts/build.sh +13 -3
  19. package/std/README.md +44 -7
  20. package/std/cli-args.ts +1216 -0
  21. package/std/cli-flags.ts +585 -0
  22. package/std/cli.ts +308 -19
  23. package/std/compress/checksum.ts +290 -0
  24. package/std/compress/deflate.ts +2790 -0
  25. package/std/compress/gzip.ts +761 -0
  26. package/std/compress/zlib.ts +168 -0
  27. package/std/crypto/LICENSE-fiat-crypto-p384 +21 -0
  28. package/std/crypto/p256.ts +63 -1
  29. package/std/crypto/p384.ts +6044 -0
  30. package/std/crypto/pki-roots.ts +322 -0
  31. package/std/crypto/pki.ts +1207 -0
  32. package/std/crypto/rsa.ts +663 -0
  33. package/std/crypto/sha256.ts +2 -3
  34. package/std/crypto/x25519.ts +3 -4
  35. package/std/crypto/x509.ts +1054 -58
  36. package/std/net/dns-config.ts +734 -0
  37. package/std/net/dns-message.ts +517 -0
  38. package/std/net/dns-resolver.ts +1015 -0
  39. package/std/net/dns.ts +478 -0
  40. package/std/net/http-compress.ts +393 -0
  41. package/std/net/http1-client.ts +1227 -0
  42. package/std/net/http1-server.ts +101 -1
  43. package/std/net/http1.ts +9 -9
  44. package/std/net/tls/client.ts +1094 -0
  45. package/std/net/tls/codec.ts +585 -3
  46. package/std/net/tls/record-client.ts +1192 -0
  47. package/std/net/tls/verify.ts +221 -0
  48. package/std/net/tls-tcp.ts +642 -11
  49. package/std/net/tls.ts +2 -2
  50. package/std/net/url.ts +559 -4
  51. package/std/yaml-write.ts +415 -0
package/runtime/nish.mjs CHANGED
@@ -84,12 +84,16 @@ globalThis.console = {
84
84
  // `Array#sort`'s UTF-16 order — the two agree on ASCII names only.
85
85
  provide("write", shim.write);
86
86
  provide("writeError", shim.writeError);
87
+ provide("writeBytes", shim.writeBytes);
87
88
  provide("panic", shim.panic);
88
89
  provide("readFileSync", shim.readFileSync);
89
90
  provide("readFileSyncOrNull", shim.readFileSyncOrNull);
90
91
  provide("readFileBytesSync", shim.readFileBytesSync);
91
92
  provide("writeFileSync", shim.writeFileSync);
92
93
  provide("appendFileSync", shim.appendFileSync);
94
+ provide("writeFileBytesSync", shim.writeFileBytesSync);
95
+ provide("renameSync", shim.renameSync);
96
+ provide("unlinkSync", shim.unlinkSync);
93
97
  provide("mkdirSync", shim.mkdirSync);
94
98
  provide("isDirectorySync", shim.isDirectorySync);
95
99
  provide("readdirSync", shim.readdirSync);
@@ -103,6 +107,14 @@ provide("kill", shim.kill);
103
107
  provide("waitPid", shim.waitPid);
104
108
  provide("availableParallelism", shim.availableParallelism);
105
109
  provide("totalMemory", shim.totalMemory);
110
+ // `setNice` and `setIdlePriority` throw, naming themselves: Node has a reading
111
+ // of the nice value (`os.setPriority`) but none of `SCHED_IDLE`, and a reading
112
+ // of one without the other would differ from the native build on the same host.
113
+ for (const name of ["setNice", "setIdlePriority"]) {
114
+ provide(name, () => {
115
+ throw new Error(`\`${name}\` has no reading under Node: it changes how the kernel schedules the native program`);
116
+ });
117
+ }
106
118
  provide("getenv", shim.getenv);
107
119
  provide("statMtimeSync", shim.statMtimeSync);
108
120
  provide("signalFd", shim.signalFd);
@@ -130,9 +142,17 @@ for (const name of [
130
142
  "pollModify",
131
143
  "pollRemove",
132
144
  "pollWait",
145
+ "netSetOption",
133
146
  ]) {
134
147
  provide(name, () => shim.noNetReading(name));
135
148
  }
149
+ // `netSetOption`'s option ids are plain numbers, so they read the same here
150
+ // as natively: only the call that takes one throws.
151
+ provide("NET_TCP_NODELAY", 1);
152
+ provide("NET_TCP_QUICKACK", 2);
153
+ provide("NET_SO_KEEPALIVE", 3);
154
+ provide("NET_SO_SNDBUF", 4);
155
+ provide("NET_SO_RCVBUF", 5);
136
156
 
137
157
  // `nish:term`. Whether a descriptor is a terminal and how big stdout's is have
138
158
  // Node's answers; reading stdin, waiting for it and raw mode throw, each naming
@@ -180,6 +200,7 @@ if (!("set" in Array.prototype)) {
180
200
  // describes. A printed reading can never match a native run: the two origins are
181
201
  // both arbitrary, and only the difference between two reads means anything.
182
202
  provide("monotonicNanos", shim.monotonicNanos);
203
+ provide("wallClockNanos", shim.wallClockNanos);
183
204
 
184
205
  // Conversions. In f64 mode `toF64` is the identity and `toI32` is the one that
185
206
  // matters (it saturates, where a JavaScript cast would not).
@@ -265,15 +286,12 @@ globalThis.parseFloat = shim.parseFloat;
265
286
  // script under Node, which is one index later than Node's own `argv`.
266
287
  process.argv = shim.argv();
267
288
 
268
- // There is no arena. `mark`/`release`/`reset` are no-ops rather than errors so
269
- // that a program which manages memory explicitly still runs; `used()` answers
270
- // zero, which is the honest number for a host that is not bump-allocating.
289
+ // There is no arena. `mark()` answers zero rather than failing so that a
290
+ // program which reads it still runs; `used()` answers zero too, which is the honest number for a host that is not bump-allocating.
271
291
  // Delegated like everything else here rather than written inline, so that the
272
292
  // answers cannot drift from the ones the rewritten runner gives.
273
293
  provide("Arena", {
274
294
  mark: shim.arenaMark,
275
- release: shim.arenaRelease,
276
- reset: shim.arenaReset,
277
295
  used: shim.arenaUsed,
278
296
  });
279
297
  // `using a = arena()` releases nothing here either: what the block allocated
package/runtime/node.mjs CHANGED
@@ -72,6 +72,48 @@ function appendFileSync(path, data) {
72
72
  }
73
73
  }
74
74
 
75
+ /**
76
+ * The negative errno a failed `fs` call names, as `runtime-os.c` answers it.
77
+ * Node names the errno by its code; one it gives no number for, such as the
78
+ * `ERR_INVALID_ARG_VALUE` a path holding a NUL throws, is -22 (`EINVAL`), the
79
+ * runtime's answer to that path.
80
+ */
81
+ function negErrno(e) {
82
+ return -(os.constants.errno[e.code] ?? 22);
83
+ }
84
+
85
+ /** `ok` when `f` returns, or the negative errno of what it threw. */
86
+ function errnoOr(ok, f) {
87
+ try {
88
+ f();
89
+ return ok;
90
+ } catch (e) {
91
+ return negErrno(e);
92
+ }
93
+ }
94
+
95
+ /** `renameSync(from, to)`: 0, or a negative errno (-18 across file systems). */
96
+ const renameSync = (from, to) => errnoOr(0, () => fs.renameSync(from, to));
97
+
98
+ /** `unlinkSync(path)`: 0, or a negative errno; a directory is refused, not removed. */
99
+ const unlinkSync = (path) => errnoOr(0, () => fs.unlinkSync(path));
100
+
101
+ /** `writeFileBytesSync(path, bytes)`: the byte count, or a negative errno; mode 0644 and `WRITE_FLAGS`. */
102
+ const writeFileBytesSync = (path, bytes) =>
103
+ errnoOr(bytes.length, () => fs.writeFileSync(path, Uint8Array.from(bytes), { flag: WRITE_FLAGS, mode: 0o644 }));
104
+
105
+ /**
106
+ * `writeBytes`'s write, after the core checked the range `[from, to)`: every
107
+ * byte to stdout, then the count, or a negative errno. `fs.writeSync` may
108
+ * write less than it was given, so it is asked again for the rest.
109
+ */
110
+ function writeBytes(bytes, from, to) {
111
+ const buf = Uint8Array.from(bytes.slice(from, to));
112
+ return errnoOr(buf.length, () => {
113
+ for (let at = 0; at < buf.length; ) at += fs.writeSync(1, buf, at, buf.length - at);
114
+ });
115
+ }
116
+
75
117
  /**
76
118
  * `mkdirSync(path)` (WP14 D4): one directory, not recursive, answering whether
77
119
  * a directory is there afterwards. Node throws where the runtime answers
@@ -234,12 +276,7 @@ function spawnSyncTo(argv, out, err) {
234
276
  */
235
277
  function kill(pid, sig) {
236
278
  if (pid <= 0 || !(sig === 0 || sig === 2 || sig === 9 || sig === 15)) return -22;
237
- try {
238
- process.kill(pid, sig);
239
- return 0;
240
- } catch (e) {
241
- return -(os.constants.errno[e.code] ?? 22);
242
- }
279
+ return errnoOr(0, () => process.kill(pid, sig));
243
280
  }
244
281
 
245
282
  /**
@@ -275,6 +312,15 @@ function monotonicNanos() {
275
312
  return process.hrtime.bigint();
276
313
  }
277
314
 
315
+ /**
316
+ * `wallClockNanos()`: the wall clock in nanoseconds since the epoch, a BigInt
317
+ * like `monotonicNanos`'s. Node's wall clock is `Date.now`, whole
318
+ * milliseconds, so the last six digits are zeros where a native run has them.
319
+ */
320
+ function wallClockNanos() {
321
+ return BigInt(Date.now()) * 1000000n;
322
+ }
323
+
278
324
  /**
279
325
  * `statMtimeSync(path)`: Node's `mtimeMs` for the path, or NaN when it cannot
280
326
  * be stat'd, which is the native answer too. `mtimeMs` is the same arithmetic
@@ -344,6 +390,10 @@ const CALLS = {
344
390
  readFileBytesSync,
345
391
  writeFileSync,
346
392
  appendFileSync,
393
+ writeFileBytesSync,
394
+ renameSync,
395
+ unlinkSync,
396
+ writeBytes,
347
397
  mkdirSync,
348
398
  isDirectorySync,
349
399
  readdirSync,
@@ -357,6 +407,7 @@ const CALLS = {
357
407
  availableParallelism,
358
408
  totalMemory,
359
409
  monotonicNanos,
410
+ wallClockNanos,
360
411
  statMtimeSync,
361
412
  lstatOwnerModeSync,
362
413
  geteuid,
@@ -17,7 +17,8 @@
17
17
  * errno to it: where `signalFd` and `readSignal` answer a plain -1, a call
18
18
  * here answers the failure's errno, negated. The codes a loop branches on are Linux's numbers on every platform (-11
19
19
  * would block, -95 unsupported, -32 the peer is gone, -104 reset, -98 the
20
- * address is in use, -111 refused, -110 timed out, -22 a bad argument), so
20
+ * address is in use, -111 refused, -110 timed out, -22 a bad argument, -88
21
+ * not a socket), so
21
22
  * `nish_net_err` translates Darwin's; any other failure is the host's own
22
23
  * `-errno`. Nothing here allocates: the addresses a call reads or writes are
23
24
  * the caller's `u8[]`.
@@ -29,13 +30,14 @@
29
30
  * (`EAFNOSUPPORT`, as in many containers) `::` falls back to IPv4's
30
31
  * `0.0.0.0`.
31
32
  *
32
- * Linux and Darwin differ in five places: `SOCK_NONBLOCK | SOCK_CLOEXEC` and
33
+ * Linux and Darwin differ in six places: `SOCK_NONBLOCK | SOCK_CLOEXEC` and
33
34
  * `accept4` against a `socket` or `accept` followed by `fcntl`, `MSG_NOSIGNAL`
34
35
  * against the `SO_NOSIGPIPE` socket option (a write to a gone peer is -32 on
35
36
  * both, never SIGPIPE), the errno numbers, UDP's segmentation offload
36
37
  * (`UDP_SEGMENT`, `UDP_GRO`) and ECN marks, which Linux has and for which
37
38
  * Darwin answers -95, and the readiness loop, which is epoll on Linux and
38
- * kqueue on Darwin. Only the sending side and the GRO bind say -95 there: a
39
+ * kqueue on Darwin, and `TCP_QUICKACK`, which only Linux has (-95 on
40
+ * Darwin). Only the sending side and the GRO bind say -95 there: a
39
41
  * receive leaves `meta` at 0, 0, and a `meta[1]` of 0 is also what a real
40
42
  * Not-ECT datagram reads as, so a program learns that Darwin has no ECN from
41
43
  * the send, never from the receive. The Darwin branch is compiled by CI's Darwin bootstrap
@@ -100,6 +102,7 @@ static int32_t nish_net_err(int e) {
100
102
  if (e == EADDRINUSE) return -98;
101
103
  if (e == ECONNREFUSED) return -111;
102
104
  if (e == ETIMEDOUT) return -110;
105
+ if (e == ENOTSOCK) return -88;
103
106
  #endif
104
107
  return -e;
105
108
  }
@@ -359,7 +362,7 @@ int32_t nish_connect_result(int32_t fd) {
359
362
 
360
363
  /* `netRead(fd, buf, off, len)`: at most `len` bytes into `buf` from `off`.
361
364
  The compiled call has already checked `0 <= off <= off + len <=
362
- buf.length`, unless the program was built with `--unchecked-indexing`. */
365
+ buf.length`. */
363
366
  int32_t nish_net_read(int32_t fd, nish_array *buf, int64_t off, int64_t len) {
364
367
  ssize_t n = recv(fd, buf->data + off, (size_t)len, 0);
365
368
  return n < 0 ? nish_net_fail() : (int32_t)n;
@@ -383,6 +386,44 @@ int32_t nish_net_shutdown(int32_t fd, int32_t how) {
383
386
  promises on Linux, so a failure is information rather than a retry. */
384
387
  int32_t nish_net_close(int32_t fd) { return close(fd) != 0 ? nish_net_fail() : 0; }
385
388
 
389
+ /* `netSetOption(fd, option, value)`: one option from a whitelist, named by
390
+ the runtime's own id rather than a level and an option number, so a
391
+ program can set these five and nothing else. `value` is 0 or 1 for the
392
+ flags and a byte count for the buffers, passed to the kernel as it is
393
+ (Linux stores twice a buffer size, to cover its bookkeeping). Linux does
394
+ not keep quick-ACK mode: the kernel leaves it of its own accord, so a
395
+ server that wants it on or off sets it again after each response. */
396
+ int32_t nish_net_set_option(int32_t fd, int32_t option, int32_t value) {
397
+ int level = SOL_SOCKET;
398
+ int name;
399
+ switch (option) {
400
+ case 1:
401
+ level = IPPROTO_TCP;
402
+ name = TCP_NODELAY;
403
+ break;
404
+ case 2:
405
+ #if defined(__linux__)
406
+ level = IPPROTO_TCP;
407
+ name = TCP_QUICKACK;
408
+ break;
409
+ #else
410
+ return -95;
411
+ #endif
412
+ case 3:
413
+ name = SO_KEEPALIVE;
414
+ break;
415
+ case 4:
416
+ name = SO_SNDBUF;
417
+ break;
418
+ case 5:
419
+ name = SO_RCVBUF;
420
+ break;
421
+ default:
422
+ return -22;
423
+ }
424
+ return setsockopt(fd, level, name, &value, sizeof value) != 0 ? nish_net_fail() : 0;
425
+ }
426
+
386
427
  /* `udpBind(host, port, flags)`: a datagram socket, non-blocking and
387
428
  close-on-exec, bound like `tcpListen`'s, with `SO_REUSEPORT` for flag 1
388
429
  and `UDP_GRO` for flag 2; any other bit is -22. */
@@ -51,6 +51,7 @@
51
51
  /* WASI has no processes, so `spawn.h` and `wait.h` do not exist there. */
52
52
  #include <signal.h>
53
53
  #include <spawn.h>
54
+ #include <sys/resource.h>
54
55
  #include <sys/wait.h>
55
56
  extern char **environ;
56
57
  #endif
@@ -178,23 +179,89 @@ nish_str *nish_read_file(const nish_str *path) {
178
179
 
179
180
  `O_CLOEXEC` for the reason the read has it (RT-12), and `EINTR` is retried
180
181
  as the read retries it (RT-13): a write cut short is finished, and one that
181
- fails is `cannot write`, never a silently short file. */
182
+ fails is `cannot write`, never a silently short file.
183
+
184
+ The loop is `nish_put_all`, which `writeFileBytesSync` and `writeBytes`
185
+ share: all `n` bytes of `p` to `fd`, answering 0 or the negative errno of
186
+ the write that failed (-EIO for one that wrote nothing). */
187
+ static int32_t nish_put_all(int fd, const char *p, uint64_t n) {
188
+ for (uint64_t done = 0; done < n;) {
189
+ ssize_t k = write(fd, p + done, n - done);
190
+ if (k > 0) done += k;
191
+ else if (k == 0) return -EIO;
192
+ else if (errno != EINTR) return -errno;
193
+ }
194
+ return 0;
195
+ }
182
196
  static void nish_put_file(const nish_str *path, const nish_str *data, int flags) {
183
197
  int fd = open(nish_cpath(path), O_WRONLY | O_CREAT | O_NOFOLLOW | O_CLOEXEC | flags, 0644);
184
- if (fd < 0) nish_io_fail("write ", path);
185
- for (uint64_t done = 0; done < data->len;) {
186
- ssize_t n = write(fd, data->data + done, data->len - done);
187
- if (n > 0) {
188
- done += n;
189
- } else if (n == 0 || errno != EINTR) {
190
- nish_io_fail("write ", path);
191
- }
192
- }
198
+ if (fd < 0 || nish_put_all(fd, data->data, data->len) < 0) nish_io_fail("write ", path);
193
199
  close(fd);
194
200
  }
195
201
  void nish_write_file(const nish_str *path, const nish_str *data) { nish_put_file(path, data, O_TRUNC); }
196
202
  void nish_append_file(const nish_str *path, const nish_str *data) { nish_put_file(path, data, O_APPEND); }
197
203
 
204
+ /* ---- Replacing and removing files: `renameSync`, `unlinkSync` and
205
+ `writeFileBytesSync`. Unlike the writers above, none exits: each answers 0
206
+ (or the bytes written) on success and a negative errno otherwise, so a
207
+ program that writes a file beside its target and renames it over the
208
+ target owns its own diagnostic. A path holding a NUL answers -EINVAL before
209
+ any system call, rather than reaching the file its prefix names
210
+ (docs/security/runtime.md, RT-3). */
211
+
212
+ /* `renameSync(from, to)`: rename(2). On one file system it replaces `to`
213
+ atomically, so a reader sees the old file or the new one and never a part;
214
+ across two it answers -EXDEV and moves nothing, since a copy and a delete
215
+ would not be atomic. A link at `from` is moved as a link, not followed. */
216
+ int32_t nish_rename(const nish_str *from, const nish_str *to) {
217
+ const char *f = nish_cpath(from), *t = nish_cpath(to);
218
+ if (f != from->data || t != to->data) return -EINVAL;
219
+ return rename(f, t) == 0 ? 0 : -errno;
220
+ }
221
+
222
+ /* `unlinkSync(path)`: unlink(2). A directory is refused with whatever the
223
+ kernel answers (-EISDIR on Linux, -EPERM elsewhere) and is never handed to
224
+ rmdir. A link is removed, not the file it names. */
225
+ int32_t nish_unlink(const nish_str *path) {
226
+ const char *p = nish_cpath(path);
227
+ if (p != path->data) return -EINVAL;
228
+ return unlink(p) == 0 ? 0 : -errno;
229
+ }
230
+
231
+ /* `writeFileBytesSync(path, bytes)`: every element of a `u8[]` to the file,
232
+ opened as `writeFileSync` opens it (`O_NOFOLLOW`, `O_CLOEXEC`, `O_TRUNC`,
233
+ 0644 before the umask) and written through `nish_put_all`, a short write
234
+ finished and `EINTR` retried. It answers the length written or the negative
235
+ errno of the `open` or `write` that failed (-EIO for a write that wrote
236
+ nothing); a failed write leaves the file truncated to what got out. */
237
+ int32_t nish_write_file_bytes(const nish_str *path, const nish_array *bytes) {
238
+ const char *p = nish_cpath(path);
239
+ if (p != path->data) return -EINVAL;
240
+ if (bytes->len > NISH_LENGTH_MAX) return -EFBIG;
241
+ int fd = open(p, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW | O_CLOEXEC, 0644);
242
+ if (fd < 0) return -errno;
243
+ int32_t r = nish_put_all(fd, bytes->data, bytes->len);
244
+ /* A write NFS or a quota refuses can surface only at `close`, and the file
245
+ is then short: an answer of the full length would let the caller rename
246
+ it over the one it replaces. */
247
+ if (close(fd) != 0 && r == 0 && errno != EINTR) r = -errno;
248
+ return r < 0 ? r : (int32_t)bytes->len;
249
+ }
250
+
251
+ /* `writeBytes(bytes, off, len)` (nish:io): the bytes `[off, off + len)` of a
252
+ `u8[]` on stdout, through `nish_put_all`, so a short write is finished and `EINTR` retried. The
253
+ emitter has range-checked `off` and `len` against `bytes->len`
254
+ (`emitNetCall`). It answers `len`, every byte out, or the negative errno of
255
+ the write that failed (`-EIO` for one that wrote nothing), and never exits:
256
+ how much of a failed call reached the descriptor is not said. A pipe whose
257
+ reader has gone raises SIGPIPE, as it does for `write`. */
258
+ int32_t nish_write_bytes(const nish_array *bytes, int64_t off, int64_t len) {
259
+ /* An empty array's data may be NULL, and NULL + 0 is undefined. */
260
+ if (len == 0) return 0;
261
+ int32_t r = nish_put_all(1, bytes->data + off, (uint64_t)len);
262
+ return r < 0 ? r : (int32_t)len;
263
+ }
264
+
198
265
  /* ---- Directories and subprocesses (WP14 D4): what a driver needs to create
199
266
  its own output directory and shell out for `--link`. Both answer a value
200
267
  instead of exiting, as `nish_read_file_or_null` does — there are no
@@ -477,6 +544,36 @@ int64_t nish_total_memory(void) {
477
544
  #endif
478
545
  }
479
546
 
547
+ /* `setNice(n)`: this process's nice value set to `n`, 0 or a negative errno.
548
+ The kernel clamps `n` to its range (-20 to 19), so only lowering the value
549
+ below what the process has can fail: -EACCES (Linux, Darwin) or -EPERM
550
+ without the privilege to raise a priority. Linux keeps the value per thread,
551
+ so it is the calling thread's and every thread started after it. */
552
+ int32_t nish_set_nice(int32_t n) {
553
+ #ifdef __wasi__
554
+ (void)n;
555
+ return -ENOSYS;
556
+ #else
557
+ return setpriority(PRIO_PROCESS, 0, n) == 0 ? 0 : -errno;
558
+ #endif
559
+ }
560
+
561
+ /* `setIdlePriority()`: this process moved to `SCHED_IDLE`, which runs it only
562
+ when nothing else wants the CPU. Linux alone has the policy; elsewhere the
563
+ answer is -EOPNOTSUPP's Linux number, -95, as runtime-net.c answers it. An
564
+ unprivileged process cannot leave the policy again. Like the nice value, the
565
+ policy is the calling thread's. */
566
+ int32_t nish_set_idle_priority(void) {
567
+ #if defined(__linux__)
568
+ struct sched_param param = {0};
569
+ return sched_setscheduler(0, SCHED_IDLE, &param) == 0 ? 0 : -errno;
570
+ #elif defined(__wasi__)
571
+ return -ENOSYS;
572
+ #else
573
+ return -95;
574
+ #endif
575
+ }
576
+
480
577
  /* `readdirSync(path)`: the listing a driver needs to discover its own inputs.
481
578
 
482
579
  **Sorted by bytes**, which `readdir(3)` and Node are not, for two reasons
@@ -545,11 +642,17 @@ nish_array *nish_readdir(const nish_str *path) {
545
642
  backwards and make an elapsed time negative. Only the difference between two
546
643
  reads means anything — the origin is arbitrary and is not comparable across
547
644
  processes or machines — and an i64 of nanoseconds is 292 years of it. */
548
- int64_t nish_monotonic_nanos(void) {
645
+ static int64_t nish_clock_nanos(clockid_t clock) {
549
646
  struct timespec ts;
550
- clock_gettime(CLOCK_MONOTONIC, &ts);
647
+ clock_gettime(clock, &ts);
551
648
  return (int64_t)ts.tv_sec * 1000000000 + ts.tv_nsec;
552
649
  }
650
+ int64_t nish_monotonic_nanos(void) { return nish_clock_nanos(CLOCK_MONOTONIC); }
651
+
652
+ /* `wallClockNanos()`: `CLOCK_REALTIME`, nanoseconds since the Unix epoch, for
653
+ dating an event. It steps when the system time is set, so a duration is
654
+ `monotonicNanos`'s to measure. */
655
+ int64_t nish_wall_clock_nanos(void) { return nish_clock_nanos(CLOCK_REALTIME); }
553
656
 
554
657
  /* A C string copied into the arena, or NULL for NULL: the shape of every
555
658
  answer libc hands back by pointer, `getenv`'s here and `realpath`'s below. */
@@ -145,8 +145,8 @@ static int nish_stdin_ready(int32_t timeout_ms) {
145
145
 
146
146
  /* `readStdin(buf, off, len)`: at most `len` bytes into `buf` from `off`, the
147
147
  count, 0 at the end of the stream and -11 when nothing is waiting. The
148
- compiled call has already checked `0 <= off <= off + len <= buf.length`,
149
- unless the program was built with `--unchecked-indexing`. A `read` that a
148
+ compiled call has already checked `0 <= off <= off + len <= buf.length`.
149
+ A `read` that a
150
150
  signal interrupts is asked again; one that would block (a descriptor some
151
151
  other program made non-blocking) is -11 too, and so is a readiness check a
152
152
  signal interrupts, which would otherwise fall through to a `read` that
package/runtime/runtime.c CHANGED
@@ -1354,7 +1354,7 @@ nish_array *nish_alloc_array(uint64_t elem_size, uint64_t len) {
1354
1354
  return a;
1355
1355
  }
1356
1356
 
1357
- /* process.argv: malloc, not the arena, so Arena.reset() cannot free it. Built once by @main.
1357
+ /* process.argv: malloc, not the arena, so no arena scope can free it. Built once by @main.
1358
1358
  Deliberately *not* NISH_TLS (WP20 T0 §3.2): `nish_argv_init` writes it once
1359
1359
  before the program's `main` body runs and nothing writes it afterwards, so
1360
1360
  every thread may read the one copy. That holds only while no thread is
package/scripts/build.sh CHANGED
@@ -62,6 +62,10 @@
62
62
  # put in the .ll (line table, variables) reaches the binary. `nish
63
63
  # --link -g` passes it through automatically.
64
64
  #
65
+ # Build id: every native ELF binary carries an NT_GNU_BUILD_ID note
66
+ # (--build-id=sha1). A -g link hashes other bytes than a plain one, so strip
67
+ # the -g build to pair a binary with its debug file (docs/guide/using-nish/cli.md).
68
+ #
65
69
  # CPU level (WP38 S2): `--cpu x86-64-v3` compiles and links every input --
66
70
  # the module, the runtime and any other C -- with -march=x86-64-v3
67
71
  # -ffp-contract=off, so the code generator may use AVX2, BMI2 and the other
@@ -281,8 +285,8 @@ case "$(uname -s)" in
281
285
  # followed by SIGABRT, so the stage1 that linked went on to abort the
282
286
  # moment the bootstrap ran it. Measured too, on the run after the one that
283
287
  # named the UUID. An unloadable compiler is worse than any reproducibility,
284
- # so the UUID stays. The ELF branch's --build-id=none below is the flag
285
- # this would have been; ELF has no loader that insists on one.
288
+ # so the UUID stays. ELF's twin is the build-id note below, which ELF's
289
+ # loader never asks for and which is a hash of the output's bytes anyway.
286
290
  #
287
291
  # It costs nothing, because the UUID was measured stable across two links
288
292
  # of one input to one output path, and differing on a link to another. That
@@ -294,8 +298,14 @@ case "$(uname -s)" in
294
298
  strip_flag=(-Wl,-x) ;;
295
299
  *)
296
300
  # shellcheck disable=SC2054 # -Wl,<flag> is one argument: the comma is the linker's
297
- gc=(-Wl,--gc-sections -Wl,--as-needed -Wl,-O2 -Wl,--build-id=none); strip_flag=(-s)
301
+ gc=(-Wl,--gc-sections -Wl,--as-needed -Wl,-O2); strip_flag=(-s)
298
302
  elf=(-fno-plt)
303
+ # A GNU build-id note on every native link, debug included, so perf, gdb
304
+ # and debuginfod can match a stripped binary to its debug file. sha1 is a
305
+ # hash of the output's own bytes in lld, GNU ld and gold, so one input
306
+ # links to one id and `stage3 == stage2` holds; uuid would be random.
307
+ # shellcheck disable=SC2054
308
+ common+=(-Wl,--build-id=sha1)
299
309
  # GNU ld needs the gold plugin for LTO; prefer lld when clang can find it.
300
310
  if command -v ld.lld >/dev/null 2>&1; then common+=(-fuse-ld=lld); lld=1; fi ;;
301
311
  esac