@amritk/nish-x86_64-linux 0.26.0 → 0.28.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.
@@ -0,0 +1,239 @@
1
+ /* Nish runtime, the SHA-256 half: the block function of FIPS 180-4 §6.2.2 on
2
+ * the CPU's own SHA-256 instructions. std/crypto/sha256.ts is the hash; this is
3
+ * a faster body for its private `sha256Compress`, which the compiler calls in
4
+ * that function's place when `nish_sha256_hw` says this machine can run it
5
+ * (src/kernels.ts).
6
+ *
7
+ * A translation unit of its own for the reason runtime-aes.c is one: its own
8
+ * measured `.text*` ceiling in tests/runtime-budgets.js, and a program that
9
+ * hashes links no AES and one that encrypts no SHA-256. Section GC means a
10
+ * program that never calls the kernel links none of this file.
11
+ *
12
+ * There are two paths and no portable one. x86-64 runs the SHA extensions
13
+ * (SHA-NI: `sha256rnds2`, `sha256msg1`, `sha256msg2`) with SSSE3's byte shuffle
14
+ * and SSE4.1's word blend, compiled for those extensions alone with
15
+ * `target(...)` so the binary stays a baseline binary; AArch64 runs the ARMv8
16
+ * Cryptography Extension's SHA-256 instructions, likewise with
17
+ * `target("+sha2")`. A machine without them, or a process with
18
+ * `NISH_SHA256=soft`, runs the Nish code in std/crypto/sha256.ts instead: the
19
+ * compiler keeps that code and branches on `nish_sha256_hw` at each call. Every
20
+ * other target, wasm32 included, compiles `nish_sha256_hw` to `return 0` and
21
+ * the kernel to a trap nothing reaches, so a link that names it still resolves.
22
+ *
23
+ * The kernel checks the guard `sha256Compress` runs its body under and returns
24
+ * 0 without touching anything when it fails, so the caller runs the Nish
25
+ * function, which panics with its own message: both paths fail the same way.
26
+ *
27
+ * Constant time: the kernel's branches are on array headers and `at`, which are
28
+ * public, and its loads and stores are at addresses fixed by them; the
29
+ * round-constant table is read whole, in order. tests/ct-asm-runtime.js reads
30
+ * the `clang -O2` assembly of this file for both targets and holds the kernel
31
+ * to that. The one branch on anything else is `nish_sha256_hw`'s, on the CPU's
32
+ * feature bits and `NISH_SHA256` (docs/security/ct-verification.md).
33
+ *
34
+ * Wiping: the kernel keeps no buffer of its own; the state and the message
35
+ * words live in vector registers between the load from `src` and the store to
36
+ * `h`. What sits in those registers, and what the register allocator spills,
37
+ * no store from C can reach (docs/security/crypto-sha256-hw.md).
38
+ *
39
+ * The algorithm is FIPS 180-4 §6.2.2; the instruction sequences are the ones
40
+ * the instructions are defined for, written here from Intel's "Intel SHA
41
+ * Extensions" (S. Gulley et al., 2013) and Arm's description of SHA256H,
42
+ * SHA256H2, SHA256SU0 and SHA256SU1 (Arm ARM, DDI 0487).
43
+ */
44
+ #include <stddef.h>
45
+ #include <stdint.h>
46
+
47
+ #include "nish.h"
48
+
49
+ #if defined(__x86_64__)
50
+ #include <cpuid.h>
51
+ #include <immintrin.h>
52
+ #define NISH_SHA256_X86 1
53
+ #elif defined(__aarch64__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
54
+ #include <arm_neon.h>
55
+ #define NISH_SHA256_ARM 1
56
+ #endif
57
+
58
+ #if defined(NISH_SHA256_X86) || defined(NISH_SHA256_ARM)
59
+
60
+ /* Named here rather than through <stdlib.h>, for runtime-aes.c's reason: the
61
+ constant-time check compiles this file for targets whose libc headers the
62
+ host does not have. */
63
+ char *getenv(const char *name);
64
+
65
+ #if defined(__has_attribute) && __has_attribute(no_builtin)
66
+ #define NISH_SHA256_NO_LIBC no_builtin
67
+ #else
68
+ #define NISH_SHA256_NO_LIBC
69
+ #endif
70
+ #if defined(NISH_SHA256_X86)
71
+ #define NISH_SHA256_TARGET __attribute__((target("sha,sse4.1,ssse3"), NISH_SHA256_NO_LIBC))
72
+ #else
73
+ #define NISH_SHA256_TARGET __attribute__((target("+sha2"), NISH_SHA256_NO_LIBC))
74
+ #endif
75
+ #define NISH_SHA256_INLINE static inline __attribute__((always_inline)) NISH_SHA256_TARGET
76
+
77
+ /* K of FIPS 180-4 §4.2.2: the first 32 bits of the fractional parts of the cube
78
+ roots of the first sixty-four primes. */
79
+ static const uint32_t nish_sha256_k[64] __attribute__((aligned(16))) = {
80
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
81
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
82
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
83
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
84
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
85
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
86
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
87
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
88
+ };
89
+
90
+ #if defined(NISH_SHA256_X86)
91
+ /* One block. `sha256rnds2` runs two rounds on the state split as ABEF and CDGH
92
+ (the words high lane to low), taking W + K for the two rounds from its third
93
+ operand's low two lanes, so the eight words are shuffled into that order
94
+ once, on the way in, and back on the way out. */
95
+ NISH_SHA256_INLINE void nish_sha256_block(char *state, const char *in) {
96
+ /* Each of the sixteen bytes' places in a big-endian word read little-endian. */
97
+ const __m128i bswap = _mm_set_epi64x(0x0c0d0e0f08090a0bLL, 0x0405060700010203LL);
98
+ __m128i dcba = _mm_loadu_si128((const __m128i *)state);
99
+ __m128i hgfe = _mm_loadu_si128((const __m128i *)(state + 16));
100
+ __m128i cdab = _mm_shuffle_epi32(dcba, 0xb1);
101
+ __m128i efgh = _mm_shuffle_epi32(hgfe, 0x1b);
102
+ __m128i abef = _mm_alignr_epi8(cdab, efgh, 8);
103
+ __m128i cdgh = _mm_blend_epi16(efgh, cdab, 0xf0);
104
+ const __m128i abef0 = abef;
105
+ const __m128i cdgh0 = cdgh;
106
+ /* W in four-word groups, a ring of the last four: group i is W[4i .. 4i + 4). */
107
+ __m128i w[4];
108
+ for (int i = 0; i < 16; i++) {
109
+ if (i < 4) {
110
+ w[i] = _mm_shuffle_epi8(_mm_loadu_si128((const __m128i *)(in + 16 * i)), bswap);
111
+ } else {
112
+ /* (6.2) for t = 4i .. 4i + 3: msg1 adds σ0 of the next word to W[t - 16],
113
+ the align brings in W[t - 7], and msg2 adds σ1 of W[t - 2], two of
114
+ which it computes itself from the words before. */
115
+ __m128i w7 = _mm_alignr_epi8(w[(i - 1) & 3], w[(i - 2) & 3], 4);
116
+ w[i & 3] = _mm_sha256msg2_epu32(_mm_add_epi32(_mm_sha256msg1_epu32(w[i & 3], w[(i - 3) & 3]), w7),
117
+ w[(i - 1) & 3]);
118
+ }
119
+ __m128i wk = _mm_add_epi32(w[i & 3], _mm_load_si128((const __m128i *)&nish_sha256_k[4 * i]));
120
+ cdgh = _mm_sha256rnds2_epu32(cdgh, abef, wk);
121
+ abef = _mm_sha256rnds2_epu32(abef, cdgh, _mm_shuffle_epi32(wk, 0x0e));
122
+ }
123
+ abef = _mm_add_epi32(abef, abef0);
124
+ cdgh = _mm_add_epi32(cdgh, cdgh0);
125
+ __m128i feba = _mm_shuffle_epi32(abef, 0x1b);
126
+ __m128i dchg = _mm_shuffle_epi32(cdgh, 0xb1);
127
+ _mm_storeu_si128((__m128i *)state, _mm_blend_epi16(feba, dchg, 0xf0));
128
+ _mm_storeu_si128((__m128i *)(state + 16), _mm_alignr_epi8(dchg, feba, 8));
129
+ }
130
+ #else
131
+ /* One block. SHA256H and SHA256H2 run four rounds on ABCD and EFGH together,
132
+ each giving one half of the new state, with W + K for the four in their
133
+ third operand. */
134
+ NISH_SHA256_INLINE void nish_sha256_block(char *state, const char *in) {
135
+ uint32x4_t abcd = vld1q_u32((const uint32_t *)state);
136
+ uint32x4_t efgh = vld1q_u32((const uint32_t *)(state + 16));
137
+ const uint32x4_t abcd0 = abcd;
138
+ const uint32x4_t efgh0 = efgh;
139
+ uint32x4_t w[4];
140
+ for (int i = 0; i < 16; i++) {
141
+ if (i < 4) {
142
+ w[i] = vreinterpretq_u32_u8(vrev32q_u8(vld1q_u8((const uint8_t *)(in + 16 * i))));
143
+ } else {
144
+ /* (6.2) for t = 4i .. 4i + 3: SU0 adds σ0 to W[t - 16], SU1 adds W[t - 7]
145
+ and σ1 of W[t - 2]. */
146
+ w[i & 3] = vsha256su1q_u32(vsha256su0q_u32(w[i & 3], w[(i - 3) & 3]), w[(i - 2) & 3], w[(i - 1) & 3]);
147
+ }
148
+ uint32x4_t wk = vaddq_u32(w[i & 3], vld1q_u32(&nish_sha256_k[4 * i]));
149
+ uint32x4_t before = abcd;
150
+ abcd = vsha256hq_u32(abcd, efgh, wk);
151
+ efgh = vsha256h2q_u32(efgh, before, wk);
152
+ }
153
+ vst1q_u32((uint32_t *)state, vaddq_u32(abcd, abcd0));
154
+ vst1q_u32((uint32_t *)(state + 16), vaddq_u32(efgh, efgh0));
155
+ }
156
+ #endif
157
+
158
+ /* `sha256Compress`'s own guard, then the block. That guard reads the source's
159
+ length through `toI32`, which saturates at 2^31 - 1, so it refuses a block
160
+ ending past that offset whatever the length: `at > INT32_MAX - 64`. */
161
+ NISH_SHA256_TARGET int32_t nish_sha256_compress(nish_array *h, const nish_array *w, const nish_array *src,
162
+ int32_t at) {
163
+ if (h->len < 8 || w->len < 64 || at < 0 || at > INT32_MAX - 64 || src->len < 64 || (uint64_t)at > src->len - 64)
164
+ return 0;
165
+ nish_sha256_block(h->data, src->data + at);
166
+ return 1;
167
+ }
168
+
169
+ #if defined(NISH_SHA256_X86)
170
+ /* Whether this CPU runs the kernel: CPUID leaf 7's EBX has SHA (bit 29), and
171
+ leaf 1's ECX SSE4.1 (bit 19) and SSSE3 (bit 9). Asked through `cpuid.h`, for
172
+ runtime-aes.c's reason; `__get_cpuid_count` answers 0 when leaf 7 is past
173
+ the CPU's highest. */
174
+ static int32_t nish_sha256_cpu(void) {
175
+ unsigned a, b, c, d;
176
+ if (!__get_cpuid(1, &a, &b, &c, &d)) return 0;
177
+ if ((c & bit_SSE4_1) == 0 || (c & bit_SSSE3) == 0) return 0;
178
+ if (!__get_cpuid_count(7, 0, &a, &b, &c, &d)) return 0;
179
+ return (b & bit_SHA) != 0;
180
+ }
181
+ #elif defined(__linux__)
182
+ /* Linux reports the ARMv8 SHA-256 instructions in the auxiliary vector:
183
+ AT_HWCAP is 16, and its HWCAP_SHA2 bit is 6
184
+ (arch/arm64/include/uapi/asm/hwcap.h). */
185
+ unsigned long getauxval(unsigned long type);
186
+
187
+ static int32_t nish_sha256_cpu(void) {
188
+ return (getauxval(16) & (1ul << 6)) != 0;
189
+ }
190
+ #elif defined(__APPLE__)
191
+ /* macOS names the feature in sysctl (macOS 12 and later). A system that does
192
+ not know the name answers as a CPU without it: the Nish path. */
193
+ int sysctlbyname(const char *name, void *oldp, size_t *oldlenp, void *newp, size_t newlen);
194
+
195
+ static int32_t nish_sha256_cpu(void) {
196
+ int32_t has = 0;
197
+ size_t size = sizeof has;
198
+ if (sysctlbyname("hw.optional.arm.FEAT_SHA256", &has, &size, 0, 0) != 0) return 0;
199
+ return has != 0;
200
+ }
201
+ #else
202
+ /* Another AArch64 system: no question asked, so the Nish path. */
203
+ static int32_t nish_sha256_cpu(void) {
204
+ return 0;
205
+ }
206
+ #endif
207
+
208
+ /* The answer: 0 until the first call, then 1 for the kernel or 2 for the Nish
209
+ path, with runtime-aes.c's relaxed atomics for the same reason. */
210
+ static int32_t nish_sha256_answer;
211
+
212
+ int32_t nish_sha256_hw(void) {
213
+ int32_t answer = __atomic_load_n(&nish_sha256_answer, __ATOMIC_RELAXED);
214
+ if (answer == 0) {
215
+ /* `soft` pins the Nish path and never asks the CPU; anything else, or
216
+ nothing, means the best this machine has. */
217
+ const char *pin = getenv("NISH_SHA256");
218
+ int soft = pin != 0 && pin[0] == 's' && pin[1] == 'o' && pin[2] == 'f' && pin[3] == 't' && pin[4] == 0;
219
+ answer = !soft && nish_sha256_cpu() ? 1 : 2;
220
+ __atomic_store_n(&nish_sha256_answer, answer, __ATOMIC_RELAXED);
221
+ }
222
+ return answer == 1;
223
+ }
224
+
225
+ #else
226
+
227
+ /* A target with no SHA-256 instructions this file uses, wasm32 among them: the
228
+ Nish path always, and a kernel that a program whose every call is behind
229
+ `nish_sha256_hw` never reaches. */
230
+ int32_t nish_sha256_hw(void) {
231
+ return 0;
232
+ }
233
+
234
+ int32_t nish_sha256_compress(nish_array *h, const nish_array *w, const nish_array *src, int32_t at) {
235
+ (void)h, (void)w, (void)src, (void)at;
236
+ __builtin_trap();
237
+ }
238
+
239
+ #endif
@@ -1,5 +1,6 @@
1
1
  /* Nish runtime, the vector half: byte searches that read sixteen or thirty-two
2
- * bytes at a time (WP38 S1, docs/wp38-simd.md §3.1).
2
+ * bytes at a time (WP38 S1, docs/wp38-simd.md §3.1), and the SSE4.1 rounding
3
+ * behind `Math.floor`, `Math.ceil` and `Math.trunc` (#943, at the end).
3
4
  *
4
5
  * A translation unit of its own for the reason runtime-host.c is one: each file
5
6
  * carries its own measured `.text*` ceiling in tests/runtime-budgets.js, and
@@ -176,10 +177,17 @@ static int nish_cpu_has_avx2(void) {
176
177
  `base` never reads the CPU's feature bits at all. */
