@amritk/nish 0.11.0 → 0.13.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/README.md +43 -23
- package/bin/launcher.js +45 -41
- package/bin/packaging.js +39 -33
- package/docs/AI.md +135 -11
- package/docs/INSTALL.md +29 -13
- package/package.json +10 -6
- package/runtime/nish.d.ts +22 -1
- package/runtime/nish.h +31 -11
- package/runtime/nish.mjs +16 -3
- package/runtime/{runtime_os.c → runtime-os.c} +1 -1
- package/runtime/{runtime_parallel.c → runtime-parallel.c} +112 -4
- package/runtime/{runtime_wasm.c → runtime-wasm.c} +6 -1
- package/runtime/runtime.c +5 -5
- package/runtime/shim.mjs +6 -6
- package/scripts/bootstrap.sh +29 -29
- package/scripts/build.sh +14 -9
- package/scripts/changelog-gen.mjs +260 -192
- package/scripts/ci-profile.mjs +81 -68
- package/scripts/codes-registry.js +25 -13
- package/scripts/gen-diagnostic-codes.mjs +86 -69
- package/scripts/nish-compiler.sh +2 -0
- package/scripts/platform-package.mjs +26 -26
- package/scripts/postinstall.mjs +46 -36
- package/scripts/size-report.sh +3 -3
- package/scripts/smoke.sh +1 -1
- package/std/README.md +16 -11
- package/std/collections.ts +221 -178
- package/std/json.ts +136 -136
- package/std/map.ts +39 -0
- package/std/pair.ts +2 -2
- package/std/testing.ts +67 -67
- package/std/text.ts +54 -54
- package/std/threads.ts +126 -38
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/* Nish runtime, the parallel half: how one range of work becomes several
|
|
2
2
|
* threads, and nothing else.
|
|
3
3
|
*
|
|
4
|
-
* A third translation unit rather than a third of `
|
|
4
|
+
* A third translation unit rather than a third of `runtime-os.c`, for the
|
|
5
5
|
* reason that file's header gives for being a second one: a budget should mean
|
|
6
|
-
* one thing. `
|
|
6
|
+
* one thing. `runtime-os.c` is the syscall wrappers and it had 29 bytes of its
|
|
7
7
|
* ceiling left; `pthread_create` plus a partitioner does not fit in 29 bytes,
|
|
8
8
|
* and raising the syscall half's ceiling to make room would move the number a
|
|
9
9
|
* reader sees for "the operating-system surface" for a reason that has nothing
|
|
@@ -21,11 +21,16 @@
|
|
|
21
21
|
* function exists. The thread-local arena the divided case needs is WP20 T0 and
|
|
22
22
|
* arrives with the same macro.
|
|
23
23
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
24
|
+
* docs/wp29-thread-surface.md is the surface this is the stage under: P1's
|
|
25
|
+
* `parallelMapInto` and `parallelReduce` divide a range here, and P2's scopes
|
|
26
|
+
* run their tasks here (`nish_scope_spawn`, `nish_scope_join`).
|
|
26
27
|
*/
|
|
27
28
|
#include "nish.h"
|
|
28
29
|
|
|
30
|
+
#include <stdlib.h>
|
|
31
|
+
#include <string.h>
|
|
32
|
+
#include <unistd.h>
|
|
33
|
+
|
|
29
34
|
#if defined(NISH_THREADS) && !defined(__wasi__) && !defined(__wasm__)
|
|
30
35
|
#define NISH_PAR_REAL 1
|
|
31
36
|
#include <pthread.h>
|
|
@@ -154,3 +159,106 @@ void nish_parallel_range(nish_par_body body, void *ctx, int64_t len, int64_t gra
|
|
|
154
159
|
body(0, len, ctx);
|
|
155
160
|
#endif
|
|
156
161
|
}
|
|
162
|
+
|
|
163
|
+
/* ---- A scope's tasks (wp29 P2) ----
|
|
164
|
+
*
|
|
165
|
+
* A task is filed when it is spawned and run when its scope joins, which is
|
|
166
|
+
* what keeps a scope race-free without a borrow checker: while the tasks run,
|
|
167
|
+
* the thread that opened the scope is inside the join and runs nothing else,
|
|
168
|
+
* the tasks write nothing but their own payloads (the language's rule), and
|
|
169
|
+
* every store into the caller's memory is a `finish` made on the caller's
|
|
170
|
+
* thread after the last task has finished.
|
|
171
|
+
*
|
|
172
|
+
* The filed tasks are one list per thread, newest first, and each carries the
|
|
173
|
+
* scope it belongs to. Only the thread that opened a scope files into it or
|
|
174
|
+
* joins it, so the list needs no lock, and scopes may nest: a join takes its
|
|
175
|
+
* own scope's tasks out of the list and leaves the rest. */
|
|
176
|
+
typedef struct nish_task {
|
|
177
|
+
struct nish_task *next;
|
|
178
|
+
void *scope;
|
|
179
|
+
nish_task_fn run;
|
|
180
|
+
nish_task_fn finish;
|
|
181
|
+
#ifdef NISH_PAR_REAL
|
|
182
|
+
pthread_t th;
|
|
183
|
+
int started;
|
|
184
|
+
#endif
|
|
185
|
+
/* The copied payload, aligned for any field it holds. */
|
|
186
|
+
uint64_t payload[];
|
|
187
|
+
} nish_task;
|
|
188
|
+
|
|
189
|
+
static NISH_TLS nish_task *nish_tasks = 0;
|
|
190
|
+
|
|
191
|
+
void nish_scope_spawn(void *scope, nish_task_fn run, nish_task_fn finish, const void *payload, int64_t size) {
|
|
192
|
+
nish_task *t = (nish_task *)malloc(sizeof(nish_task) + (size_t)size);
|
|
193
|
+
/* Out of memory is the end of the program, as it is for every other
|
|
194
|
+
* allocation the runtime makes: running the task here instead would store
|
|
195
|
+
* its answer ahead of tasks filed before it, and a later spawn into the same
|
|
196
|
+
* slot has to be the one that stays. */
|
|
197
|
+
if (!t) {
|
|
198
|
+
(void)!write(2, "nish: out of memory\n", 20);
|
|
199
|
+
_exit(1);
|
|
200
|
+
}
|
|
201
|
+
t->scope = scope;
|
|
202
|
+
t->run = run;
|
|
203
|
+
t->finish = finish;
|
|
204
|
+
memcpy(t->payload, payload, (size_t)size);
|
|
205
|
+
t->next = nish_tasks;
|
|
206
|
+
nish_tasks = t;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
#ifdef NISH_PAR_REAL
|
|
210
|
+
static void *nish_task_worker(void *p) {
|
|
211
|
+
nish_task *t = (nish_task *)p;
|
|
212
|
+
/* A task is one thread's work, as a chunk is, so a region inside it runs on
|
|
213
|
+
* this thread rather than multiplying the threads. */
|
|
214
|
+
nish_par_depth = 1;
|
|
215
|
+
t->run(t->payload);
|
|
216
|
+
nish_free_arena(); /* as `nish_par_worker`: this thread's arena, and only it */
|
|
217
|
+
return 0;
|
|
218
|
+
}
|
|
219
|
+
#endif
|
|
220
|
+
|
|
221
|
+
void nish_scope_join(void *scope) {
|
|
222
|
+
/* This scope's tasks, oldest first: the list is newest first, and moving each
|
|
223
|
+
* one to the front of `mine` reverses it. */
|
|
224
|
+
nish_task *mine = 0;
|
|
225
|
+
nish_task **link = &nish_tasks;
|
|
226
|
+
while (*link) {
|
|
227
|
+
nish_task *t = *link;
|
|
228
|
+
if (t->scope == scope) {
|
|
229
|
+
*link = t->next;
|
|
230
|
+
t->next = mine;
|
|
231
|
+
mine = t;
|
|
232
|
+
} else {
|
|
233
|
+
link = &t->next;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
if (!mine) return;
|
|
237
|
+
#ifdef NISH_PAR_REAL
|
|
238
|
+
/* Every task but the first on a thread of its own, and the first on this
|
|
239
|
+
* one, so N tasks cost N-1 spawns and this thread is not idle. A task inside
|
|
240
|
+
* a region or another task's thread runs here, as a nested region does. */
|
|
241
|
+
for (nish_task *t = mine->next; t; t = t->next) {
|
|
242
|
+
t->started = nish_par_depth == 0 && pthread_create(&t->th, 0, nish_task_worker, t) == 0;
|
|
243
|
+
}
|
|
244
|
+
nish_par_depth++;
|
|
245
|
+
mine->run(mine->payload);
|
|
246
|
+
for (nish_task *t = mine->next; t; t = t->next) {
|
|
247
|
+
if (!t->started) t->run(t->payload);
|
|
248
|
+
}
|
|
249
|
+
nish_par_depth--;
|
|
250
|
+
for (nish_task *t = mine->next; t; t = t->next) {
|
|
251
|
+
if (t->started) pthread_join(t->th, 0);
|
|
252
|
+
}
|
|
253
|
+
#else
|
|
254
|
+
for (nish_task *t = mine; t; t = t->next) t->run(t->payload);
|
|
255
|
+
#endif
|
|
256
|
+
/* The stores, in the order the tasks were spawned, so two tasks with one
|
|
257
|
+
* destination leave the later one's answer there, as they would in sequence. */
|
|
258
|
+
while (mine) {
|
|
259
|
+
nish_task *t = mine;
|
|
260
|
+
mine = t->next;
|
|
261
|
+
t->finish(t->payload);
|
|
262
|
+
free(t);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/* Nish runtime for the freestanding wasm profile (WP8): the arena and the
|
|
2
2
|
* array cold paths, without libc. Link it next to the module when a function
|
|
3
3
|
* takes or returns an array:
|
|
4
|
-
* scripts/build.sh x.ll runtime/
|
|
4
|
+
* scripts/build.sh x.ll runtime/runtime-wasm.c -o x.wasm --profile wasm
|
|
5
5
|
*
|
|
6
6
|
* Linear memory past `__heap_base` (the linker's end-of-data symbol) is one
|
|
7
7
|
* arena chunk that grows with `memory.grow`; there is no chunk list because
|
|
@@ -97,3 +97,8 @@ void nish_panic_index(uint64_t idx, uint64_t len) { (void)idx; (void)len; __buil
|
|
|
97
97
|
void nish_panic_slice(int64_t s, int64_t e, int64_t len) { (void)s; (void)e; (void)len; __builtin_trap(); }
|
|
98
98
|
void nish_panic_div(_Bool by_zero) { (void)by_zero; __builtin_trap(); }
|
|
99
99
|
void nish_exit(int32_t code) { (void)code; __builtin_trap(); }
|
|
100
|
+
/* A panic's message (`panic`, `expect`, a failed range entry) has no stderr to
|
|
101
|
+
* go to here; the `nish_exit` that follows it is the trap. An exported function
|
|
102
|
+
* with a ranged parameter checks it on entry (WP31 §9), so this is what lets
|
|
103
|
+
* such a module link; the loader throws a RangeError before the call reaches it. */
|
|
104
|
+
void nish_write(const void *s, int32_t fd, _Bool newline) { (void)s; (void)fd; (void)newline; }
|
package/runtime/runtime.c
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
* it is also the half with the tighter size budget: the arena, strings, number
|
|
5
5
|
* formatting, the array cold paths, `process.argv`, `Math.random`, and the two
|
|
6
6
|
* panics. Files, directories, subprocesses, the environment and the clock are
|
|
7
|
-
* in
|
|
7
|
+
* in runtime-os.c — every one of those wraps a system call, so that surface
|
|
8
8
|
* grows as the language reaches further into the operating system, and a
|
|
9
9
|
* program that reaches nowhere should not pay for it or be measured with it.
|
|
10
|
-
*
|
|
10
|
+
* runtime-os.c's header comment has the reasoning; tests/run.js gates the two
|
|
11
11
|
* `.text*` budgets separately and docs/wp7-runtime.md records both.
|
|
12
12
|
*
|
|
13
|
-
* Nothing here calls into
|
|
13
|
+
* Nothing here calls into runtime-os.c, which is why an old link line that
|
|
14
14
|
* names runtime.c alone still builds a program that uses none of that surface.
|
|
15
15
|
* The other direction does happen: `nish_readdir` allocates through
|
|
16
16
|
* `nish_alloc_struct` and `nish_str_new`, so those calls no longer inline into
|
|
@@ -50,7 +50,7 @@ int __main_argc_argv(int argc, char **argv) { return nish_c_main(argc, argv); }
|
|
|
50
50
|
definition. Off by default so the ordinary build pays nothing.
|
|
51
51
|
|
|
52
52
|
The same definition is in runtime/nish.h (for a host that includes it) and
|
|
53
|
-
runtime/
|
|
53
|
+
runtime/runtime-wasm.c; they are one contract and move together. */
|
|
54
54
|
#ifdef NISH_THREADS
|
|
55
55
|
#define NISH_TLS _Thread_local
|
|
56
56
|
#else
|
|
@@ -59,7 +59,7 @@ int __main_argc_argv(int argc, char **argv) { return nish_c_main(argc, argv); }
|
|
|
59
59
|
|
|
60
60
|
/* ---- Arena: %struct.nish_arena = type { i8*, i64, i64, i8* }
|
|
61
61
|
|
|
62
|
-
The widths are fixed rather than `size_t` for the reason
|
|
62
|
+
The widths are fixed rather than `size_t` for the reason runtime-wasm.c
|
|
63
63
|
gives for its own copy: every compiled function inlines the bump allocator
|
|
64
64
|
and reads these fields directly, so the IR's `i64` is what `off` and `cap`
|
|
65
65
|
have to be on every target, not just the 64-bit ones. With `size_t` they
|
package/runtime/shim.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* The differential runner (tests/differential/run.js) rewrites an Nish
|
|
5
5
|
* program into plain JavaScript and runs it under Node with this module as
|
|
6
6
|
* `__nish`. Every helper here reproduces the *runtime* semantics the compiled
|
|
7
|
-
* binary has (runtime/runtime.c and runtime/
|
|
7
|
+
* binary has (runtime/runtime.c and runtime/runtime-os.c, the system-call half,
|
|
8
8
|
* plus the intrinsics in docs/wp7-runtime.md) where JavaScript's own semantics
|
|
9
9
|
* differ:
|
|
10
10
|
*
|
|
@@ -486,10 +486,10 @@ export function isDirectorySync(path) {
|
|
|
486
486
|
* the directory cannot be read. Node throws where the runtime answers a value,
|
|
487
487
|
* so the `catch` is what makes the two agree, and a directory that exists and
|
|
488
488
|
* is empty answers an empty array on both sides. Node's readdir never yields
|
|
489
|
-
* `.` or `..` — the pair `
|
|
489
|
+
* `.` or `..` — the pair `runtime-os.c` skips explicitly — so there is nothing
|
|
490
490
|
* to filter out here.
|
|
491
491
|
*
|
|
492
|
-
* The sort is the semantic point. `
|
|
492
|
+
* The sort is the semantic point. `runtime-os.c` orders the names with
|
|
493
493
|
* `strcmp`, which compares UTF-8 bytes, and `Array#sort` compares UTF-16 code
|
|
494
494
|
* units. The two agree on ASCII names and part company above the BMP, where a
|
|
495
495
|
* surrogate pair sorts below `U+E000`..`U+FFFF` in UTF-16 and above them in
|
|
@@ -526,7 +526,7 @@ export function realpathSync(path) {
|
|
|
526
526
|
|
|
527
527
|
/**
|
|
528
528
|
* `process.platform` / `process.arch` (WP14 §7a). Node's spellings are the
|
|
529
|
-
* ones `
|
|
529
|
+
* ones `runtime-os.c` answers with, so on any machine this compiler has a
|
|
530
530
|
* triple for the two runtimes give the same string; elsewhere the native build
|
|
531
531
|
* says `unknown` where Node names the platform, which is the one place they
|
|
532
532
|
* part.
|
|
@@ -553,7 +553,7 @@ export function getenv(name) {
|
|
|
553
553
|
* `spawnSync(argv)` and `spawnSyncTo(argv, out, err)` (WP14 D4), which are one
|
|
554
554
|
* run with its streams answered differently: the child's exit status, 128 + n
|
|
555
555
|
* when signal n killed it, -1 for an empty vector or a program that would not
|
|
556
|
-
* start. `
|
|
556
|
+
* start. `runtime-os.c` puts one `static nish_spawn_impl` behind both builtins
|
|
557
557
|
* for the same reason this module puts one function behind both helpers — the
|
|
558
558
|
* argument vector, the wait and the signal convention are written once and
|
|
559
559
|
* cannot drift between the two.
|
|
@@ -606,7 +606,7 @@ export function spawnSyncTo(argv, out, err) {
|
|
|
606
606
|
/**
|
|
607
607
|
* `monotonicNanos()`: `process.hrtime.bigint()`, a monotonic clock in
|
|
608
608
|
* nanoseconds (`CLOCK_MONOTONIC` on every platform that has it, which is the
|
|
609
|
-
* one `
|
|
609
|
+
* one `runtime-os.c` reads). The value is a BigInt because that is how an `i64`
|
|
610
610
|
* is held on this side.
|
|
611
611
|
*
|
|
612
612
|
* No rewrite can make a *reading* agree with a native run: both origins are
|
package/scripts/bootstrap.sh
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Build the self-hosted compiler: `
|
|
2
|
+
# Build the self-hosted compiler: `src/`, compiled by `src/`.
|
|
3
3
|
#
|
|
4
4
|
# scripts/bootstrap.sh [-o <exe>] [--stages 1|2|3] [--profile speed|size|debug]
|
|
5
5
|
# [--work <dir>] [--verify] [--quiet]
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
# parameter rather than a fixture (docs/wp19-stage0-retirement.md §3, G3):
|
|
9
9
|
#
|
|
10
10
|
# seed whatever compiles stage1 NISH_BOOTSTRAP, or build/seed
|
|
11
|
-
# stage1
|
|
12
|
-
# stage2
|
|
13
|
-
# stage3
|
|
11
|
+
# stage1 src/, built by the seed
|
|
12
|
+
# stage2 src/, built by stage1 the default output
|
|
13
|
+
# stage3 src/, built by stage2 --verify only
|
|
14
14
|
#
|
|
15
15
|
# NISH_BOOTSTRAP=<path> names the seed, the way GOROOT_BOOTSTRAP names the Go
|
|
16
16
|
# that builds Go. It is either a released `nish`, executed directly, or a Node
|
|
@@ -31,14 +31,14 @@
|
|
|
31
31
|
#
|
|
32
32
|
# --verify runs the equalities the proof is made of, byte for byte:
|
|
33
33
|
#
|
|
34
|
-
# IR(seed,
|
|
35
|
-
# IR(stage1,
|
|
34
|
+
# IR(seed, src/) == IR(stage1, src/) reported, never asserted
|
|
35
|
+
# IR(stage1, src/) == IR(stage2, src/) the fixed point: self-hosted
|
|
36
36
|
# stage3 == stage2 as files, on ELF and on Mach-O
|
|
37
37
|
# alike; see below for what the
|
|
38
38
|
# Mach-O half took
|
|
39
39
|
#
|
|
40
40
|
# The last two are properties of the working tree and of nothing else: whatever
|
|
41
|
-
# built stage1, the compiler `
|
|
41
|
+
# built stage1, the compiler `src/` describes has to agree with itself and
|
|
42
42
|
# then reproduce itself. Both are asserted whatever the seed is.
|
|
43
43
|
#
|
|
44
44
|
# The third is a raw byte comparison, and on Mach-O two links of the same input
|
|
@@ -59,22 +59,22 @@
|
|
|
59
59
|
# measurement, byte offsets and all.
|
|
60
60
|
#
|
|
61
61
|
# The first one is reported and not asserted. With a released seed it is one
|
|
62
|
-
# implementation at two points in time: the IR HEAD emits for `
|
|
62
|
+
# implementation at two points in time: the IR HEAD emits for `src/` is the
|
|
63
63
|
# IR the last release emitted for it. Nothing in that sentence is about
|
|
64
64
|
# bootstrapping. It is a freeze on codegen between releases, and it fails on
|
|
65
65
|
# exactly the changes a release cycle exists to carry: the first improvement to
|
|
66
|
-
# land broke it, when a flow-sensitive bounds analysis proved 46 of `
|
|
66
|
+
# land broke it, when a flow-sensitive bounds analysis proved 46 of `src/`'s
|
|
67
67
|
# 1,206 index checks redundant and 20 of 56 modules "differed" because the
|
|
68
68
|
# optimisation worked. So the difference is reported, and the report is
|
|
69
69
|
# information about this release rather than a verdict on the bootstrap.
|
|
70
70
|
#
|
|
71
|
-
# While `src/` existed, a stage0 seed made the same comparison the second half
|
|
71
|
+
# While stage0's `src/` existed, a stage0 seed made the same comparison the second half
|
|
72
72
|
# of Wheeler's diverse double-compiling, and it was asserted there
|
|
73
|
-
# (docs/wp19-stage0-retirement.md §1, G6). That claim went with `src/`.
|
|
73
|
+
# (docs/wp19-stage0-retirement.md §1, G6). That claim went with stage0's `src/`.
|
|
74
74
|
#
|
|
75
75
|
# What the seeded run buys is not that equality. The rolling freeze — "a
|
|
76
|
-
# construct added in 0.N cannot be used by `
|
|
77
|
-
# by stage1 being built at all: a `
|
|
76
|
+
# construct added in 0.N cannot be used by `src/` until 0.(N+1)" — is enforced
|
|
77
|
+
# by stage1 being built at all: a `src/` that reaches for something the seed
|
|
78
78
|
# has never heard of does not compile, does not link, and never gets as far as
|
|
79
79
|
# a comparison. That failure is loud here whatever the seed is, and it is the
|
|
80
80
|
# whole of what the seeded run proves about the freeze
|
|
@@ -115,7 +115,7 @@ usage: scripts/bootstrap.sh [-o <exe>] [--stages 1|2|3] [--profile speed|size|de
|
|
|
115
115
|
|
|
116
116
|
Builds the self-hosted compiler. The seed builds stage1, stage1 builds stage2
|
|
117
117
|
(the default output), stage2 builds stage3. --verify compares the IR each stage
|
|
118
|
-
emits for
|
|
118
|
+
emits for src/ and the stage2/stage3 binaries, byte for byte. The result is
|
|
119
119
|
the command line itself: -o, --link, --profile and the rest.
|
|
120
120
|
|
|
121
121
|
IR(seed) == IR(stage1) asks whether codegen has changed since the seed was
|
|
@@ -156,10 +156,10 @@ build_to=$stages
|
|
|
156
156
|
[ "$verify" -eq 1 ] && build_to=3
|
|
157
157
|
|
|
158
158
|
# The published npm package ships bin/, runtime/, scripts/ and std/ but not
|
|
159
|
-
#
|
|
159
|
+
# src/ -- and, since 0.6.0, no compiler of its own at all -- so say which file
|
|
160
160
|
# is missing rather than failing inside the compiler.
|
|
161
|
-
if [ ! -f
|
|
162
|
-
echo "bootstrap:
|
|
161
|
+
if [ ! -f src/compile.ts ]; then
|
|
162
|
+
echo "bootstrap: src/compile.ts is missing; run this from a checkout of the repository" >&2
|
|
163
163
|
exit 3
|
|
164
164
|
fi
|
|
165
165
|
|
|
@@ -211,7 +211,7 @@ else
|
|
|
211
211
|
[ -r "$seed" ] || seed_die "is not readable"
|
|
212
212
|
command -v node >/dev/null 2>&1 || seed_die "needs node on PATH, which is not there"
|
|
213
213
|
fi
|
|
214
|
-
# A seed that cannot answer `--version` cannot compile
|
|
214
|
+
# A seed that cannot answer `--version` cannot compile src/ either — a
|
|
215
215
|
# binary built for another platform, a .js that is not a compiler — and
|
|
216
216
|
# finding that out here names the variable and the path the caller set, where
|
|
217
217
|
# finding it out in the stage1 link names a temporary file three stages deep.
|
|
@@ -223,7 +223,7 @@ fi
|
|
|
223
223
|
|
|
224
224
|
say() { [ "$quiet" -eq 1 ] || printf '%s\n' "$*"; }
|
|
225
225
|
|
|
226
|
-
# `a` and `b` hold one `.ll` per module of `
|
|
226
|
+
# `a` and `b` hold one `.ll` per module of `src/`. Both the module set and
|
|
227
227
|
# every byte of every module must match: a stage that emitted one module fewer
|
|
228
228
|
# has not agreed about the rest.
|
|
229
229
|
compare_ir() {
|
|
@@ -260,11 +260,11 @@ survey_ir() {
|
|
|
260
260
|
done
|
|
261
261
|
if [ "$differing" -eq 0 ]; then
|
|
262
262
|
say " note: IR(seed) vs IR(stage1): all $total modules identical."
|
|
263
|
-
say " Not asserted: codegen simply has not moved for
|
|
263
|
+
say " Not asserted: codegen simply has not moved for src/ since"
|
|
264
264
|
say " the seed was built."
|
|
265
265
|
else
|
|
266
266
|
say " note: IR(seed) vs IR(stage1): $differing of $total modules differ."
|
|
267
|
-
say " Not a bootstrap failure. Codegen has moved for
|
|
267
|
+
say " Not a bootstrap failure. Codegen has moved for src/ since the"
|
|
268
268
|
say " seed was built, which is what a release carries. See the header"
|
|
269
269
|
say " of this script and docs/wp19-stage0-retirement.md §3, G3."
|
|
270
270
|
fi
|
|
@@ -295,8 +295,8 @@ survey_ir() {
|
|
|
295
295
|
link_stage() {
|
|
296
296
|
local by="$1" name="$2"
|
|
297
297
|
rm -rf "$work/stage" "$work/stage.modules"
|
|
298
|
-
"$by"
|
|
299
|
-
rm -rf "$work/$name" "$work/$name.modules"
|
|
298
|
+
"$by" src/compile.ts --link "$work/stage" --profile "$profile" >/dev/null
|
|
299
|
+
rm -rf "${work:?}/${name:?}" "${work:?}/${name:?}.modules"
|
|
300
300
|
mv "$work/stage" "$work/$name"
|
|
301
301
|
mv "$work/stage.modules" "$work/$name.modules"
|
|
302
302
|
}
|
|
@@ -310,28 +310,28 @@ outdir=$(dirname "$out")
|
|
|
310
310
|
# `scripts/build.sh` itself (docs/wp14-selfhost.md §7a). That leaves each
|
|
311
311
|
# stage's IR in `<exe>.modules/`, which is where the equalities read it, and
|
|
312
312
|
# means the chain exercises the same driver a user does.
|
|
313
|
-
say "stage1:
|
|
313
|
+
say "stage1: src/ compiled by $seed_label"
|
|
314
314
|
rm -rf "$work/stage1.modules"
|
|
315
315
|
# This is the step the rolling freeze is enforced by, so it says so when it
|
|
316
316
|
# fails rather than leaving a reader with the compiler's own diagnostic and no
|
|
317
317
|
# idea which rule it just met.
|
|
318
|
-
if ! run_seed
|
|
319
|
-
echo "bootstrap: $seed_label could not build stage1 from
|
|
318
|
+
if ! run_seed src/compile.ts --link "$work/stage1" --profile "$profile" >/dev/null; then
|
|
319
|
+
echo "bootstrap: $seed_label could not build stage1 from src/ (its output is above)" >&2
|
|
320
320
|
echo "bootstrap: if it refused the source, that is the freeze doing its job: a construct" >&2
|
|
321
|
-
echo "bootstrap: added in 0.N cannot be used by
|
|
321
|
+
echo "bootstrap: added in 0.N cannot be used by src/ until 0.(N+1) (docs/wp12-release.md," >&2
|
|
322
322
|
echo "bootstrap: \"The bootstrap seed\"; docs/wp19-stage0-retirement.md §3, G3)" >&2
|
|
323
323
|
exit 1
|
|
324
324
|
fi
|
|
325
325
|
|
|
326
326
|
if [ "$build_to" -ge 2 ]; then
|
|
327
|
-
say "stage2:
|
|
327
|
+
say "stage2: src/ compiled by stage1"
|
|
328
328
|
link_stage "$work/stage1" stage2
|
|
329
329
|
# The seed equality, reported and not asserted.
|
|
330
330
|
[ "$verify" -eq 1 ] && survey_ir "$work/stage1.modules" "$work/stage2.modules"
|
|
331
331
|
fi
|
|
332
332
|
|
|
333
333
|
if [ "$build_to" -ge 3 ]; then
|
|
334
|
-
say "stage3:
|
|
334
|
+
say "stage3: src/ compiled by stage2"
|
|
335
335
|
link_stage "$work/stage2" stage3
|
|
336
336
|
[ "$verify" -eq 1 ] && compare_ir "$work/stage2.modules" "$work/stage3.modules" "IR(stage1) == IR(stage2)"
|
|
337
337
|
# `stage3 == stage2`, in scripts/verify-binaries.sh: byte-identical, which
|
package/scripts/build.sh
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
# scripts/build.sh <module.ll> [more .ll/.c files...] -o <out> [--profile debug|speed|size|wasm]
|
|
5
5
|
#
|
|
6
6
|
# The C runtime is three translation units and is named as one: an input
|
|
7
|
-
# <dir>/runtime.c also compiles <dir>/
|
|
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
|
-
# and <dir>/
|
|
9
|
+
# and <dir>/runtime-parallel.c, the half that divides a range of work across
|
|
10
10
|
# threads. Each of those files says why they are compiled and measured apart.
|
|
11
11
|
#
|
|
12
12
|
# Profiles:
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
# size -Oz + LTO + section GC + strip + no unwind tables. Rust
|
|
16
16
|
# `opt-level="z"`, `panic="abort"`, `strip=true` equivalent.
|
|
17
17
|
# wasm wasm32 freestanding module exporting every non-internal function;
|
|
18
|
-
# load it from Node. Add runtime/
|
|
18
|
+
# load it from Node. Add runtime/runtime-wasm.c to the inputs when a
|
|
19
19
|
# function uses arrays (the arena and the array cold paths, no libc);
|
|
20
20
|
# strings and I/O still need a WASI runtime and are not available.
|
|
21
21
|
# wasm wasm32 freestanding module exporting every non-internal function
|
|
@@ -84,12 +84,12 @@ done
|
|
|
84
84
|
[ -n "$out" ] || { echo "error: -o <out> is required" >&2; exit 2; }
|
|
85
85
|
|
|
86
86
|
# The runtime is three translation units, and a caller names one: whoever passes
|
|
87
|
-
# <dir>/runtime.c gets <dir>/
|
|
87
|
+
# <dir>/runtime.c gets <dir>/runtime-os.c and <dir>/runtime-parallel.c compiled
|
|
88
88
|
# beside it. They were one file until the operating-system half was split out
|
|
89
89
|
# for its own size budget, and the parallel half followed for the same reason
|
|
90
90
|
# (each file's header comment says why), and a link line is where those splits
|
|
91
91
|
# would otherwise leak: `nish --link` builds its command line in
|
|
92
|
-
#
|
|
92
|
+
# src/compile.ts, the published package's recipe in every document and
|
|
93
93
|
# README names runtime.c, and a user's own clang line does too. Pairing them
|
|
94
94
|
# here keeps every one of those correct, and keeps "the runtime" one thing to
|
|
95
95
|
# name from the outside. A caller that names one itself is left alone, because
|
|
@@ -97,7 +97,7 @@ done
|
|
|
97
97
|
for i in ${inputs[@]+"${inputs[@]}"}; do
|
|
98
98
|
case "$i" in
|
|
99
99
|
*/runtime.c|runtime.c)
|
|
100
|
-
for half in
|
|
100
|
+
for half in runtime-os.c runtime-parallel.c; do
|
|
101
101
|
side="${i%runtime.c}$half"
|
|
102
102
|
have=0
|
|
103
103
|
for j in "${inputs[@]}"; do
|
|
@@ -133,8 +133,12 @@ case "$(uname -s)" in
|
|
|
133
133
|
# of one input to one output path, and differing on a link to another. That
|
|
134
134
|
# is what scripts/bootstrap.sh links every comparable stage at one path for,
|
|
135
135
|
# and it is why `stage3 == stage2` holds on Mach-O with the load command in.
|
|
136
|
-
|
|
136
|
+
# shellcheck disable=SC2054 # -Wl,<flag> is one argument: the comma is the linker's
|
|
137
|
+
gc=(-Wl,-dead_strip)
|
|
138
|
+
# shellcheck disable=SC2054
|
|
139
|
+
strip_flag=(-Wl,-x) ;;
|
|
137
140
|
*)
|
|
141
|
+
# shellcheck disable=SC2054 # -Wl,<flag> is one argument: the comma is the linker's
|
|
138
142
|
gc=(-Wl,--gc-sections -Wl,--as-needed -Wl,-O2 -Wl,--build-id=none); strip_flag=(-s)
|
|
139
143
|
elf=(-fno-plt)
|
|
140
144
|
# GNU ld needs the gold plugin for LTO; prefer lld when clang can find it.
|
|
@@ -158,7 +162,7 @@ if [ "$debug" = 1 ]; then common+=(-g); strip_flag=(); fi
|
|
|
158
162
|
# own command line instead of using `common`; it is empty on every ordinary
|
|
159
163
|
# build, hence the bash 3.2 expansion spelling explained below.
|
|
160
164
|
#
|
|
161
|
-
# -pthread is for
|
|
165
|
+
# -pthread is for runtime-parallel.c, the translation unit that divides a range
|
|
162
166
|
# of work across threads: it is compiled in either configuration and only spawns
|
|
163
167
|
# under this macro, so this is the build where the flag has to be on the command
|
|
164
168
|
# line. On a current glibc the library half is already inside libc and the link
|
|
@@ -167,7 +171,7 @@ if [ "$debug" = 1 ]; then common+=(-g); strip_flag=(); fi
|
|
|
167
171
|
#
|
|
168
172
|
# It is deliberately in `common` and not in `tls`: `tls` is the wasm and wasi
|
|
169
173
|
# command lines, which have no threads to link against, and where
|
|
170
|
-
#
|
|
174
|
+
# runtime-parallel.c compiles to its sequential fallback because it tests
|
|
171
175
|
# __wasi__ and __wasm__ as well as the macro.
|
|
172
176
|
tls=()
|
|
173
177
|
if [ "$threads" = 1 ]; then
|
|
@@ -265,6 +269,7 @@ case "$profile" in
|
|
|
265
269
|
shared=(-shared -fPIC)
|
|
266
270
|
# macOS: the napi_* symbols come from the node binary at load time, so the
|
|
267
271
|
# linker must not insist on resolving them. ELF shared objects allow this.
|
|
272
|
+
# shellcheck disable=SC2054 # -Wl,<flag> is one argument: the comma is the linker's
|
|
268
273
|
case "$(uname -s)" in Darwin) shared+=(-Wl,-undefined,dynamic_lookup) ;; esac
|
|
269
274
|
# runtime/nish.h is the public ABI header the generated shim includes.
|
|
270
275
|
runtime_inc="$(cd "$(dirname "$0")/../runtime" && pwd)"
|