@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.
- package/INSTALL.md +13 -8
- package/bin/nish +0 -0
- package/docs/AI.md +27 -5
- package/package.json +1 -1
- package/runtime/core.d.ts +47 -0
- package/runtime/core.mjs +67 -0
- package/runtime/nish.d.ts +54 -5
- package/runtime/nish.h +67 -8
- package/runtime/nish.mjs +6 -0
- package/runtime/runtime-checksum.c +270 -0
- package/runtime/runtime-os.c +9 -4
- package/runtime/runtime-parallel.c +21 -0
- package/runtime/runtime-sandbox.c +470 -0
- package/runtime/runtime-sha256.c +239 -0
- package/runtime/runtime-simd.c +63 -2
- package/runtime/runtime-wasm.c +18 -0
- package/scripts/build.sh +60 -24
- package/std/README.md +16 -10
- package/std/compress/checksum.ts +88 -75
- package/std/compress/deflate.ts +224 -0
- package/std/compress/gzip.ts +52 -0
- package/std/crypto/bcrypt.ts +816 -280
- package/std/crypto/sha256.ts +335 -188
- package/std/json-equal.ts +335 -0
- package/std/json-format.ts +1070 -0
- package/std/json-number.ts +428 -0
- package/std/json-pointer.ts +245 -0
- package/std/json-value.ts +510 -17
- package/std/net/http-client.ts +1645 -0
- package/std/net/http-compress.ts +259 -16
- package/std/net/http1-client.ts +104 -19
- package/std/net/http1-server.ts +61 -12
- package/std/net/postgres.ts +576 -8
- package/std/net/websocket.ts +295 -14
- package/std/regex.ts +613 -52
- package/std/term-io.ts +35 -8
- package/std/yaml-json.ts +841 -0
|
@@ -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
|
package/runtime/runtime-simd.c
CHANGED
|
@@ -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
|
-
|
|
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)
|
package/runtime/runtime-wasm.c
CHANGED
|
@@ -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
|
|
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-
|
|
16
|
-
# <dir>/runtime-
|
|
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
|
|
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-
|
|
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
|
|
204
|
-
# because the compiler emits no call to
|
|
205
|
-
# runtime-term.c reads stdin and answers "not a terminal"
|
|
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
|
|
638
|
-
#
|
|
639
|
-
#
|
|
640
|
-
#
|
|
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
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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 \
|