177
178
  enum { NISH_PATH_SCALAR, NISH_PATH_BASE, NISH_PATH_AVX2 };
178
179
 
179
- static int nish_simd_choose(void) {
180
+ /* The path `NISH_SIMD` pins, or -1 when it pins none. */
181
+ static int nish_simd_pin(void) {
180
182
  const char *pin = getenv("NISH_SIMD");
181
183
  if (pin != NULL && strcmp(pin, "scalar") == 0) return NISH_PATH_SCALAR;
182
184
  if (pin != NULL && strcmp(pin, "base") == 0) return NISH_PATH_BASE;
185
+ return -1;
186
+ }
187
+
188
+ static int nish_simd_choose(void) {
189
+ int pin = nish_simd_pin();
190
+ if (pin >= 0) return pin;
183
191
  #if defined(NISH_SIMD_SSE2)
184
192
  if (nish_cpu_has_avx2()) return NISH_PATH_AVX2;
185
193
  #endif
@@ -425,3 +433,56 @@ int64_t nish_regex_scan(const nish_str *s, const nish_array *masks, int64_t dept
425
433
  int64_t from) {
426
434
  return __atomic_load_n(&nish_scan, __ATOMIC_RELAXED)(s, masks, depth, off, width, from);
427
435
  }
436
+
437
+ /* `Math.floor`, `Math.ceil` and `Math.trunc`, and the floor `Math.round` takes
438
+ (#943), the contract in nish.h. SSE4.1's `roundsd` answers each in one
439
+ instruction, but the x86-64 baseline is SSE2, where LLVM lowers
440
+ `llvm.floor.f64` to a call of libm's `floor` through the GOT. So a module
441
+ for x86-64 calls these instead (`roundingCallee`, src/emit-builtins.ts) and
442
+ registers a constructor that hands `nish_round_init` libm's three routines;
443
+ it asks the CPU once, before `main`, and keeps them only when the answer is
444
+ no. The runtime names no libm symbol itself, so a link without `-lm` (a C
445
+ host, a program that never rounds) still resolves: the module that rounds
446
+ brings the reference, as it did before. A null slot is the instruction and
447
+ is never written after the constructor, so on a CPU without SSE4.1 each
448
+ call is an indirect jump to libm, the path a module took before.
449
+ `NISH_SIMD=scalar` keeps libm, the forced fallback a test runs. `base` does
450
+ not: the instruction gate counts under it, and glibc's own `floor` takes
451
+ `roundsd` there as well, so the count is of the instruction both call.
452
+ Built for a machine that has SSE4.1 already (`--cpu x86-64-v3`), or for any
453
+ other, each is the builtin, which is the one instruction there, and the
454
+ constructor keeps nothing. The immediate is the rounding mode, 1 down, 2 up,
455
+ 3 toward zero, plus 8, which keeps the precision exception quiet as libm's
456
+ `floor` does. */
457
+ #if defined(NISH_SIMD_SSE2) && !defined(__SSE4_1__)
458
+ static double (*nish_round_libm[3])(double);
459
+
460
+ void nish_round_init(double (*floor_fn)(double), double (*ceil_fn)(double), double (*trunc_fn)(double)) {
461
+ unsigned a, b, c, d;
462
+ if (nish_simd_pin() != NISH_PATH_SCALAR && __get_cpuid(1, &a, &b, &c, &d) && (c & bit_SSE4_1)) return;
463
+ nish_round_libm[0] = floor_fn;
464
+ nish_round_libm[1] = ceil_fn;
465
+ nish_round_libm[2] = trunc_fn;
466
+ }
467
+
468
+ #define NISH_ROUNDING(name, base, slot, mode) \
469
+ double name(double x) { \
470
+ double (*libm)(double) = nish_round_libm[slot]; \
471
+ if (libm) return libm(x); \
472
+ __asm__("roundsd $" #mode ", %0, %0" : "+x"(x)); \
473
+ return x; \
474
+ }
475
+ #else
476
+ void nish_round_init(double (*floor_fn)(double), double (*ceil_fn)(double), double (*trunc_fn)(double)) {
477
+ (void)floor_fn;
478
+ (void)ceil_fn;
479
+ (void)trunc_fn;
480
+ }
481
+
482
+ #define NISH_ROUNDING(name, base, slot, mode) \
483
+ double name(double x) { return __builtin_##base(x); }
484
+ #endif
485
+
486
+ NISH_ROUNDING(nish_floor, floor, 0, 9)
487
+ NISH_ROUNDING(nish_ceil, ceil, 1, 10)
488
+ NISH_ROUNDING(nish_trunc, trunc, 2, 11)
@@ -128,3 +128,21 @@ void nish_exit(int32_t code) { (void)code; __builtin_trap(); }
128
128
  void nish_panic(const void *s) { (void)s; __builtin_trap(); }
129
129
  /* Output has nowhere to go either. */
130
130
  void nish_write(const void *s, int32_t fd, _Bool newline) { (void)s; (void)fd; (void)newline; }
131
+
132
+ /* ---- Rounding (#943): IR compiled for the host calls these and registers a
133
+ * constructor that passes `nish_round_init` libm's three routines
134
+ * (runtime-simd.c has the native ones). On wasm32 each is the one
135
+ * instruction, f64.floor, f64.ceil or f64.trunc, and there is no CPU to ask.
136
+ * The link is freestanding, so the libm names the constructor takes are
137
+ * defined here too, as the same instructions. */
138
+ void nish_round_init(double (*floor_fn)(double), double (*ceil_fn)(double), double (*trunc_fn)(double)) {
139
+ (void)floor_fn;
140
+ (void)ceil_fn;
141
+ (void)trunc_fn;
142
+ }
143
+ double nish_floor(double x) { return __builtin_floor(x); }
144
+ double nish_ceil(double x) { return __builtin_ceil(x); }
145
+ double nish_trunc(double x) { return __builtin_trunc(x); }
146
+ double floor(double x) { return __builtin_floor(x); }
147
+ double ceil(double x) { return __builtin_ceil(x); }
148
+ double trunc(double x) { return __builtin_trunc(x); }
package/scripts/build.sh CHANGED
@@ -3,17 +3,21 @@
3
3
  #
4
4
  # scripts/build.sh <module.ll> [more .ll/.c files...] -o <out> [--profile debug|speed|size|wasm|wasi|napi]
5
5
  #
6
- # The C runtime is ten translation units and is named as one: an input
6
+ # The C runtime is thirteen 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-cross.c, the copies that carry a string or an array
11
11
  # from one thread's arena to another's, <dir>/runtime-host.c, the wall clock, entropy, file times and
12
12
  # signals, <dir>/runtime-net.c, the sockets of `nish:net`,
13
- # <dir>/runtime-simd.c, the byte searches that read a vector at a time,
13
+ # <dir>/runtime-simd.c, the byte searches that read a vector at a time and
14
+ # the SSE4.1 rounding of `Math.floor` and its kin,
14
15
  # <dir>/runtime-aes.c, AES-GCM on the CPU's AES instructions,
15
- # <dir>/runtime-term.c, stdin and the terminal of `nish:term`, and
16
- # <dir>/runtime-bytes.c, the byte-class runs of `nish/bytes`. Each of those
16
+ # <dir>/runtime-sha256.c, SHA-256 on the CPU's SHA instructions,
17
+ # <dir>/runtime-checksum.c, CRC-32 on its carry-less multiply or CRC instructions,
18
+ # <dir>/runtime-term.c, stdin and the terminal of `nish:term`,
19
+ # <dir>/runtime-bytes.c, the byte-class runs of `nish/bytes`, and
20
+ # <dir>/runtime-sandbox.c, the confinement `--sandbox` applies. Each of those
17
21
  # files says why they are compiled and measured apart.
18
22
  #
19
23
  # Profiles:
@@ -168,6 +172,15 @@ case "$profile" in
168
172
  if grep -E '^define [^@]*@memory\(' "$i" | grep -Evq '^define (internal|private) '; then
169
173
  echo "error: $i defines a function named \`memory\`, the name the $profile profile's module exports its linear memory under; rename the function" >&2
170
174
  exit 2
175
+ fi
176
+ # `--sandbox` IR compiled for the host confines through Linux system
177
+ # calls a wasm module has none of; the compiler emits no call for a
178
+ # wasm target, so only IR that never told it reaches this. The match
179
+ # is the call instruction, not the name: the compiler's own IR holds
180
+ # the name as string data.
181
+ if grep -Eq '^[[:space:]]+call void @nish_sandbox\(' "$i"; then
182
+ echo "error: $i was compiled with --sandbox, and a $profile module has no sandbox; compile it with --target wasm32 and without --sandbox" >&2
183
+ exit 2
171
184
  fi ;;
172
185
  esac
173
186
  done ;;
@@ -194,15 +207,17 @@ if [ -n "$sqlite_to" ] || [ -n "$sqlite_from" ]; then
194
207
  fi
195
208
  [ -n "$out" ] || [ -n "$objects_to" ] || [ -n "$sqlite_to" ] || { echo "error: -o <out> is required" >&2; exit 2; }
196
209
 
197
- # The runtime is ten translation units, and a caller names one: whoever passes
210
+ # The runtime is thirteen translation units, and a caller names one: whoever passes
198
211
  # <dir>/runtime.c gets <dir>/runtime-os.c, <dir>/runtime-parallel.c,
199
212
  # <dir>/runtime-cross.c, <dir>/runtime-host.c, <dir>/runtime-net.c, <dir>/runtime-simd.c,
200
- # <dir>/runtime-aes.c, <dir>/runtime-term.c and <dir>/runtime-bytes.c compiled beside it, on every profile,
213
+ # <dir>/runtime-aes.c, <dir>/runtime-sha256.c, <dir>/runtime-checksum.c, <dir>/runtime-term.c,
214
+ # <dir>/runtime-bytes.c and <dir>/runtime-sandbox.c compiled beside it, on every profile,
201
215
  # wasi included: runtime-simd.c and runtime-bytes.c take their scalar paths there
202
216
  # unless the build adds -msimd128,
203
- # runtime-aes.c compiles to an answer of 0 and stubs that nothing calls,
204
- # because the compiler emits no call to its kernels for wasm32, and
205
- # runtime-term.c reads stdin and answers "not a terminal". They were one file until the
217
+ # runtime-aes.c, runtime-sha256.c and runtime-checksum.c compile to an answer of 0 and
218
+ # stubs that nothing calls, because the compiler emits no call to their kernels for wasm32, and
219
+ # runtime-term.c reads stdin and answers "not a terminal", and runtime-sandbox.c
220
+ # compiles to two functions that refuse to run, which no wasm build calls. They were one file until the
206
221
  # operating-system half was split out for its own size budget, and the parallel
207
222
  # half followed for the same reason (each file's header comment says why), and
208
223
  # a link line is where those splits
@@ -212,7 +227,7 @@ fi
212
227
  # here keeps every one of those correct, and keeps "the runtime" one thing to
213
228
  # name from the outside. A caller that names one itself is left alone, because
214
229
  # naming one object twice is a duplicate-symbol error.
215
- halves="runtime-os.c runtime-parallel.c runtime-cross.c runtime-host.c runtime-net.c runtime-simd.c runtime-aes.c runtime-term.c runtime-bytes.c"
230
+ halves="runtime-os.c runtime-parallel.c runtime-cross.c runtime-host.c runtime-net.c runtime-simd.c runtime-aes.c runtime-sha256.c runtime-checksum.c runtime-term.c runtime-bytes.c runtime-sandbox.c"
216
231
  for i in ${inputs[@]+"${inputs[@]}"}; do
217
232
  case "$i" in
218
233
  */runtime.c|runtime.c)
@@ -634,22 +649,29 @@ case "$profile" in
634
649
  exit 2
635
650
  fi
636
651
  # IR compiled for the host rather than with `--target wasm32` calls std/crypto/aes.ts's
637
- # AES kernels behind `nish_aes_hw` (src/kernels.ts). runtime-aes.c's wasm32 build
638
- # answers 0 there, so the bitsliced code runs, and it is added only for such IR:
639
- # every other module links the bytes it always did.
640
- # A caller that names runtime-aes.c itself is left alone, as `halves` leaves it:
652
+ # AES kernels behind `nish_aes_hw`, std/crypto/sha256.ts's block function behind
653
+ # `nish_sha256_hw` and std/compress/checksum.ts's CRC-32 kernel behind
654
+ # `nish_crc32_hw` (src/kernels.ts). runtime-aes.c's, runtime-sha256.c's and
655
+ # runtime-checksum.c's wasm32 builds answer 0 there, so the Nish code runs, and
656
+ # each is added only for such IR: every other module links the bytes it always did.
657
+ # Each pair is a unit and the kernel its probe names.
658
+ # A caller that names the unit itself is left alone, as `halves` leaves it:
641
659
  # one object named twice is a duplicate-symbol error.
642
- aes_named=0
643
- aes_wanted=0
644
- for i in ${inputs[@]+"${inputs[@]}"}; do
645
- case "$i" in
646
- */runtime-aes.c|runtime-aes.c) aes_named=1 ;;
647
- *.ll) if grep -q '@nish_aes_hw(' "$i"; then aes_wanted=1; fi ;;
648
- esac
660
+ for pair in aes:aes sha256:sha256 checksum:crc32; do
661
+ unit=${pair%%:*}
662
+ kernel=${pair#*:}
663
+ named=0
664
+ wanted=0
665
+ for i in ${inputs[@]+"${inputs[@]}"}; do
666
+ case "$i" in
667
+ */runtime-"$unit".c|runtime-"$unit".c) named=1 ;;
668
+ *.ll) if grep -q "@nish_${kernel}_hw(" "$i"; then wanted=1; fi ;;
669
+ esac
670
+ done
671
+ if [ "$wanted" = 1 ] && [ "$named" = 0 ]; then
672
+ inputs+=("$(cd "$(dirname "$0")/../runtime" && pwd)/runtime-$unit.c")
673
+ fi
649
674
  done
