@amritk/nish-x86_64-linux 0.20.0 → 0.22.1
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 +52 -10
- package/bin/nish +0 -0
- package/docs/AI.md +2777 -0
- package/package.json +2 -1
- package/runtime/browser.d.ts +6 -0
- package/runtime/browser.mjs +77 -0
- package/runtime/core/arrays.d.ts +36 -0
- package/runtime/core/arrays.mjs +153 -0
- package/runtime/core/checks.mjs +20 -0
- package/runtime/core/host.d.ts +80 -0
- package/runtime/core/host.mjs +325 -0
- package/runtime/core/numbers.d.ts +66 -0
- package/runtime/core/numbers.mjs +343 -0
- package/runtime/core/result.d.ts +45 -0
- package/runtime/core/result.mjs +140 -0
- package/runtime/core/strings.d.ts +14 -0
- package/runtime/core/strings.mjs +169 -0
- package/runtime/core.d.ts +9 -0
- package/runtime/core.mjs +55 -0
- package/runtime/host.d.ts +6 -0
- package/runtime/nish.d.ts +46 -2
- package/runtime/nish.h +75 -2
- package/runtime/nish.mjs +23 -3
- package/runtime/node.mjs +386 -0
- package/runtime/runtime-bytes.c +401 -0
- package/runtime/runtime-cross.c +1 -1
- package/runtime/runtime-host.c +1 -1
- package/runtime/runtime-net.c +12 -1
- package/runtime/runtime-os.c +1 -1
- package/runtime/runtime-parallel.c +1 -1
- package/runtime/runtime-simd.c +231 -7
- package/runtime/runtime-term.c +504 -0
- package/runtime/runtime-wasm.c +0 -11
- package/runtime/runtime.c +190 -6
- package/runtime/shim.mjs +6 -985
- package/runtime/sqlite/LICENSE.md +85 -0
- package/runtime/sqlite/README.md +45 -0
- package/runtime/sqlite/nish-sqlite.c +359 -0
- package/runtime/sqlite/sqlite3.c +271131 -0
- package/runtime/sqlite/sqlite3.h +14447 -0
- package/scripts/build.sh +311 -59
- package/std/LICENSE-fast-float +23 -0
- package/std/LICENSE-fdlibm +8 -0
- package/std/README.md +32 -12
- package/std/base64.ts +116 -0
- package/std/bytes.ts +256 -0
- package/std/cli.ts +344 -0
- package/std/collections.ts +6 -5
- package/std/crypto/LICENSE-crypt_blowfish +26 -0
- package/std/crypto/base64url.ts +125 -0
- package/std/crypto/bcrypt.ts +1557 -0
- package/std/crypto/hmac.ts +138 -2
- package/std/crypto/x509.ts +11 -122
- package/std/json-tokenizer.ts +2998 -57
- package/std/json-value.ts +22 -3
- package/std/json.ts +87 -8
- package/std/math.ts +608 -0
- package/std/net/http1-server.ts +282 -40
- package/std/net/http1.ts +40 -21
- package/std/net/http3-server.ts +50 -12
- package/std/net/postgres.ts +2294 -0
- package/std/net/prefork.ts +23 -3
- package/std/net/quic-listener.ts +20 -4
- package/std/random.ts +66 -4
- package/std/regex.ts +4274 -0
- package/std/sqlite.ts +391 -0
- package/std/term-io.ts +978 -0
- package/std/term-pixels.ts +874 -0
- package/std/term-probe.ts +459 -0
- package/std/term-rate.ts +993 -0
- package/std/term.ts +2453 -0
- package/std/text.ts +90 -7
- package/std/yaml-lines.ts +141 -0
- package/std/yaml.ts +4675 -0
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
/* Nish runtime, the terminal half: reading stdin without blocking, waiting
|
|
2
|
+
* for it with a timeout, and the three questions a terminal UI asks of a TTY
|
|
3
|
+
* (is it one, how big is it, and raw mode on or off), the `nish:term` builtin
|
|
4
|
+
* module.
|
|
5
|
+
*
|
|
6
|
+
* A translation unit of its own for the reason runtime-net.c is one: each
|
|
7
|
+
* file carries its own measured `.text*` ceiling in tests/runtime-budgets.js,
|
|
8
|
+
* and runtime-os.c, the other half this could have joined, had 177 bytes of
|
|
9
|
+
* its ceiling left. Section GC means a program that calls none of these pays
|
|
10
|
+
* for none of them, and scripts/build.sh pairs this file with runtime.c like
|
|
11
|
+
* the other halves, so a link line still names one runtime. `waitStdin`
|
|
12
|
+
* reads its clock through runtime-os.c's `nish_monotonic_nanos`, so a program
|
|
13
|
+
* that waits links that function too.
|
|
14
|
+
*
|
|
15
|
+
* Errors take `nish:net`'s shape: an `int32_t` that is `>= 0` for success and
|
|
16
|
+
* a negated errno for a failure. Two codes a program branches on are spelled
|
|
17
|
+
* as numbers, because WASI numbers its errnos differently: -11 is "nothing is
|
|
18
|
+
* waiting" and -25 is "not a terminal", on every platform. Any other failure
|
|
19
|
+
* is the host's own `-errno`.
|
|
20
|
+
*
|
|
21
|
+
* stdin is never made non-blocking. `O_NONBLOCK` is a property of the open
|
|
22
|
+
* file description, which a shell shares with every program it starts, so
|
|
23
|
+
* setting it would leave the shell's own stdin non-blocking after a crash.
|
|
24
|
+
* `readStdin` asks `select` first instead, with a timeout of 0.
|
|
25
|
+
*
|
|
26
|
+
* Raw mode is cfmakeraw's input side without its output side: no echo, no line
|
|
27
|
+
* editing, no translation of CR or NL and no CR dropped, a break and a parity
|
|
28
|
+
* error read as plain bytes, no flow control, 8-bit bytes, a read that answers
|
|
29
|
+
* as soon as one byte is there. `ISIG` stays on, so Ctrl-C is still
|
|
30
|
+
* SIGINT and Ctrl-Z still stops the program, and `OPOST` stays on, so "\n"
|
|
31
|
+
* still moves to the start of the next line. The termios that was there is
|
|
32
|
+
* saved by each `setRawMode(true)` that enters raw mode and put back by
|
|
33
|
+
* `setRawMode(false)`, by an atexit handler, by every panic (runtime.c's
|
|
34
|
+
* `nish_fatal_hook`, because a failed check ends in `_exit`), and by each of
|
|
35
|
+
* the signals POSIX says end a program, SIGKILL aside: SIGINT, SIGTERM and
|
|
36
|
+
* their kin, SIGPIPE from a reader that went away, and the faults (SIGILL,
|
|
37
|
+
* SIGTRAP, SIGABRT, SIGFPE, SIGBUS, SIGSEGV). A handler is installed for one
|
|
38
|
+
* only when the program left it at its default: it restores the terminal, the
|
|
39
|
+
* default comes back (`SA_RESETHAND`), and the signal is raised again, so the
|
|
40
|
+
* program still dies of it and its parent sees that. The handlers run on an
|
|
41
|
+
* alternate stack of the thread that entered raw mode, so a stack overflow
|
|
42
|
+
* there restores the terminal too; one on another thread has no stack to run
|
|
43
|
+
* it on, and the terminal stays raw. SIGTSTP, Ctrl-Z, is caught the same way,
|
|
44
|
+
* because no shell can be counted on for it: bash puts its own termios back
|
|
45
|
+
* while a job is stopped but does not give the job its raw mode back on `fg`,
|
|
46
|
+
* and a shell without that bookkeeping puts back nothing. Its handler restores
|
|
47
|
+
* the terminal, stops the program, and once a shell continues it puts the
|
|
48
|
+
* handler back and raw mode with it.
|
|
49
|
+
* A stop by SIGSTOP, which no program can catch, leaves the terminal raw at a
|
|
50
|
+
* shell that puts nothing back.
|
|
51
|
+
*
|
|
52
|
+
* `setTerminalRestore(bytes)` gives that way out bytes to write to stdout
|
|
53
|
+
* first, such as the escapes that leave an alternate screen or show the
|
|
54
|
+
* cursor again: once, by `write` alone, at exit, on a panic and in each of the
|
|
55
|
+
* handlers that end the program, before the termios comes back. Setting them
|
|
56
|
+
* arms the same handlers, raw mode or not. A Ctrl-Z and `setRawMode(false)`
|
|
57
|
+
* do not end the program, so they write nothing.
|
|
58
|
+
*
|
|
59
|
+
* WASI has `read` and `poll` on stdin and no terminal interface, so there the
|
|
60
|
+
* first two work (`readStdin` waiting, below) and the TTY questions answer
|
|
61
|
+
* "not a terminal": false, `[-25, 0]`, and -25 for entering raw mode and for
|
|
62
|
+
* restore bytes, which are not kept.
|
|
63
|
+
* Otherwise Linux and Darwin only; the file uses nothing else. */
|
|
64
|
+
/* `SA_RESETHAND` is XSI, so _POSIX_C_SOURCE alone does not declare it on
|
|
65
|
+
glibc; Darwin hides `TIOCGWINSZ` behind either unless `_DARWIN_C_SOURCE`
|
|
66
|
+
says otherwise. */
|
|
67
|
+
#define _XOPEN_SOURCE 700
|
|
68
|
+
#if defined(__APPLE__)
|
|
69
|
+
#define _DARWIN_C_SOURCE
|
|
70
|
+
#endif
|
|
71
|
+
#include "nish.h"
|
|
72
|
+
|
|
73
|
+
#include <errno.h>
|
|
74
|
+
#include <string.h>
|
|
75
|
+
#include <unistd.h>
|
|
76
|
+
#ifdef __wasi__
|
|
77
|
+
#include <poll.h>
|
|
78
|
+
#else
|
|
79
|
+
#include <signal.h>
|
|
80
|
+
#include <stdlib.h>
|
|
81
|
+
#include <sys/ioctl.h>
|
|
82
|
+
#include <sys/select.h>
|
|
83
|
+
#include <termios.h>
|
|
84
|
+
#endif
|
|
85
|
+
|
|
86
|
+
#ifndef __wasi__
|
|
87
|
+
/* How many times the SIGTSTP handler has stopped the program, so a wait the
|
|
88
|
+
stop interrupted can tell that from any other signal. Atomic, because the
|
|
89
|
+
handler on one thread counts what a wait on another reads. */
|
|
90
|
+
static int nish_term_stops;
|
|
91
|
+
|
|
92
|
+
static int nish_term_stop_count(void) {
|
|
93
|
+
return __atomic_load_n(&nish_term_stops, __ATOMIC_RELAXED);
|
|
94
|
+
}
|
|
95
|
+
#endif
|
|
96
|
+
|
|
97
|
+
/* 1 when stdin is readable within `timeout_ms` (a negative one waits for
|
|
98
|
+
ever), which includes its end and an error, 0 when it is not or a signal
|
|
99
|
+
ends the wait, and the host's `-errno` when the question fails. A Ctrl-Z
|
|
100
|
+
and `fg` do not end it once `setRawMode(true)` has caught SIGTSTP: no
|
|
101
|
+
system restarts a `select` after a handler, `SA_RESTART` or not, so one the
|
|
102
|
+
handler's stop interrupted is asked again, for what is left until a
|
|
103
|
+
deadline read once from the monotonic clock, since `select` promises
|
|
104
|
+
nothing of its timeout or its set after an error. The first ask is for the
|
|
105
|
+
whole timeout, so a wait reads the clock once up front and again only after
|
|
106
|
+
a stop. A stop that lands between reading the clock and `select` starting
|
|
107
|
+
goes unseen, and that wait runs the time it had left after `fg`; one in the
|
|
108
|
+
last few instructions, after the stop count is read, also has the next
|
|
109
|
+
other signal ask again rather than end the wait. Before raw mode the
|
|
110
|
+
default stop decides: Linux goes on with what is left, Darwin ends the
|
|
111
|
+
wait. Outside WASI this asks `select`, because Darwin's `poll` does not
|
|
112
|
+
support devices: it answers POLLNVAL for a terminal at once, with no input
|
|
113
|
+
there. Under WASI a timeout under 10 ms asks for 10: Node's WASI (uvwasi)
|
|
114
|
+
runs a timer that is due before its first look at the descriptor, and
|
|
115
|
+
counts it from before it set the look up, so a shorter wait answers
|
|
116
|
+
"nothing waiting" while bytes are there whenever setting up took longer
|
|
117
|
+
than the wait, as on a busy machine. */
|
|
118
|
+
static int nish_stdin_ready(int32_t timeout_ms) {
|
|
119
|
+
#ifdef __wasi__
|
|
120
|
+
struct pollfd p = {0, POLLIN, 0};
|
|
121
|
+
int r = poll(&p, 1, timeout_ms < 0 ? -1 : timeout_ms < 10 ? 10 : timeout_ms);
|
|
122
|
+
#else
|
|
123
|
+
fd_set in;
|
|
124
|
+
struct timeval tv;
|
|
125
|
+
int64_t left = timeout_ms * 1000LL;
|
|
126
|
+
int64_t end = timeout_ms > 0 ? nish_monotonic_nanos() + left * 1000 : 0;
|
|
127
|
+
int r, stops;
|
|
128
|
+
for (;;) {
|
|
129
|
+
tv.tv_sec = left / 1000000;
|
|
130
|
+
tv.tv_usec = left % 1000000;
|
|
131
|
+
FD_ZERO(&in);
|
|
132
|
+
FD_SET(0, &in);
|
|
133
|
+
stops = nish_term_stop_count();
|
|
134
|
+
r = select(1, &in, 0, 0, timeout_ms < 0 ? 0 : &tv);
|
|
135
|
+
if (r >= 0 || errno != EINTR || stops == nish_term_stop_count()) break;
|
|
136
|
+
if (timeout_ms > 0) {
|
|
137
|
+
left = (end - nish_monotonic_nanos()) / 1000;
|
|
138
|
+
if (left < 0) left = 0;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
#endif
|
|
142
|
+
if (r < 0) return errno == EINTR ? 0 : -errno;
|
|
143
|
+
return r;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* `readStdin(buf, off, len)`: at most `len` bytes into `buf` from `off`, the
|
|
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
|
|
150
|
+
signal interrupts is asked again; one that would block (a descriptor some
|
|
151
|
+
other program made non-blocking) is -11 too, and so is a readiness check a
|
|
152
|
+
signal interrupts, which would otherwise fall through to a `read` that
|
|
153
|
+
waits. Under WASI the check is left out and the read waits, so there the
|
|
154
|
+
program asks `waitStdin` first. */
|
|
155
|
+
int32_t nish_read_stdin(nish_array *buf, int64_t off, int64_t len) {
|
|
156
|
+
#ifndef __wasi__
|
|
157
|
+
int ready = nish_stdin_ready(0);
|
|
158
|
+
if (ready <= 0) return ready == 0 ? -11 : ready;
|
|
159
|
+
#endif
|
|
160
|
+
ssize_t n;
|
|
161
|
+
do n = read(0, buf->data + off, (size_t)len);
|
|
162
|
+
while (n < 0 && errno == EINTR);
|
|
163
|
+
if (n >= 0) return (int32_t)n;
|
|
164
|
+
return errno == EAGAIN || errno == EWOULDBLOCK ? -11 : -errno;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/* `waitStdin(timeoutMs)`: true once stdin is readable, which includes its end
|
|
168
|
+
and an error, so a read after it answers at once; false when `timeoutMs`
|
|
169
|
+
passes first, or a signal ends the wait early: not a Ctrl-Z once
|
|
170
|
+
`setRawMode(true)` has caught SIGTSTP. A negative timeout waits for ever. */
|
|
171
|
+
bool nish_wait_stdin(int32_t timeout_ms) { return nish_stdin_ready(timeout_ms) != 0; }
|
|
172
|
+
|
|
173
|
+
#ifdef __wasi__
|
|
174
|
+
bool nish_is_terminal(int32_t fd) {
|
|
175
|
+
(void)fd;
|
|
176
|
+
return 0;
|
|
177
|
+
}
|
|
178
|
+
nish_array *nish_terminal_size(void) {
|
|
179
|
+
nish_array *out = nish_alloc_array(sizeof(int32_t), 2);
|
|
180
|
+
int32_t *w = (int32_t *)out->data;
|
|
181
|
+
w[0] = -25;
|
|
182
|
+
w[1] = 0;
|
|
183
|
+
return out;
|
|
184
|
+
}
|
|
185
|
+
int32_t nish_set_raw_mode(bool on) { return on ? -25 : 0; }
|
|
186
|
+
int32_t nish_set_terminal_restore(const nish_str *bytes) {
|
|
187
|
+
return bytes->len > NISH_TERM_RESTORE_MAX ? -7 : bytes->len == 0 ? 0 : -25;
|
|
188
|
+
}
|
|
189
|
+
#else
|
|
190
|
+
/* `isTerminal(fd)`: `isatty`. */
|
|
191
|
+
bool nish_is_terminal(int32_t fd) { return isatty(fd) == 1; }
|
|
192
|
+
|
|
193
|
+
/* `terminalSize()`: `[columns, rows]` of the terminal on stdout, or on stderr
|
|
194
|
+
when stdout is not one (the two Node keeps a size for), as a fresh
|
|
195
|
+
two-element `i32[]`; `[-errno, 0]` when neither is, -25 for "not a
|
|
196
|
+
terminal" when either answered so. A terminal no one set a size for
|
|
197
|
+
answers `[0, 0]`. */
|
|
198
|
+
nish_array *nish_terminal_size(void) {
|
|
199
|
+
nish_array *out = nish_alloc_array(sizeof(int32_t), 2);
|
|
200
|
+
int32_t *w = (int32_t *)out->data;
|
|
201
|
+
struct winsize ws;
|
|
202
|
+
int r = ioctl(1, TIOCGWINSZ, &ws);
|
|
203
|
+
int err = errno;
|
|
204
|
+
if (r != 0) r = ioctl(2, TIOCGWINSZ, &ws);
|
|
205
|
+
if (r == 0) {
|
|
206
|
+
w[0] = ws.ws_col;
|
|
207
|
+
w[1] = ws.ws_row;
|
|
208
|
+
} else {
|
|
209
|
+
w[0] = err == ENOTTY || errno == ENOTTY ? -25 : -errno;
|
|
210
|
+
w[1] = 0;
|
|
211
|
+
}
|
|
212
|
+
return out;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/* The termios stdin had before raw mode was entered, the raw one entered,
|
|
216
|
+
whether that is in force now, and whether the exit and signal handlers are
|
|
217
|
+
in place. One terminal per process, so one of each. */
|
|
218
|
+
static struct termios nish_term_saved, nish_term_raw_t;
|
|
219
|
+
static volatile sig_atomic_t nish_term_raw, nish_term_entering;
|
|
220
|
+
static int nish_term_armed;
|
|
221
|
+
|
|
222
|
+
/* Held across `setRawMode`, the atexit and panic restores, and the whole
|
|
223
|
+
SIGTSTP handler, stop included, so two threads entering raw mode at once
|
|
224
|
+
save the cooked termios once and arm the handlers once, and no mode is set
|
|
225
|
+
between a Ctrl-Z's restore and its re-entry: the program stops with the
|
|
226
|
+
terminal as it was, and comes back raw. A spin, because the runtime links
|
|
227
|
+
no pthread without `--threads`. A thread holds it only with SIGTSTP blocked,
|
|
228
|
+
so that handler never waits on the thread it interrupted, and every handler
|
|
229
|
+
blocks SIGTSTP too; the other handlers end the program, and never take
|
|
230
|
+
it. */
|
|
231
|
+
static int nish_term_lock;
|
|
232
|
+
|
|
233
|
+
/* Block or unblock SIGTSTP on this thread, `how` as `sigprocmask` takes it.
|
|
234
|
+
`sigprocmask` rather than `pthread_sigmask` for the reason the lock is a
|
|
235
|
+
spin; both Linux and Darwin apply it to the calling thread alone. */
|
|
236
|
+
static void nish_term_mask(int how, sigset_t *old) {
|
|
237
|
+
sigset_t s;
|
|
238
|
+
sigemptyset(&s);
|
|
239
|
+
sigaddset(&s, SIGTSTP);
|
|
240
|
+
sigprocmask(how, &s, old);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/* Block SIGTSTP and take the lock, the mask before in `old`. */
|
|
244
|
+
static void nish_term_hold(sigset_t *old) {
|
|
245
|
+
nish_term_mask(SIG_BLOCK, old);
|
|
246
|
+
while (__atomic_exchange_n(&nish_term_lock, 1, __ATOMIC_ACQUIRE)) {
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
static void nish_term_drop(const sigset_t *old) {
|
|
251
|
+
__atomic_store_n(&nish_term_lock, 0, __ATOMIC_RELEASE);
|
|
252
|
+
sigprocmask(SIG_SETMASK, old, 0);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/* The bytes `setTerminalRestore` left for the way out, in one of two buffers,
|
|
256
|
+
and which: `nish_term_out` is 0 for none, `len * 2 + buffer` for some, and
|
|
257
|
+
-1 once they went out, after which nothing is stored again. An update
|
|
258
|
+
copies into the buffer not published, under the lock, and publishes by
|
|
259
|
+
swapping the word last, so a signal mid-update writes the old bytes or the
|
|
260
|
+
new ones, never half of each; one update runs at a time, so the buffer a
|
|
261
|
+
writer reads is never the one being filled. `nish_term_next` is the buffer
|
|
262
|
+
the next update fills. */
|
|
263
|
+
static char nish_term_bytes[2][NISH_TERM_RESTORE_MAX];
|
|
264
|
+
static int nish_term_out, nish_term_next;
|
|
265
|
+
|
|
266
|
+
/* Write the published bytes to stdout, once per process: the swap to -1
|
|
267
|
+
takes them, so the atexit handler, a panic and a signal racing each other
|
|
268
|
+
write them once between them. `write` alone, finished across short writes
|
|
269
|
+
and retried when a signal interrupts it; the program ends after it, so
|
|
270
|
+
errno is not kept. */
|
|
271
|
+
static void nish_term_write_out(void) {
|
|
272
|
+
int s = __atomic_exchange_n(&nish_term_out, -1, __ATOMIC_ACQ_REL);
|
|
273
|
+
if (s <= 0) return;
|
|
274
|
+
const char *p = nish_term_bytes[s & 1];
|
|
275
|
+
size_t n = (size_t)(s >> 1);
|
|
276
|
+
while (n > 0) {
|
|
277
|
+
ssize_t w = write(1, p, n);
|
|
278
|
+
if (w < 0 && errno == EINTR) continue;
|
|
279
|
+
if (w <= 0) break;
|
|
280
|
+
p += w;
|
|
281
|
+
n -= (size_t)w;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/* Put the saved termios back, once. Called with the lock held, or from a
|
|
286
|
+
handler that ends the program, so it uses only calls that are
|
|
287
|
+
async-signal-safe and needs no Ctrl-Z kept out: SIGTSTP is blocked in both.
|
|
288
|
+
The flag is cleared after the call, so any other signal that lands during it
|
|
289
|
+
restores again, which is harmless, rather than not at all. The panic hook
|
|
290
|
+
stays while restore bytes wait. */
|
|
291
|
+
static void nish_term_restore(void) {
|
|
292
|
+
struct termios now;
|
|
293
|
+
if (nish_term_raw || (nish_term_entering && tcgetattr(0, &now) == 0 && now.c_lflag == nish_term_raw_t.c_lflag &&
|
|
294
|
+
now.c_iflag == nish_term_raw_t.c_iflag)) {
|
|
295
|
+
tcsetattr(0, TCSANOW, &nish_term_saved);
|
|
296
|
+
nish_term_raw = 0;
|
|
297
|
+
if (__atomic_load_n(&nish_term_out, __ATOMIC_ACQUIRE) <= 0)
|
|
298
|
+
__atomic_store_n(&nish_fatal_hook, 0, __ATOMIC_RELEASE);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/* The way out of the program: the restore bytes, then the termios. */
|
|
303
|
+
static void nish_term_finish(void) {
|
|
304
|
+
nish_term_write_out();
|
|
305
|
+
nish_term_restore();
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/* The atexit and panic restore: under the lock, so it waits out a Ctrl-Z
|
|
309
|
+
handler on another thread rather than leave the terminal to its re-entry,
|
|
310
|
+
and with every signal blocked, so one that lands mid-write runs its handler
|
|
311
|
+
once the bytes are out rather than end the program with them half written. */
|
|
312
|
+
static void nish_term_leave(void) {
|
|
313
|
+
sigset_t all, old;
|
|
314
|
+
sigfillset(&all);
|
|
315
|
+
sigprocmask(SIG_BLOCK, &all, &old);
|
|
316
|
+
nish_term_hold(&all);
|
|
317
|
+
nish_term_finish();
|
|
318
|
+
nish_term_drop(&old);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/* Enter the raw termios, `when` as `tcsetattr` takes it: 0, or the host's
|
|
322
|
+
`-errno`. Raw once the call has switched, not before: a signal that ends the
|
|
323
|
+
program while it drains the output, or while SIGTTOU holds it stopped in the
|
|
324
|
+
background, finds the terminal as it was and leaves it, rather than setting
|
|
325
|
+
it from the background and being stopped again. */
|
|
326
|
+
static int32_t nish_term_enter(int when) {
|
|
327
|
+
nish_term_entering = 1;
|
|
328
|
+
if (tcsetattr(0, when, &nish_term_raw_t) != 0) {
|
|
329
|
+
nish_term_entering = 0;
|
|
330
|
+
return -errno;
|
|
331
|
+
}
|
|
332
|
+
nish_term_raw = 1;
|
|
333
|
+
nish_term_entering = 0;
|
|
334
|
+
__atomic_store_n(&nish_fatal_hook, nish_term_leave, __ATOMIC_RELEASE);
|
|
335
|
+
return 0;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/* Where a fault's handler runs, so a SIGSEGV from an exhausted stack can:
|
|
339
|
+
64 KiB, past Darwin's MINSIGSTKSZ of 32 KiB and glibc's on any core. */
|
|
340
|
+
static char nish_term_alt_stack[65536];
|
|
341
|
+
|
|
342
|
+
static void nish_term_on_signal(int sig);
|
|
343
|
+
|
|
344
|
+
/* Catch `sig`: once, `SA_RESETHAND`, on the alternate stack, and with the
|
|
345
|
+
signal left unblocked while the handler runs (`SA_NODEFER`), so the
|
|
346
|
+
handler's own `raise` acts at once rather than after it returns, SIGTSTP
|
|
347
|
+
aside. SIGTSTP is blocked in every handler, so a Ctrl-Z cannot stop one
|
|
348
|
+
between its restore and its `raise`, and re-enter raw mode for the program
|
|
349
|
+
to die in. A handler that ends the program blocks every signal, its own
|
|
350
|
+
too, so a second one cannot nest into its write and end the program with
|
|
351
|
+
the bytes half out; its own `raise` then lands as the handler returns.
|
|
352
|
+
SIGTSTP's blocks only SIGTSTP, so a signal still ends a program its stop
|
|
353
|
+
holds. `SA_RESTART`, so a call that a Ctrl-Z and `fg` interrupt goes on
|
|
354
|
+
where the system restarts it. */
|
|
355
|
+
static void nish_term_catch(int sig) {
|
|
356
|
+
struct sigaction sa;
|
|
357
|
+
sa.sa_handler = nish_term_on_signal;
|
|
358
|
+
sa.sa_flags = SA_RESETHAND | SA_ONSTACK | SA_NODEFER | SA_RESTART;
|
|
359
|
+
sigemptyset(&sa.sa_mask);
|
|
360
|
+
if (sig != SIGTSTP) sigfillset(&sa.sa_mask);
|
|
361
|
+
sigaddset(&sa.sa_mask, SIGTSTP);
|
|
362
|
+
sigaction(sig, &sa, 0);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/* Restore the terminal and raise `sig` again at its default. `SA_RESETHAND`
|
|
366
|
+
put the default back, except that POSIX lets a system keep the handler for
|
|
367
|
+
SIGILL and SIGTRAP, so it is put back here too: `signal` is
|
|
368
|
+
async-signal-safe. Every signal caught but SIGTSTP ends the program in that
|
|
369
|
+
`raise`. SIGTSTP's handler holds the lock throughout and unblocks SIGTSTP
|
|
370
|
+
for the `raise` alone, which stops the program and returns once a shell
|
|
371
|
+
continues it, in the foreground or, after a SIGTTOU stop in the
|
|
372
|
+
`tcsetattr`, once it is brought there. The handler that ends the program
|
|
373
|
+
takes no lock, because the thread it interrupted may hold it. */
|
|
374
|
+
static void nish_term_on_signal(int sig) {
|
|
375
|
+
if (sig != SIGTSTP) {
|
|
376
|
+
nish_term_finish();
|
|
377
|
+
signal(sig, SIG_DFL);
|
|
378
|
+
raise(sig);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
int err = errno;
|
|
382
|
+
sigset_t old;
|
|
383
|
+
nish_term_hold(&old);
|
|
384
|
+
int was = nish_term_raw;
|
|
385
|
+
nish_term_restore();
|
|
386
|
+
signal(sig, SIG_DFL);
|
|
387
|
+
__atomic_fetch_add(&nish_term_stops, 1, __ATOMIC_RELAXED);
|
|
388
|
+
nish_term_mask(SIG_UNBLOCK, 0);
|
|
389
|
+
raise(sig);
|
|
390
|
+
nish_term_mask(SIG_BLOCK, 0);
|
|
391
|
+
nish_term_catch(sig);
|
|
392
|
+
if (was) nish_term_enter(TCSANOW);
|
|
393
|
+
nish_term_drop(&old);
|
|
394
|
+
errno = err;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/* Put the atexit handler, the alternate stack and the signal handlers in
|
|
398
|
+
place, once, for raw mode and for restore bytes alike. Called with the
|
|
399
|
+
lock held. */
|
|
400
|
+
static void nish_term_arm(void) {
|
|
401
|
+
if (nish_term_armed) return;
|
|
402
|
+
nish_term_armed = 1;
|
|
403
|
+
atexit(nish_term_leave);
|
|
404
|
+
/* An alternate stack the thread already has, from C code the program
|
|
405
|
+
links, is kept. */
|
|
406
|
+
stack_t alt;
|
|
407
|
+
if (sigaltstack(0, &alt) == 0 && (alt.ss_flags & SS_DISABLE)) {
|
|
408
|
+
alt.ss_sp = nish_term_alt_stack;
|
|
409
|
+
alt.ss_size = sizeof nish_term_alt_stack;
|
|
410
|
+
alt.ss_flags = 0;
|
|
411
|
+
sigaltstack(&alt, 0);
|
|
412
|
+
}
|
|
413
|
+
/* Every signal whose default ends the program, the real-time ones and those
|
|
414
|
+
a system adds (Linux's SIGIO, SIGPWR and SIGSTKFLT, Darwin's SIGEMT)
|
|
415
|
+
included, and SIGTSTP; not SIGKILL and SIGSTOP, which no handler sees, nor
|
|
416
|
+
those a default ignores or that stop the program, since the handler raises
|
|
417
|
+
its signal again at the default and a program that went on would go on
|
|
418
|
+
with its terminal restored. Off Linux SIGIO's default ignores it. A signal
|
|
419
|
+
the program or the C it links already handles is left alone, and one the C
|
|
420
|
+
library keeps for itself refuses the `sigaction`. */
|
|
421
|
+
unsigned spare = 1u << SIGCHLD | 1u << SIGCONT | 1u << SIGURG | 1u << SIGWINCH | 1u << SIGTTIN | 1u << SIGTTOU;
|
|
422
|
+
#ifdef SIGINFO
|
|
423
|
+
spare |= 1u << SIGINFO;
|
|
424
|
+
#endif
|
|
425
|
+
#ifndef __linux__
|
|
426
|
+
spare |= 1u << SIGIO;
|
|
427
|
+
#endif
|
|
428
|
+
#ifdef SIGRTMAX
|
|
429
|
+
int last = SIGRTMAX;
|
|
430
|
+
#else
|
|
431
|
+
int last = 31;
|
|
432
|
+
#endif
|
|
433
|
+
for (int sig = 1; sig <= last; sig++) {
|
|
434
|
+
struct sigaction old;
|
|
435
|
+
if ((sig >= 32 || !(spare >> sig & 1)) && sigaction(sig, 0, &old) == 0 && old.sa_handler == SIG_DFL)
|
|
436
|
+
nish_term_catch(sig);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/* `setRawMode(on)`: 0, or -25 when stdin is not a terminal and the host's
|
|
441
|
+
`-errno` when `tcsetattr` refuses. Entering flushes the input a line
|
|
442
|
+
discipline had already buffered; leaving keeps it. Asking for the mode the
|
|
443
|
+
terminal is already in changes nothing. A program its shell runs in the
|
|
444
|
+
background is stopped by SIGTTOU here, as any program that sets the
|
|
445
|
+
terminal is, and goes on into raw mode on `fg`. */
|
|
446
|
+
static int32_t nish_term_set(bool on) {
|
|
447
|
+
if (!on) {
|
|
448
|
+
nish_term_restore();
|
|
449
|
+
return 0;
|
|
450
|
+
}
|
|
451
|
+
if (nish_term_raw) return 0;
|
|
452
|
+
if (!isatty(0)) return -25;
|
|
453
|
+
if (tcgetattr(0, &nish_term_saved) != 0) return -errno;
|
|
454
|
+
struct termios *t = &nish_term_raw_t;
|
|
455
|
+
*t = nish_term_saved;
|
|
456
|
+
t->c_iflag &= ~(tcflag_t)(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | INPCK | IXON);
|
|
457
|
+
t->c_cflag = (t->c_cflag & ~(tcflag_t)(CSIZE | PARENB)) | CS8;
|
|
458
|
+
t->c_lflag &= ~(tcflag_t)(ECHO | ECHONL | ICANON | IEXTEN);
|
|
459
|
+
t->c_cc[VMIN] = 1;
|
|
460
|
+
t->c_cc[VTIME] = 0;
|
|
461
|
+
nish_term_arm();
|
|
462
|
+
return nish_term_enter(TCSAFLUSH);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
int32_t nish_set_raw_mode(bool on) {
|
|
466
|
+
sigset_t old;
|
|
467
|
+
nish_term_hold(&old);
|
|
468
|
+
int32_t r = nish_term_set(on);
|
|
469
|
+
nish_term_drop(&old);
|
|
470
|
+
return r;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* `setTerminalRestore(bytes)`: keep a copy of `bytes` to write to stdout on
|
|
474
|
+
the way out, replacing what was kept; "" keeps nothing. 0, -7 past
|
|
475
|
+
`NISH_TERM_RESTORE_MAX` bytes and -25 when stdout is not a terminal, both
|
|
476
|
+
storing nothing. Once the bytes went out, as the program ends, it stores
|
|
477
|
+
nothing more and answers 0. The way out is each of `setRawMode`'s but
|
|
478
|
+
`setRawMode(false)` and a stop, which do not end the program, so the
|
|
479
|
+
bytes go before the termios and the handlers are put in place here too,
|
|
480
|
+
raw mode or not. */
|
|
481
|
+
int32_t nish_set_terminal_restore(const nish_str *bytes) {
|
|
482
|
+
uint64_t n = bytes->len;
|
|
483
|
+
if (n > NISH_TERM_RESTORE_MAX) return -7;
|
|
484
|
+
if (n > 0 && !isatty(1)) return -25;
|
|
485
|
+
sigset_t old;
|
|
486
|
+
nish_term_hold(&old);
|
|
487
|
+
int cur = __atomic_load_n(&nish_term_out, __ATOMIC_ACQUIRE);
|
|
488
|
+
if (cur >= 0) {
|
|
489
|
+
int next = 0;
|
|
490
|
+
if (n > 0) {
|
|
491
|
+
nish_term_arm();
|
|
492
|
+
memcpy(nish_term_bytes[nish_term_next], bytes->data, n);
|
|
493
|
+
next = (int)(n * 2) + nish_term_next;
|
|
494
|
+
nish_term_next ^= 1;
|
|
495
|
+
__atomic_store_n(&nish_fatal_hook, nish_term_leave, __ATOMIC_RELEASE);
|
|
496
|
+
}
|
|
497
|
+
/* A writer that took the bytes meanwhile left -1, which stays. */
|
|
498
|
+
__atomic_compare_exchange_n(&nish_term_out, &cur, next, 0, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
|
|
499
|
+
if (n == 0 && !nish_term_raw) __atomic_store_n(&nish_fatal_hook, 0, __ATOMIC_RELEASE);
|
|
500
|
+
}
|
|
501
|
+
nish_term_drop(&old);
|
|
502
|
+
return 0;
|
|
503
|
+
}
|
|
504
|
+
#endif
|
package/runtime/runtime-wasm.c
CHANGED
|
@@ -115,17 +115,6 @@ void nish_array_grow(nish_array *a, uint64_t elem_size) {
|
|
|
115
115
|
a->cap = cap;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
/* ---- Math.min and Math.max of a float: llvm.minnum / llvm.maxnum, which wasm32
|
|
119
|
-
* has no instruction for. Its `f64.min` answers NaN when either operand is NaN,
|
|
120
|
-
* and minnum answers the other operand, so the backend lowers minnum to a libm
|
|
121
|
-
* call, and these are that libm. They answer what minnum answers natively: a
|
|
122
|
-
* NaN operand is dropped, and two NaNs give NaN (tests/cases/math_minmax_float,
|
|
123
|
-
* held to these by tests/run.js). Between `0` and `-0` minnum may answer either. */
|
|
124
|
-
double fmin(double a, double b) { return a != a ? b : b != b ? a : a < b ? a : b; }
|
|
125
|
-
double fmax(double a, double b) { return a != a ? b : b != b ? a : a > b ? a : b; }
|
|
126
|
-
float fminf(float a, float b) { return a != a ? b : b != b ? a : a < b ? a : b; }
|
|
127
|
-
float fmaxf(float a, float b) { return a != a ? b : b != b ? a : a > b ? a : b; }
|
|
128
|
-
|
|
129
118
|
/* ---- Panics: `unreachable`, which the host sees as a RuntimeError */
|
|
130
119
|
void nish_panic_index(uint64_t idx, uint64_t len) { (void)idx; (void)len; __builtin_trap(); }
|
|
131
120
|
void nish_panic_slice(int64_t s, int64_t e, int64_t len) { (void)s; (void)e; (void)len; __builtin_trap(); }
|