650
- if [ "$aes_wanted" = 1 ] && [ "$aes_named" = 0 ]; then
651
- inputs+=("$(cd "$(dirname "$0")/../runtime" && pwd)/runtime-aes.c")
652
- fi
653
675
  # IR of either kind that imports std/json or calls nish/bytes's byte-class runs
654
676
  # calls runtime-bytes.c's kernels (src/kernels.ts), which compile freestanding:
655
677
  # the scalar path, or the 128-bit one under -msimd128. It is added the same way,
@@ -665,6 +687,20 @@ case "$profile" in
665
687
  if [ "$bytes_wanted" = 1 ] && [ "$bytes_named" = 0 ]; then
666
688
  inputs+=("$(cd "$(dirname "$0")/../runtime" && pwd)/runtime-bytes.c")
667
689
  fi
690
+ # IR compiled for the host rounds through runtime-simd.c's `nish_floor` and its
691
+ # kin behind the constructor `nish_round_init` (#943). runtime-wasm.c has their
692
+ # wasm32 bodies, and it is added the same way when such IR does not name it.
693
+ wasm_named=0
694
+ round_wanted=0
695
+ for i in ${inputs[@]+"${inputs[@]}"}; do
696
+ case "$i" in
697
+ */runtime-wasm.c|runtime-wasm.c) wasm_named=1 ;;
698
+ *.ll) if grep -q '@nish_round_init' "$i"; then round_wanted=1; fi ;;
699
+ esac
700
+ done
701
+ if [ "$round_wanted" = 1 ] && [ "$wasm_named" = 0 ]; then
702
+ inputs+=("$(cd "$(dirname "$0")/../runtime" && pwd)/runtime-wasm.c")
703
+ fi
668
704
  # -mbulk-memory lowers llvm.memset/memcpy (`new Array<T>(n)`, `push` growth) to the
669
705
  # memory.fill/memory.copy instructions instead of libc calls the freestanding link lacks.
670
706
  "$CC" -Wno-override-module --target=wasm32-unknown-unknown -Oz -nostdlib -mbulk-memory \