@amritk/nish-x86_64-linux 0.33.0 → 0.34.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 CHANGED
@@ -254,8 +254,8 @@ routes are
254
254
  # on the musl host, with its own clang and the runtime from this repository
255
255
  clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
256
256
  runtime/runtime-cross.c runtime/runtime-host.c runtime/runtime-net.c \
257
- runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-term.c \
258
- runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
257
+ runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-chacha20.c \
258
+ runtime/runtime-term.c runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
259
259
  runtime/runtime-shm.c runtime/runtime-region.c -lm -o app
260
260
  ```
261
261
 
@@ -392,7 +392,7 @@ it into `build/seed/`; `NISH_BOOTSTRAP=<path>` names one you already have and
392
392
  skips the download. §2a has what the build does with it.
393
393
 
394
394
  The published package ships `bin/` (the `nish` command, which is a launcher),
395
- `runtime/` (the C runtime — fifteen translation units, the wasm subset and their
395
+ `runtime/` (the C runtime — sixteen translation units, the wasm subset and their
396
396
  header — and `runtime/sqlite/`, SQLite's vendored source),
397
397
  `scripts/build.sh` (the link pipeline), `std/` (the standard library) and
398
398
  `docs/AI.md` (the rules card). The
@@ -405,14 +405,14 @@ working directory — and the binary runs from inside its own platform package
405
405
  for exactly that reason.
406
406
 
407
407
  Building the IR yourself rather than through `--link` means naming the runtime
408
- on the `clang` line, and it is fifteen files:
408
+ on the `clang` line, and it is sixteen files:
409
409
 
410
410
  ```bash
411
411
  clang app.ll runtime/runtime.c runtime/runtime-os.c runtime/runtime-parallel.c \
412
412
  runtime/runtime-cross.c runtime/runtime-host.c runtime/runtime-net.c \
413
- runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-term.c \
414
- runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
415
- runtime/runtime-shm.c -lm -o app
413
+ runtime/runtime-simd.c runtime/runtime-aes.c runtime/runtime-sha256.c runtime/runtime-chacha20.c \
414
+ runtime/runtime-term.c runtime/runtime-bytes.c runtime/runtime-checksum.c runtime/runtime-sandbox.c \
415
+ runtime/runtime-shm.c runtime/runtime-region.c -lm -o app
416
416
  ```
417
417
 
418
418
  `runtime.c` is the part every program touches — the arena, strings, arrays,
@@ -427,7 +427,9 @@ entropy, file times and signals, `runtime-net.c` the sockets of `nish:net`,
427
427
  `runtime-simd.c` and `runtime-aes.c` the vector text search and AES-GCM,
428
428
  `runtime-sha256.c` the SHA-256 block kernel `nish/crypto/sha256` hands its
429
429
  blocks to on a CPU with the SHA instructions (`NISH_SHA256=soft` keeps the
430
- portable path), `runtime-term.c` the stdin reads and terminal calls of `nish:term`,
430
+ portable path), `runtime-chacha20.c` the ChaCha20 keystream `nish/crypto/chacha20poly1305`
431
+ hands a message of 256 bytes or more to on a CPU with AVX2 or NEON (`NISH_CHACHA20=soft`
432
+ keeps the portable path), `runtime-term.c` the stdin reads and terminal calls of `nish:term`,
431
433
  `runtime-bytes.c` the byte-class runs of `nish/bytes`, which `nish/json` steps
432
434
  over, `runtime-checksum.c` the CRC-32 kernels of `nish/compress/checksum`,
433
435
  `runtime-sandbox.c` the confinement a `--sandbox` build applies, and
package/bin/nish CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-x86_64-linux",
3
- "version": "0.33.0",
3
+ "version": "0.34.0",
4
4
  "description": "The nish native compiler for Linux on x86_64",
5
5
  "license": "MIT",
6
6
  "os": [
package/runtime/core.d.ts CHANGED
@@ -16,14 +16,30 @@ import type { f32, f64, i32, u16, u32, u8 } from "./core/numbers.js";
16
16
  * `nish` does, but lets `===` and `<` through, which `nish` refuses. Under
17
17
  * Node it is a typed array made once and never written after.
18
18
  */
19
- export interface u8x16 { readonly __nishSimd: "u8x16"; readonly __lane: u8 }
20
- export interface u16x8 { readonly __nishSimd: "u16x8"; readonly __lane: u16 }
21
- export interface i32x4 { readonly __nishSimd: "i32x4"; readonly __lane: i32 }
22
- export interface u32x4 { readonly __nishSimd: "u32x4"; readonly __lane: u32 }
23
- export interface f32x4 { readonly __nishSimd: "f32x4"; readonly __lane: f32 }
24
- export interface f64x2 { readonly __nishSimd: "f64x2"; readonly __lane: f64 }
19
+ export interface u8x16 { readonly __nishSimd: "u8x16"; readonly __lane: u8; readonly __mask: mask8x16 }
20
+ export interface u16x8 { readonly __nishSimd: "u16x8"; readonly __lane: u16; readonly __mask: mask16x8 }
21
+ export interface i32x4 { readonly __nishSimd: "i32x4"; readonly __lane: i32; readonly __mask: mask32x4 }
22
+ export interface u32x4 { readonly __nishSimd: "u32x4"; readonly __lane: u32; readonly __mask: mask32x4 }
23
+ export interface f32x4 { readonly __nishSimd: "f32x4"; readonly __lane: f32; readonly __mask: mask32x4 }
24
+ export interface f64x2 { readonly __nishSimd: "f64x2"; readonly __lane: f64; readonly __mask: mask64x2 }
25
25
  /** Any of the six. */
26
26
  export type SimdVector = u8x16 | u16x8 | i32x4 | u32x4 | f32x4 | f64x2;
27
+ /** The four with integer lanes. */
28
+ export type IntegerVector = u8x16 | u16x8 | i32x4 | u32x4;
29
+ /** The two with float lanes. */
30
+ export type FloatVector = f32x4 | f64x2;
31
+ /**
32
+ * One boolean per lane, which a lane-wise compare answers. Under Node an
33
+ * array of booleans. `nish` keeps a mask in a local, a parameter or a
34
+ * return of a function the module keeps, and nowhere else; it has no
35
+ * constructor.
36
+ */
37
+ export interface mask8x16 { readonly __nishMask: "mask8x16" }
38
+ export interface mask16x8 { readonly __nishMask: "mask16x8" }
39
+ export interface mask32x4 { readonly __nishMask: "mask32x4" }
40
+ export interface mask64x2 { readonly __nishMask: "mask64x2" }
41
+ /** Any of the four. */
42
+ export type SimdMask = mask8x16 | mask16x8 | mask32x4 | mask64x2;
27
43
  /** One value per lane, lane 0 first. */
28
44
  export declare function u8x16(
29
45
  a0: u8, a1: u8, a2: u8, a3: u8, a4: u8, a5: u8, a6: u8, a7: u8,
@@ -54,3 +70,64 @@ export declare function store<V extends SimdVector>(v: V, xs: V["__lane"][], i:
54
70
  export declare function extract<V extends SimdVector>(v: V, k: i32): V["__lane"];
55
71
  /** A new vector: `v` with lane `k` set to `x`. */
56
72
  export declare function replace<V extends SimdVector>(v: V, k: i32, x: V["__lane"]): V;
73
+ /** Float lane arithmetic, lane by lane. An integer lane's form wraps. */
74
+ export declare function add<V extends FloatVector>(a: V, b: V): V;
75
+ export declare function sub<V extends FloatVector>(a: V, b: V): V;
76
+ export declare function mul<V extends FloatVector>(a: V, b: V): V;
77
+ export declare function div<V extends FloatVector>(a: V, b: V): V;
78
+ export declare function sqrt<V extends FloatVector>(a: V): V;
79
+ /** Integer lane arithmetic, wrapping at the lane width. */
80
+ export declare function addWrapping<V extends IntegerVector>(a: V, b: V): V;
81
+ export declare function subWrapping<V extends IntegerVector>(a: V, b: V): V;
82
+ export declare function mulWrapping<V extends IntegerVector>(a: V, b: V): V;
83
+ /** Negation and absolute value of float lanes: the sign bit. */
84
+ export declare function neg<V extends FloatVector>(a: V): V;
85
+ export declare function abs<V extends FloatVector>(a: V): V;
86
+ /** The lesser and the greater lane: a float NaN lane answers NaN. */
87
+ export declare function min<V extends SimdVector>(a: V, b: V): V;
88
+ export declare function max<V extends SimdVector>(a: V, b: V): V;
89
+ /** Bitwise, on integer lanes or lane by lane on two masks. */
90
+ export declare function and<V extends IntegerVector | SimdMask>(a: V, b: V): V;
91
+ export declare function or<V extends IntegerVector | SimdMask>(a: V, b: V): V;
92
+ export declare function xor<V extends IntegerVector | SimdMask>(a: V, b: V): V;
93
+ export declare function not<V extends IntegerVector | SimdMask>(a: V): V;
94
+ /** Lane-wise compares, answering the vector's mask. A float compare is ordered (`ne` unordered). */
95
+ export declare function eq<V extends SimdVector>(a: V, b: V): V["__mask"];
96
+ export declare function ne<V extends SimdVector>(a: V, b: V): V["__mask"];
97
+ export declare function lt<V extends SimdVector>(a: V, b: V): V["__mask"];
98
+ export declare function le<V extends SimdVector>(a: V, b: V): V["__mask"];
99
+ export declare function gt<V extends SimdVector>(a: V, b: V): V["__mask"];
100
+ export declare function ge<V extends SimdVector>(a: V, b: V): V["__mask"];
101
+ /** Lane `k` of `a` where lane `k` of `m` is set, else of `b`. */
102
+ export declare function select<V extends SimdVector>(m: V["__mask"], a: V, b: V): V;
103
+ /** Whether any lane, or every lane, is set. */
104
+ export declare function any(m: SimdMask): boolean;
105
+ export declare function all(m: SimdMask): boolean;
106
+ /** Lane `k`'s flag in bit `k`. */
107
+ export declare function bitmask(m: SimdMask): i32;
108
+ /**
109
+ * The lanes added: a `u8x16` or `u16x8` in an `i32`, a float vector in lane
110
+ * order. An `i32x4` or `u32x4` total is `sumWrapping`.
111
+ */
112
+ export declare function sum(v: u8x16 | u16x8): i32;
113
+ export declare function sum(v: f32x4): f32;
114
+ export declare function sum(v: f64x2): f64;
115
+ export declare function sumWrapping(v: i32x4): i32;
116
+ export declare function sumWrapping(v: u32x4): u32;
117
+ /** Lanes picked from `a` then `b` by integer-literal indices, one per lane. */
118
+ export declare function shuffle<V extends SimdVector>(a: V, b: V, ...lanes: i32[]): V;
119
+ /**
120
+ * Shifts and rotates by an integer-literal count below the lane width:
121
+ * `shr` is logical on an unsigned lane and arithmetic on an `i32` lane.
122
+ */
123
+ export declare function shl<V extends IntegerVector>(v: V, count: i32): V;
124
+ export declare function shr<V extends IntegerVector>(v: V, count: i32): V;
125
+ export declare function rotl<V extends IntegerVector>(v: V, count: i32): V;
126
+ export declare function rotr<V extends IntegerVector>(v: V, count: i32): V;
127
+ /** The same sixteen bytes read as another vector type. */
128
+ export declare function asU8x16(v: SimdVector): u8x16;
129
+ export declare function asU16x8(v: SimdVector): u16x8;
130
+ export declare function asI32x4(v: SimdVector): i32x4;
131
+ export declare function asU32x4(v: SimdVector): u32x4;
132
+ export declare function asF32x4(v: SimdVector): f32x4;
133
+ export declare function asF64x2(v: SimdVector): f64x2;
package/runtime/core.mjs CHANGED
@@ -120,3 +120,93 @@ export function replace(v, k, x) {
120
120
  out[k] = x;
121
121
  return out;
122
122
  }
123
+
124
+ // ---- `nish:simd` lane operations ---------------------------------------------
125
+ // Each answers a fresh vector of its operands' type, and the typed array does
126
+ // the wrapping: a `u8` sum stored into a `Uint8Array` keeps its low byte, as
127
+ // `add <16 x i8>` does. A mask is an array of booleans, one a lane. `f32`
128
+ // arithmetic rounds once, through the array, which for `+ - * /` and `sqrt` is
129
+ // the single rounding the native lane does: the double result of two floats
130
+ // rounds to the same float. `Math.min` and `Math.max` are `llvm.minimum` and
131
+ // `llvm.maximum`: a NaN lane answers NaN and -0 orders below +0.
132
+
133
+ const simdLanes = (a, f) => {
134
+ const out = new a.constructor(a.length);
135
+ for (let j = 0; j < a.length; j++) out[j] = f(a[j], j);
136
+ return out;
137
+ };
138
+ const simdMask = (a, f) => {
139
+ const out = new Array(a.length);
140
+ for (let j = 0; j < a.length; j++) out[j] = f(a[j], j);
141
+ return out;
142
+ };
143
+ const simdBits = (a) => a.BYTES_PER_ELEMENT * 8;
144
+
145
+ export const add = (a, b) => simdLanes(a, (x, j) => x + b[j]);
146
+ export const sub = (a, b) => simdLanes(a, (x, j) => x - b[j]);
147
+ export const mul = (a, b) => simdLanes(a, (x, j) => x * b[j]);
148
+ export const div = (a, b) => simdLanes(a, (x, j) => x / b[j]);
149
+ export const neg = (a) => simdLanes(a, (x) => -x);
150
+ export const abs = (a) => simdLanes(a, (x) => Math.abs(x));
151
+ export const sqrt = (a) => simdLanes(a, (x) => Math.sqrt(x));
152
+ // A typed array wraps on store, so the integer forms share the float bodies.
153
+ export const addWrapping = (a, b) => simdLanes(a, (x, j) => x + b[j]);
154
+ export const subWrapping = (a, b) => simdLanes(a, (x, j) => x - b[j]);
155
+ // A 32-bit product loses its low bits as a double; `Math.imul` keeps them.
156
+ export const mulWrapping = (a, b) => simdLanes(a, (x, j) => (simdBits(a) === 32 ? Math.imul(x, b[j]) : x * b[j]));
157
+ export const min = (a, b) => simdLanes(a, (x, j) => Math.min(x, b[j]));
158
+ export const max = (a, b) => simdLanes(a, (x, j) => Math.max(x, b[j]));
159
+
160
+ /** `and`, `or`, `xor` and `not` take an integer vector or a mask. */
161
+ export const and = (a, b) => (Array.isArray(a) ? simdMask(a, (x, j) => x && b[j]) : simdLanes(a, (x, j) => x & b[j]));
162
+ export const or = (a, b) => (Array.isArray(a) ? simdMask(a, (x, j) => x || b[j]) : simdLanes(a, (x, j) => x | b[j]));
163
+ export const xor = (a, b) => (Array.isArray(a) ? simdMask(a, (x, j) => x !== b[j]) : simdLanes(a, (x, j) => x ^ b[j]));
164
+ export const not = (a) => (Array.isArray(a) ? simdMask(a, (x) => !x) : simdLanes(a, (x) => ~x));
165
+
166
+ // `ne` is the unordered compare: a NaN lane is unequal to everything.
167
+ export const eq = (a, b) => simdMask(a, (x, j) => x === b[j]);
168
+ export const ne = (a, b) => simdMask(a, (x, j) => x !== b[j]);
169
+ export const lt = (a, b) => simdMask(a, (x, j) => x < b[j]);
170
+ export const le = (a, b) => simdMask(a, (x, j) => x <= b[j]);
171
+ export const gt = (a, b) => simdMask(a, (x, j) => x > b[j]);
172
+ export const ge = (a, b) => simdMask(a, (x, j) => x >= b[j]);
173
+
174
+ export const select = (m, a, b) => simdLanes(a, (x, j) => (m[j] ? x : b[j]));
175
+ export const any = (m) => m.some((x) => x);
176
+ export const all = (m) => m.every((x) => x);
177
+ export const bitmask = (m) => m.reduce((bits, x, j) => (x ? bits | (1 << j) : bits), 0);
178
+
179
+ /**
180
+ * `sum`: a float vector's lanes added first to last from -0, rounding each
181
+ * step as `llvm.vector.reduce.fadd` does; a `u8` or `u16` vector's, which
182
+ * cannot overflow an `i32`.
183
+ */
184
+ export const sum = (a) => {
185
+ let acc = -0;
186
+ for (const x of a) acc = a instanceof Float32Array ? Math.fround(acc + x) : acc + x;
187
+ return acc;
188
+ };
189
+ /** `sumWrapping`: a 32-bit vector's lanes added modulo 2^32. */
190
+ export const sumWrapping = (a) => {
191
+ let acc = 0;
192
+ for (const x of a) acc = (acc + x) | 0;
193
+ return a instanceof Uint32Array ? acc >>> 0 : acc;
194
+ };
195
+
196
+ /** `shuffle(a, b, ...lanes)`: lane `k` of the 2N lanes `a` then `b`. */
197
+ export const shuffle = (a, b, ...lanes) => simdLanes(a, (_, j) => (lanes[j] < a.length ? a[lanes[j]] : b[lanes[j] - a.length]));
198
+
199
+ // A count is a constant below the lane width, so `x >>> (w - k)` is a real
200
+ // shift; at k = 0 and w = 32 JavaScript's count wraps to 0, and the `|` keeps x.
201
+ export const shl = (a, k) => simdLanes(a, (x) => x << k);
202
+ export const shr = (a, k) => simdLanes(a, (x) => (a instanceof Int32Array ? x >> k : x >>> k));
203
+ export const rotl = (a, k) => simdLanes(a, (x) => (x << k) | (x >>> (simdBits(a) - k)));
204
+ export const rotr = (a, k) => simdLanes(a, (x) => (x >>> k) | (x << (simdBits(a) - k)));
205
+
206
+ const simdAs = (Lanes) => (v) => new Lanes(v.buffer.slice(v.byteOffset, v.byteOffset + 16));
207
+ export const asU8x16 = simdAs(Uint8Array);
208
+ export const asU16x8 = simdAs(Uint16Array);
209
+ export const asI32x4 = simdAs(Int32Array);
210
+ export const asU32x4 = simdAs(Uint32Array);
211
+ export const asF32x4 = simdAs(Float32Array);
212
+ export const asF64x2 = simdAs(Float64Array);
package/runtime/nish.d.ts CHANGED
@@ -856,14 +856,30 @@ declare module "nish:simd" {
856
856
  * `nish` does, but lets `===` and `<` through, which `nish` refuses. Under
857
857
  * Node it is a typed array made once and never written after.
858
858
  */
859
- export interface u8x16 { readonly __nishSimd: "u8x16"; readonly __lane: u8 }
860
- export interface u16x8 { readonly __nishSimd: "u16x8"; readonly __lane: u16 }
861
- export interface i32x4 { readonly __nishSimd: "i32x4"; readonly __lane: i32 }
862
- export interface u32x4 { readonly __nishSimd: "u32x4"; readonly __lane: u32 }
863
- export interface f32x4 { readonly __nishSimd: "f32x4"; readonly __lane: f32 }
864
- export interface f64x2 { readonly __nishSimd: "f64x2"; readonly __lane: f64 }
859
+ export interface u8x16 { readonly __nishSimd: "u8x16"; readonly __lane: u8; readonly __mask: mask8x16 }
860
+ export interface u16x8 { readonly __nishSimd: "u16x8"; readonly __lane: u16; readonly __mask: mask16x8 }
861
+ export interface i32x4 { readonly __nishSimd: "i32x4"; readonly __lane: i32; readonly __mask: mask32x4 }
862
+ export interface u32x4 { readonly __nishSimd: "u32x4"; readonly __lane: u32; readonly __mask: mask32x4 }
863
+ export interface f32x4 { readonly __nishSimd: "f32x4"; readonly __lane: f32; readonly __mask: mask32x4 }
864
+ export interface f64x2 { readonly __nishSimd: "f64x2"; readonly __lane: f64; readonly __mask: mask64x2 }
865
865
  /** Any of the six. */
866
866
  export type SimdVector = u8x16 | u16x8 | i32x4 | u32x4 | f32x4 | f64x2;
867
+ /** The four with integer lanes. */
868
+ export type IntegerVector = u8x16 | u16x8 | i32x4 | u32x4;
869
+ /** The two with float lanes. */
870
+ export type FloatVector = f32x4 | f64x2;
871
+ /**
872
+ * One boolean per lane, which a lane-wise compare answers. Under Node an
873
+ * array of booleans. `nish` keeps a mask in a local, a parameter or a
874
+ * return of a function the module keeps, and nowhere else; it has no
875
+ * constructor.
876
+ */
877
+ export interface mask8x16 { readonly __nishMask: "mask8x16" }
878
+ export interface mask16x8 { readonly __nishMask: "mask16x8" }
879
+ export interface mask32x4 { readonly __nishMask: "mask32x4" }
880
+ export interface mask64x2 { readonly __nishMask: "mask64x2" }
881
+ /** Any of the four. */
882
+ export type SimdMask = mask8x16 | mask16x8 | mask32x4 | mask64x2;
867
883
  /** One value per lane, lane 0 first. */
868
884
  export function u8x16(
869
885
  a0: u8, a1: u8, a2: u8, a3: u8, a4: u8, a5: u8, a6: u8, a7: u8,
@@ -894,6 +910,67 @@ declare module "nish:simd" {
894
910
  export function extract<V extends SimdVector>(v: V, k: i32): V["__lane"];
895
911
  /** A new vector: `v` with lane `k` set to `x`. */
896
912
  export function replace<V extends SimdVector>(v: V, k: i32, x: V["__lane"]): V;
913
+ /** Float lane arithmetic, lane by lane. An integer lane's form wraps. */
914
+ export function add<V extends FloatVector>(a: V, b: V): V;
915
+ export function sub<V extends FloatVector>(a: V, b: V): V;
916
+ export function mul<V extends FloatVector>(a: V, b: V): V;
917
+ export function div<V extends FloatVector>(a: V, b: V): V;
918
+ export function sqrt<V extends FloatVector>(a: V): V;
919
+ /** Integer lane arithmetic, wrapping at the lane width. */
920
+ export function addWrapping<V extends IntegerVector>(a: V, b: V): V;
921
+ export function subWrapping<V extends IntegerVector>(a: V, b: V): V;
922
+ export function mulWrapping<V extends IntegerVector>(a: V, b: V): V;
923
+ /** Negation and absolute value of float lanes: the sign bit. */
924
+ export function neg<V extends FloatVector>(a: V): V;
925
+ export function abs<V extends FloatVector>(a: V): V;
926
+ /** The lesser and the greater lane: a float NaN lane answers NaN. */
927
+ export function min<V extends SimdVector>(a: V, b: V): V;
928
+ export function max<V extends SimdVector>(a: V, b: V): V;
929
+ /** Bitwise, on integer lanes or lane by lane on two masks. */
930
+ export function and<V extends IntegerVector | SimdMask>(a: V, b: V): V;
931
+ export function or<V extends IntegerVector | SimdMask>(a: V, b: V): V;
932
+ export function xor<V extends IntegerVector | SimdMask>(a: V, b: V): V;
933
+ export function not<V extends IntegerVector | SimdMask>(a: V): V;
934
+ /** Lane-wise compares, answering the vector's mask. A float compare is ordered (`ne` unordered). */
935
+ export function eq<V extends SimdVector>(a: V, b: V): V["__mask"];
936
+ export function ne<V extends SimdVector>(a: V, b: V): V["__mask"];
937
+ export function lt<V extends SimdVector>(a: V, b: V): V["__mask"];
938
+ export function le<V extends SimdVector>(a: V, b: V): V["__mask"];
939
+ export function gt<V extends SimdVector>(a: V, b: V): V["__mask"];
940
+ export function ge<V extends SimdVector>(a: V, b: V): V["__mask"];
941
+ /** Lane `k` of `a` where lane `k` of `m` is set, else of `b`. */
942
+ export function select<V extends SimdVector>(m: V["__mask"], a: V, b: V): V;
943
+ /** Whether any lane, or every lane, is set. */
944
+ export function any(m: SimdMask): boolean;
945
+ export function all(m: SimdMask): boolean;
946
+ /** Lane `k`'s flag in bit `k`. */
947
+ export function bitmask(m: SimdMask): i32;
948
+ /**
949
+ * The lanes added: a `u8x16` or `u16x8` in an `i32`, a float vector in lane
950
+ * order. An `i32x4` or `u32x4` total is `sumWrapping`.
951
+ */
952
+ export function sum(v: u8x16 | u16x8): i32;
953
+ export function sum(v: f32x4): f32;
954
+ export function sum(v: f64x2): f64;
955
+ export function sumWrapping(v: i32x4): i32;
956
+ export function sumWrapping(v: u32x4): u32;
957
+ /** Lanes picked from `a` then `b` by integer-literal indices, one per lane. */
958
+ export function shuffle<V extends SimdVector>(a: V, b: V, ...lanes: i32[]): V;
959
+ /**
960
+ * Shifts and rotates by an integer-literal count below the lane width:
961
+ * `shr` is logical on an unsigned lane and arithmetic on an `i32` lane.
962
+ */
963
+ export function shl<V extends IntegerVector>(v: V, count: i32): V;
964
+ export function shr<V extends IntegerVector>(v: V, count: i32): V;
965
+ export function rotl<V extends IntegerVector>(v: V, count: i32): V;
966
+ export function rotr<V extends IntegerVector>(v: V, count: i32): V;
967
+ /** The same sixteen bytes read as another vector type. */
968
+ export function asU8x16(v: SimdVector): u8x16;
969
+ export function asU16x8(v: SimdVector): u16x8;
970
+ export function asI32x4(v: SimdVector): i32x4;
971
+ export function asU32x4(v: SimdVector): u32x4;
972
+ export function asF32x4(v: SimdVector): f32x4;
973
+ export function asF64x2(v: SimdVector): f64x2;
897
974
  }
898
975
 
899
976
  declare module "nish:io" {
package/runtime/nish.h CHANGED
@@ -10,15 +10,16 @@
10
10
  * clock, `nish_platform` / `nish_arch` — and runtime-parallel.c, runtime-host.c,
11
11
  * runtime-net.c, runtime-simd.c (the byte searches that read a vector at a
12
12
  * time), runtime-aes.c (AES-GCM on the CPU's AES instructions),
13
- * runtime-sha256.c (SHA-256 on the CPU's SHA instructions), runtime-checksum.c
13
+ * runtime-sha256.c (SHA-256 on the CPU's SHA instructions), runtime-chacha20.c
14
+ * (ChaCha20's keystream on AVX2 or NEON vectors), runtime-checksum.c
14
15
  * (CRC-32 on its carry-less multiply or CRC instructions) and runtime-term.c
15
16
  * (stdin and the terminal, `nish:term`) split off
16
17
  * the same way since. They are separate so that each carries its own measured
17
18
  * code-size ceiling (docs/wp7-runtime.md, "Runtime additions and budget"); a
18
19
  * host links all of them, which `scripts/build.sh` does for you when handed
19
- * runtime.c, runtime-aes.c, runtime-sha256.c and runtime-checksum.c included: the
20
- * compiler calls their kernels from std/crypto/aes.ts, std/crypto/sha256.ts and
21
- * std/compress/checksum.ts, so `scripts/build.sh` pairs each of them with
20
+ * runtime.c, runtime-aes.c, runtime-sha256.c, runtime-chacha20.c and runtime-checksum.c
21
+ * included: the compiler calls their kernels from std/crypto/aes.ts,
22
+ * std/crypto/sha256.ts, std/crypto/chacha20poly1305.ts and std/compress/checksum.ts, so `scripts/build.sh` pairs each of them with
22
23
  * runtime.c and `src/run-cache.ts` caches them like the others. `tests/run.js` checks that every runtime function
23
24
  * declared in runtime.ts has a prototype in this file.
24
25
  *
@@ -400,6 +401,28 @@ int32_t nish_sha256_hw(void);
400
401
  guard, so the caller's fallback panics as the Nish function does. `w` (u32[],
401
402
  the message schedule the Nish code fills) is neither read nor written. */
402
403
  int32_t nish_sha256_compress(nish_array *h, const nish_array *w, const nish_array *src, int32_t at);
404
+ /* ---- ChaCha20 on vectors ----------------------------------------------------
405
+ * runtime/runtime-chacha20.c: eight blocks at once on AVX2 and four in 128-bit
406
+ * vectors on x86-64, four in NEON on AArch64. The compiler calls the kernel in
407
+ * place of std/crypto/chacha20poly1305.ts's private `chacha20XorInto`, behind
408
+ * `nish_chacha20_hw`, and calls the Nish function itself when either answers 0.
409
+ * Constant time in the key, the nonce and the data. */
410
+ /* 1 when this process runs the kernel, 0 when it runs the Nish code. Resolved
411
+ on the first call and fixed from then on: `NISH_CHACHA20=soft` pins 0; unset,
412
+ `hw` or any other value is the best this machine has: CPUID's AVX2 with the
413
+ OS saving the YMM registers (XGETBV) on x86-64, NEON on AArch64. Always 0 on
414
+ other targets, wasm32 among them. */
415
+ int32_t nish_chacha20_hw(void);
416
+ /* RFC 8439 §2.4's encryption of src[srcAt..srcAt + len) (u8[]) into
417
+ dst[dstAt..dstAt + len) (u8[]) under the ChaCha20 state state[0..16) (u32[]),
418
+ whose block counter state[12] advances one per 64-byte block, as
419
+ `chacha20XorInto` does; 1 when it ran. Answers 0 and writes nothing when
420
+ state.len < 16, len < 256, an offset is negative, a window is outside its
421
+ array, or the windows overlap without being the same bytes, so the Nish
422
+ function runs as it would have. The keystream it makes is wiped before it
423
+ returns; the state is the caller's. */
424
+ int32_t nish_chacha20_xor(nish_array *state, const nish_array *src, int32_t src_at, int32_t len, nish_array *dst,
425
+ int32_t dst_at);
403
426
  /* ---- CRC-32 on the CPU's carry-less multiply or CRC instructions ---------
404
427
  * runtime/runtime-checksum.c: PCLMULQDQ folding on x86-64, the ARMv8 CRC32
405
428
  * instructions on AArch64. The compiler calls the kernel in
@@ -0,0 +1,366 @@
1
+ /* Nish runtime, the ChaCha20 half: the keystream of RFC 8439 §2.3 and §2.4,
2
+ * several blocks at once in vector registers. std/crypto/chacha20poly1305.ts
3
+ * is the cipher and the AEAD; this is a faster body for its private
4
+ * `chacha20XorInto`, which the compiler calls in that function's place when
5
+ * `nish_chacha20_hw` says this machine runs it (src/kernels.ts). Poly1305
6
+ * stays in Nish: WP38 S10 timed it beside this kernel and found the Nish
7
+ * code within the noise of a C copy (docs/wp38-simd.md §3.1).
8
+ *
9
+ * A translation unit of its own for the reason runtime-aes.c is one: its own
10
+ * measured `.text*` ceiling in tests/runtime-budgets.js, and a program that
11
+ * never encrypts with ChaCha20 links none of it (section GC).
12
+ *
13
+ * The paths. x86-64 runs eight blocks at once in AVX2's 256-bit registers,
14
+ * then four in 128-bit ones, then one at a time for what is left, all
15
+ * compiled with `target("avx2")` alone so the binary stays a baseline binary.
16
+ * A block is a lane: word `i` of block `b` is lane `b` of vector `i`, so the
17
+ * rounds are the scalar quarter rounds on vectors, and nothing shuffles
18
+ * until the words are written out. AArch64 has the same four-block loop in
19
+ * NEON, which every AArch64 CPU has; `NISH_CHACHA20_ARM_ON` says whether the
20
+ * resolver takes it (see there). A CPU without AVX2, a process with
21
+ * `NISH_CHACHA20=soft`, and every other target, wasm32 included, run the Nish
22
+ * code: the compiler keeps it and branches on `nish_chacha20_hw` at each call,
23
+ * and here the resolver answers 0 and the kernel is a trap nothing reaches.
24
+ *
25
+ * The kernel takes a buffer of four blocks or more and answers 0 for anything
26
+ * shorter, so a short message (a TLS record header, the Poly1305 key's one
27
+ * block, a QUIC header mask) costs no call and no wipe: S10 measured the
28
+ * kernel losing at 64 bytes until it did. It answers 0 too, touching
29
+ * nothing, when a window is outside its array or the two windows overlap
30
+ * without being the same bytes, so the Nish function runs and fails, or
31
+ * writes in its own order, as it would have.
32
+ *
33
+ * Constant time: ChaCha20 is additions, xors and rotations by constants, with
34
+ * no table and no secret-dependent branch. The kernel's branches are on array
35
+ * headers, offsets and the length, which are public, and its loads and stores
36
+ * are at addresses fixed by them. tests/ct-asm-runtime.js reads the
37
+ * `clang -O2` assembly of this file for both targets and holds the kernel to
38
+ * that. The one branch on anything else is `nish_chacha20_hw`'s, on the CPU's
39
+ * feature bits and `NISH_CHACHA20` (docs/security/ct-verification.md).
40
+ *
41
+ * Wiping: the working words, the per-call input block and the keystream
42
+ * buffers on the stack are zeroed by volatile stores before the kernel
43
+ * returns, the mechanism runtime-aes.c and `nish:secret`'s wipe use. The
44
+ * caller's state is the caller's to wipe, as with the Nish function. What
45
+ * the vector registers hold, and what the register allocator spills, no
46
+ * store from C can reach (docs/security/crypto-chacha20-hw.md).
47
+ *
48
+ * Written from RFC 8439 in this file's own structure; the block-a-lane layout
49
+ * is the one §3 of the RFC points at ("SIMD"), and nothing is ported.
50
+ */
51
+ #include <stddef.h>
52
+ #include <stdint.h>
53
+
54
+ #include "nish.h"
55
+
56
+ #if defined(__x86_64__)
57
+ #include <cpuid.h>
58
+ #define NISH_CHACHA20_X86 1
59
+ #elif defined(__aarch64__) && defined(__ARM_NEON) && defined(__ORDER_LITTLE_ENDIAN__) && \
60
+ __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
61
+ #include <arm_neon.h>
62
+ #define NISH_CHACHA20_ARM 1
63
+ #endif
64
+
65
+ #if defined(NISH_CHACHA20_X86) || defined(NISH_CHACHA20_ARM)
66
+
67
+ /* Named here rather than through <stdlib.h>, for runtime-aes.c's reason: the
68
+ constant-time check compiles this file for targets whose libc headers the
69
+ host does not have. */
70
+ char *getenv(const char *name);
71
+
72
+ #if defined(__has_attribute) && __has_attribute(no_builtin)
73
+ #define NISH_CHACHA20_NO_LIBC no_builtin
74
+ #else
75
+ #define NISH_CHACHA20_NO_LIBC
76
+ #endif
77
+ #if defined(NISH_CHACHA20_X86)
78
+ #define NISH_CHACHA20_TARGET __attribute__((target("avx2"), NISH_CHACHA20_NO_LIBC))
79
+ #else
80
+ #define NISH_CHACHA20_TARGET __attribute__((NISH_CHACHA20_NO_LIBC))
81
+ #endif
82
+ #define NISH_CHACHA20_INLINE static inline __attribute__((always_inline)) NISH_CHACHA20_TARGET
83
+
84
+ /* The buffer a vector loop needs before it runs: four blocks. */
85
+ #define NISH_CHACHA20_MIN 256
86
+
87
+ /* Zeroes `n` bytes, a multiple of 8, by volatile 8-byte stores. */
88
+ NISH_CHACHA20_INLINE void nish_chacha20_wipe(void *p, size_t n) {
89
+ volatile uint64_t *v = (volatile uint64_t *)p;
90
+ for (size_t i = 0; i < n / 8; i++) v[i] = 0;
91
+ }
92
+
93
+ NISH_CHACHA20_INLINE uint32_t nish_chacha20_rotl(uint32_t x, int n) {
94
+ return (x << n) | (x >> (32 - n));
95
+ }
96
+
97
+ #define NISH_CHACHA20_QR(a, b, c, d) \
98
+ a += b; \
99
+ d = nish_chacha20_rotl(d ^ a, 16); \
100
+ c += d; \
101
+ b = nish_chacha20_rotl(b ^ c, 12); \
102
+ a += b; \
103
+ d = nish_chacha20_rotl(d ^ a, 8); \
104
+ c += d; \
105
+ b = nish_chacha20_rotl(b ^ c, 7)
106
+
107
+ /* What is left after the vector loops, a block at a time as the Nish loop
108
+ does: `len` bytes under the keystream from `s`'s counter on, the counter
109
+ moved one a block, the last partial block taken byte by byte. */
110
+ NISH_CHACHA20_INLINE void nish_chacha20_tail(uint32_t s[16], const uint8_t *src, uint8_t *dst, size_t len) {
111
+ uint32_t x[16];
112
+ /* The block's words, which on these little-endian targets are its
113
+ keystream bytes in order: adding into words, not storing byte by byte,
114
+ keeps every store at a fixed offset of the frame. */
115
+ uint32_t ks[16];
116
+ const uint8_t *ks_bytes = (const uint8_t *)ks;
117
+ int ran = len > 0;
118
+ while (len > 0) {
119
+ for (int i = 0; i < 16; i++) x[i] = s[i];
120
+ for (int r = 0; r < 10; r++) {
121
+ NISH_CHACHA20_QR(x[0], x[4], x[8], x[12]);
122
+ NISH_CHACHA20_QR(x[1], x[5], x[9], x[13]);
123
+ NISH_CHACHA20_QR(x[2], x[6], x[10], x[14]);
124
+ NISH_CHACHA20_QR(x[3], x[7], x[11], x[15]);
125
+ NISH_CHACHA20_QR(x[0], x[5], x[10], x[15]);
126
+ NISH_CHACHA20_QR(x[1], x[6], x[11], x[12]);
127
+ NISH_CHACHA20_QR(x[2], x[7], x[8], x[13]);
128
+ NISH_CHACHA20_QR(x[3], x[4], x[9], x[14]);
129
+ }
130
+ for (int i = 0; i < 16; i++) ks[i] = x[i] + s[i];
131
+ s[12] += 1;
132
+ size_t take = len < 64 ? len : 64;
133
+ for (size_t i = 0; i < take; i++) dst[i] = src[i] ^ ks_bytes[i];
134
+ src += take;
135
+ dst += take;
136
+ len -= take;
137
+ }
138
+ if (ran) {
139
+ nish_chacha20_wipe(x, sizeof x);
140
+ nish_chacha20_wipe(ks, sizeof ks);
141
+ }
142
+ }
143
+
144
+ #if defined(NISH_CHACHA20_X86)
145
+
146
+ typedef uint32_t nish_u32x4 __attribute__((vector_size(16)));
147
+ typedef uint32_t nish_u32x8 __attribute__((vector_size(32)));
148
+
149
+ /* The rotate lane by lane; LLVM turns 16 and 8 into byte shuffles. */
150
+ #define NISH_CHACHA20_VROTL(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
151
+
152
+ #define NISH_CHACHA20_VQR(a, b, c, d) \
153
+ a += b; \
154
+ d = NISH_CHACHA20_VROTL(d ^ a, 16); \
155
+ c += d; \
156
+ b = NISH_CHACHA20_VROTL(b ^ c, 12); \
157
+ a += b; \
158
+ d = NISH_CHACHA20_VROTL(d ^ a, 8); \
159
+ c += d; \
160
+ b = NISH_CHACHA20_VROTL(b ^ c, 7)
161
+
162
+ #define NISH_CHACHA20_VROUNDS(x) \
163
+ for (int r = 0; r < 10; r++) { \
164
+ NISH_CHACHA20_VQR(x[0], x[4], x[8], x[12]); \
165
+ NISH_CHACHA20_VQR(x[1], x[5], x[9], x[13]); \
166
+ NISH_CHACHA20_VQR(x[2], x[6], x[10], x[14]); \
167
+ NISH_CHACHA20_VQR(x[3], x[7], x[11], x[15]); \
168
+ NISH_CHACHA20_VQR(x[0], x[5], x[10], x[15]); \
169
+ NISH_CHACHA20_VQR(x[1], x[6], x[11], x[12]); \
170
+ NISH_CHACHA20_VQR(x[2], x[7], x[8], x[13]); \
171
+ NISH_CHACHA20_VQR(x[3], x[4], x[9], x[14]); \
172
+ }
173
+
174
+ /* `lanes` blocks at once, `vec` lanes of `lanes` words: the input block with
175
+ the counter stepped one a lane, the rounds, then the sum written out word
176
+ by word (lane `b` of word `i` to byte 64b + 4i of the keystream) and
177
+ xored into `dst` a vector at a time. Answers the bytes it covered. */
178
+ #define NISH_CHACHA20_LOOP(vec, lanes, s, src, dst, len, ...) \
179
+ { \
180
+ vec x[16]; \
181
+ vec in[16]; \
182
+ uint8_t ks[64 * (lanes)] __attribute__((aligned(32))); \
183
+ const vec step = {__VA_ARGS__}; \
184
+ int ran = (len) >= 64 * (lanes); \
185
+ while ((len) >= 64 * (lanes)) { \
186
+ for (int i = 0; i < 16; i++) in[i] = (vec){0} + (s)[i]; \
187
+ in[12] += step; \
188
+ for (int i = 0; i < 16; i++) x[i] = in[i]; \
189
+ NISH_CHACHA20_VROUNDS(x) \
190
+ for (int i = 0; i < 16; i++) { \
191
+ vec w = x[i] + in[i]; \
192
+ for (int b = 0; b < (lanes); b++) __builtin_memcpy(ks + 64 * b + 4 * i, (const uint8_t *)&w + 4 * b, 4); \
193
+ } \
194
+ for (size_t i = 0; i < 64 * (lanes); i += sizeof(vec)) { \
195
+ vec p, k; \
196
+ __builtin_memcpy(&p, (src) + i, sizeof p); \
197
+ __builtin_memcpy(&k, ks + i, sizeof k); \
198
+ p ^= k; \
199
+ __builtin_memcpy((dst) + i, &p, sizeof p); \
200
+ } \
201
+ (s)[12] += (lanes); \
202
+ (src) += 64 * (lanes); \
203
+ (dst) += 64 * (lanes); \
204
+ (len) -= 64 * (lanes); \
205
+ } \
206
+ if (ran) { \
207
+ nish_chacha20_wipe(x, sizeof x); \
208
+ nish_chacha20_wipe(in, sizeof in); \
209
+ nish_chacha20_wipe(ks, sizeof ks); \
210
+ } \
211
+ }
212
+
213
+ NISH_CHACHA20_INLINE void nish_chacha20_blocks(uint32_t s[16], const uint8_t *src, uint8_t *dst, size_t len) {
214
+ NISH_CHACHA20_LOOP(nish_u32x8, 8, s, src, dst, len, 0, 1, 2, 3, 4, 5, 6, 7)
215
+ NISH_CHACHA20_LOOP(nish_u32x4, 4, s, src, dst, len, 0, 1, 2, 3)
216
+ nish_chacha20_tail(s, src, dst, len);
217
+ }
218
+
219
+ #else
220
+
221
+ #define NISH_CHACHA20_NROTL(x, n) vsriq_n_u32(vshlq_n_u32((x), (n)), (x), 32 - (n))
222
+ #define NISH_CHACHA20_NROT16(x) vreinterpretq_u32_u16(vrev32q_u16(vreinterpretq_u16_u32(x)))
223
+ #define NISH_CHACHA20_NQR(a, b, c, d) \
224
+ a = vaddq_u32(a, b); \
225
+ d = NISH_CHACHA20_NROT16(veorq_u32(d, a)); \
226
+ c = vaddq_u32(c, d); \
227
+ b = NISH_CHACHA20_NROTL(veorq_u32(b, c), 12); \
228
+ a = vaddq_u32(a, b); \
229
+ d = NISH_CHACHA20_NROTL(veorq_u32(d, a), 8); \
230
+ c = vaddq_u32(c, d); \
231
+ b = NISH_CHACHA20_NROTL(veorq_u32(b, c), 7)
232
+
233
+ /* Four blocks at once in NEON, a block a lane; `vst4q` interleaves four
234
+ words so each 16-byte row it stores is one row of one block. */
235
+ NISH_CHACHA20_INLINE void nish_chacha20_blocks(uint32_t s[16], const uint8_t *src, uint8_t *dst, size_t len) {
236
+ uint32x4_t x[16];
237
+ uint32x4_t in[16];
238
+ uint32_t rows[16] __attribute__((aligned(16)));
239
+ uint8_t ks[256] __attribute__((aligned(16)));
240
+ const uint32_t step_words[4] = {0, 1, 2, 3};
241
+ const uint32x4_t step = vld1q_u32(step_words);
242
+ int ran = len >= 256;
243
+ while (len >= 256) {
244
+ for (int i = 0; i < 16; i++) in[i] = vdupq_n_u32(s[i]);
245
+ in[12] = vaddq_u32(in[12], step);
246
+ for (int i = 0; i < 16; i++) x[i] = in[i];
247
+ for (int r = 0; r < 10; r++) {
248
+ NISH_CHACHA20_NQR(x[0], x[4], x[8], x[12]);
249
+ NISH_CHACHA20_NQR(x[1], x[5], x[9], x[13]);
250
+ NISH_CHACHA20_NQR(x[2], x[6], x[10], x[14]);
251
+ NISH_CHACHA20_NQR(x[3], x[7], x[11], x[15]);
252
+ NISH_CHACHA20_NQR(x[0], x[5], x[10], x[15]);
253
+ NISH_CHACHA20_NQR(x[1], x[6], x[11], x[12]);
254
+ NISH_CHACHA20_NQR(x[2], x[7], x[8], x[13]);
255
+ NISH_CHACHA20_NQR(x[3], x[4], x[9], x[14]);
256
+ }
257
+ for (int i = 0; i < 16; i += 4) {
258
+ uint32x4x4_t w;
259
+ w.val[0] = vaddq_u32(x[i], in[i]);
260
+ w.val[1] = vaddq_u32(x[i + 1], in[i + 1]);
261
+ w.val[2] = vaddq_u32(x[i + 2], in[i + 2]);
262
+ w.val[3] = vaddq_u32(x[i + 3], in[i + 3]);
263
+ vst4q_u32(rows, w);
264
+ for (int b = 0; b < 4; b++) __builtin_memcpy(ks + 64 * b + 4 * i, rows + 4 * b, 16);
265
+ }
266
+ for (size_t i = 0; i < 256; i += 16) vst1q_u8(dst + i, veorq_u8(vld1q_u8(src + i), vld1q_u8(ks + i)));
267
+ s[12] += 4;
268
+ src += 256;
269
+ dst += 256;
270
+ len -= 256;
271
+ }
272
+ if (ran) {
273
+ nish_chacha20_wipe(x, sizeof x);
274
+ nish_chacha20_wipe(in, sizeof in);
275
+ nish_chacha20_wipe(rows, sizeof rows);
276
+ nish_chacha20_wipe(ks, sizeof ks);
277
+ }
278
+ nish_chacha20_tail(s, src, dst, len);
279
+ }
280
+
281
+ #endif
282
+
283
+ /* `chacha20XorInto`'s contract, checked rather than trusted: 1 when it ran,
284
+ 0, writing nothing, unless `state` has its sixteen words, `len` is four
285
+ blocks or more, both windows are inside their arrays and end before
286
+ INT32_MAX - 64, and the windows are the same bytes or do not overlap.
287
+ The Nish function reads and writes byte
288
+ by byte in order, so an in-place call (the AEAD's and `chacha20Block`'s)
289
+ agrees with a kernel that loads a vector before it stores it; a shifted
290
+ overlap would not, and is left to the Nish function. */
291
+ NISH_CHACHA20_TARGET int32_t nish_chacha20_xor(nish_array *state, const nish_array *src, int32_t src_at,
292
+ int32_t len, nish_array *dst, int32_t dst_at) {
293
+ if (state->len < 16 || len < NISH_CHACHA20_MIN || src_at < 0 || dst_at < 0) return 0;
294
+ if ((uint64_t)src_at + (uint64_t)len > src->len || (uint64_t)dst_at + (uint64_t)len > dst->len) return 0;
295
+ /* The Nish function's i32 sums reach a window's end and its last block's
296
+ end, so past INT32_MAX - 64 it panics on an overflow; past that the
297
+ kernel refuses, and the Nish function panics as it would have. */
298
+ if ((uint64_t)src_at + (uint64_t)len > INT32_MAX - 64 || (uint64_t)dst_at + (uint64_t)len > INT32_MAX - 64) return 0;
299
+ const uint8_t *from = (const uint8_t *)src->data + src_at;
300
+ uint8_t *to = (uint8_t *)dst->data + dst_at;
301
+ uintptr_t f = (uintptr_t)from, t = (uintptr_t)to;
302
+ if (f != t && f < t + (uintptr_t)len && t < f + (uintptr_t)len) return 0;
303
+ nish_chacha20_blocks((uint32_t *)(void *)state->data, from, to, (size_t)len);
304
+ return 1;
305
+ }
306
+
307
+ #if defined(NISH_CHACHA20_X86)
308
+ /* Whether this CPU and its OS give AVX2: CPUID leaf 1's OSXSAVE and AVX,
309
+ XCR0's SSE and AVX state, and leaf 7's AVX2. Asked through `cpuid.h`, for
310
+ runtime-aes.c's reason. */
311
+ static int32_t nish_chacha20_cpu(void) {
312
+ unsigned a, b, c, d;
313
+ if (!__get_cpuid(1, &a, &b, &c, &d)) return 0;
314
+ if ((c & bit_OSXSAVE) == 0 || (c & bit_AVX) == 0) return 0;
315
+ unsigned lo, hi;
316
+ __asm__("xgetbv" : "=a"(lo), "=d"(hi) : "c"(0));
317
+ (void)hi;
318
+ if ((lo & 6) != 6) return 0;
319
+ if (!__get_cpuid_count(7, 0, &a, &b, &c, &d)) return 0;
320
+ return (b & bit_AVX2) != 0;
321
+ }
322
+ #else
323
+ /* NEON is in every AArch64 CPU, so the question is only whether the kernel
324
+ pays here: `NISH_CHACHA20_ARM_ON` is S10's AArch64 verdict
325
+ (docs/wp38-simd.md §3.1). */
326
+ #ifndef NISH_CHACHA20_ARM_ON
327
+ #define NISH_CHACHA20_ARM_ON 1
328
+ #endif
329
+ static int32_t nish_chacha20_cpu(void) {
330
+ return NISH_CHACHA20_ARM_ON;
331
+ }
332
+ #endif
333
+
334
+ /* The answer: 0 until the first call, then 1 for the kernel or 2 for the Nish
335
+ path, with runtime-aes.c's relaxed atomics for the same reason. */
336
+ static int32_t nish_chacha20_answer;
337
+
338
+ int32_t nish_chacha20_hw(void) {
339
+ int32_t answer = __atomic_load_n(&nish_chacha20_answer, __ATOMIC_RELAXED);
340
+ if (answer == 0) {
341
+ /* `soft` pins the Nish path and never asks the CPU; anything else, or
342
+ nothing, means the best this machine has. */
343
+ const char *pin = getenv("NISH_CHACHA20");
344
+ int soft = pin != 0 && pin[0] == 's' && pin[1] == 'o' && pin[2] == 'f' && pin[3] == 't' && pin[4] == 0;
345
+ answer = !soft && nish_chacha20_cpu() ? 1 : 2;
346
+ __atomic_store_n(&nish_chacha20_answer, answer, __ATOMIC_RELAXED);
347
+ }
348
+ return answer == 1;
349
+ }
350
+
351
+ #else
352
+
353
+ /* A target with no vector path here, wasm32 among them: the Nish path
354
+ always, and a kernel that a program whose every call is behind
355
+ `nish_chacha20_hw` never reaches. */
356
+ int32_t nish_chacha20_hw(void) {
357
+ return 0;
358
+ }
359
+
360
+ int32_t nish_chacha20_xor(nish_array *state, const nish_array *src, int32_t src_at, int32_t len, nish_array *dst,
361
+ int32_t dst_at) {
362
+ (void)state, (void)src, (void)src_at, (void)len, (void)dst, (void)dst_at;
363
+ __builtin_trap();
364
+ }
365
+
366
+ #endif
package/scripts/build.sh CHANGED
@@ -3,7 +3,7 @@
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 fifteen translation units and is named as one: an input
6
+ # The C runtime is sixteen 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
@@ -14,6 +14,7 @@
14
14
  # the SSE4.1 rounding of `Math.floor` and its kin,
15
15
  # <dir>/runtime-aes.c, AES-GCM on the CPU's AES instructions,
16
16
  # <dir>/runtime-sha256.c, SHA-256 on the CPU's SHA instructions,
17
+ # <dir>/runtime-chacha20.c, ChaCha20's keystream on AVX2 or NEON vectors,
17
18
  # <dir>/runtime-checksum.c, CRC-32 on its carry-less multiply or CRC instructions,
18
19
  # <dir>/runtime-term.c, stdin and the terminal of `nish:term`,
19
20
  # <dir>/runtime-bytes.c, the byte-class runs of `nish/bytes`,
@@ -223,14 +224,14 @@ if [ -n "$sqlite_to" ] || [ -n "$sqlite_from" ]; then
223
224
  fi
224
225
  [ -n "$out" ] || [ -n "$objects_to" ] || [ -n "$sqlite_to" ] || { echo "error: -o <out> is required" >&2; exit 2; }
225
226
 
226
- # The runtime is fifteen translation units, and a caller names one: whoever passes
227
+ # The runtime is sixteen translation units, and a caller names one: whoever passes
227
228
  # <dir>/runtime.c gets <dir>/runtime-os.c, <dir>/runtime-parallel.c,
228
229
  # <dir>/runtime-cross.c, <dir>/runtime-host.c, <dir>/runtime-net.c, <dir>/runtime-simd.c,
229
- # <dir>/runtime-aes.c, <dir>/runtime-sha256.c, <dir>/runtime-checksum.c, <dir>/runtime-term.c,
230
+ # <dir>/runtime-aes.c, <dir>/runtime-sha256.c, <dir>/runtime-chacha20.c, <dir>/runtime-checksum.c, <dir>/runtime-term.c,
230
231
  # <dir>/runtime-bytes.c, <dir>/runtime-sandbox.c, <dir>/runtime-shm.c and <dir>/runtime-region.c compiled beside it, on every profile,
231
232
  # wasi included: runtime-simd.c and runtime-bytes.c take their scalar paths there
232
233
  # unless the build adds -msimd128,
233
- # runtime-aes.c, runtime-sha256.c and runtime-checksum.c compile to an answer of 0 and
234
+ # runtime-aes.c, runtime-sha256.c, runtime-chacha20.c and runtime-checksum.c compile to an answer of 0 and
234
235
  # stubs that nothing calls, because the compiler emits no call to their kernels for wasm32, and
235
236
  # runtime-term.c reads stdin and answers "not a terminal", and runtime-sandbox.c
236
237
  # compiles to two functions that refuse to run, which no wasm build calls, and
@@ -244,7 +245,7 @@ fi
244
245
  # here keeps every one of those correct, and keeps "the runtime" one thing to
245
246
  # name from the outside. A caller that names one itself is left alone, because
246
247
  # naming one object twice is a duplicate-symbol error.
247
- 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 runtime-shm.c runtime-region.c"
248
+ 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-chacha20.c runtime-checksum.c runtime-term.c runtime-bytes.c runtime-sandbox.c runtime-shm.c runtime-region.c"
248
249
  for i in ${inputs[@]+"${inputs[@]}"}; do
249
250
  case "$i" in
250
251
  */runtime.c|runtime.c)
@@ -693,14 +694,16 @@ case "$profile" in
693
694
  fi
694
695
  # IR compiled for the host rather than with `--target wasm32` calls std/crypto/aes.ts's
695
696
  # AES kernels behind `nish_aes_hw`, std/crypto/sha256.ts's block function behind
696
- # `nish_sha256_hw` and std/compress/checksum.ts's CRC-32 kernel behind
697
- # `nish_crc32_hw` (src/kernels.ts). runtime-aes.c's, runtime-sha256.c's and
698
- # runtime-checksum.c's wasm32 builds answer 0 there, so the Nish code runs, and
697
+ # `nish_sha256_hw`, std/crypto/chacha20poly1305.ts's keystream loop behind
698
+ # `nish_chacha20_hw` and std/compress/checksum.ts's CRC-32 kernel behind
699
+ # `nish_crc32_hw` (src/kernels.ts). runtime-aes.c's, runtime-sha256.c's,
700
+ # runtime-chacha20.c's and runtime-checksum.c's wasm32 builds answer 0 there,
701
+ # so the Nish code runs, and
699
702
  # each is added only for such IR: every other module links the bytes it always did.
700
703
  # Each pair is a unit and the kernel its probe names.
701
704
  # A caller that names the unit itself is left alone, as `halves` leaves it:
702
705
  # one object named twice is a duplicate-symbol error.
703
- for pair in aes:aes sha256:sha256 checksum:crc32; do
706
+ for pair in aes:aes sha256:sha256 chacha20:chacha20 checksum:crc32; do
704
707
  unit=${pair%%:*}
705
708
  kernel=${pair#*:}
706
709
  named=0
package/std/README.md CHANGED
@@ -101,7 +101,7 @@ is listed in [`SECURITY.md`](../SECURITY.md#threat-model).
101
101
  | [`crypto/sha1.ts`](./crypto/sha1.ts) | `sha1(data)`, a fresh 20-byte digest, and `SHA1_SIZE` and `SHA1_BLOCK`. **Not for security**: SHA-1 is broken for collisions, and it is here only because RFC 6455 §4.2.2's `Sec-WebSocket-Accept` is defined on it (`nish/net/websocket` is its importer). One-shot only; it panics on an array longer than 2^31 − 1 bytes. It came after the #363 audit and has no record in `docs/security/` | FIPS 180-4 §6.1 |
102
102
  | [`crypto/x25519.ts`](./crypto/x25519.ts) | `x25519(scalar, u)`, the shared secret as a `Secret<u8[]>`, and `x25519Base(scalar)`, the public key, each from a `Secret<u8[]>` scalar, on ten 25.5-bit limbs in `i64`. Either answers `null` unless its arguments are `X25519_SIZE` (32) bytes; the scalar is clamped on a copy, which is wiped | RFC 7748 §5.2, §6.1, Wycheproof `x25519_test` |
103
103
  | [`crypto/ed25519.ts`](./crypto/ed25519.ts) | `ed25519PublicKey(seed)`, the 32-byte public key, `ed25519Sign(seed, msg)`, the 64-byte signature `R \|\| S`, each from a `Secret<u8[]>` seed of `ED25519_SEED_SIZE` (32) bytes or `null`, and `ed25519Verify(pub, msg, sig)`. Signing is deterministic and constant time; the seed's hash, the nonce and every intermediate the scalar reaches are wiped. Verify is cofactorless, refuses an S of L or more and a key that does not decode, and is variable time, since its inputs are public. Sign and verify panic on a message longer than 2^31 − 1 bytes. It reuses `crypto/x25519.ts`'s field arithmetic | RFC 8032 §5.1, §7.1 |
104
- | [`crypto/chacha20poly1305.ts`](./crypto/chacha20poly1305.ts) | `chacha20Poly1305Seal(key, nonce, aad, plaintext)`, which answers the ciphertext followed by the 16-byte tag, and `chacha20Poly1305Open`, which checks the whole tag before it decrypts anything and answers `null` for a message that does not authenticate. Beneath them `chacha20Block`, `chacha20`, `chacha20QuarterRound`, `poly1305` and `poly1305KeyGen`, and `chacha20HeaderMask`, QUIC's 5-byte header-protection mask. Every input of the wrong length answers `null`. What a call derives from the key — the ChaCha20 state, the keystream, the Poly1305 key, `r` and accumulator, and on open the tag it compared — is wiped with `wipe`'s volatile stores before the call returns, the refusal path included | RFC 8439 §2, RFC 9001 §5.4.4, Wycheproof `chacha20_poly1305` (all 325 cases) |
104
+ | [`crypto/chacha20poly1305.ts`](./crypto/chacha20poly1305.ts) | `chacha20Poly1305Seal(key, nonce, aad, plaintext)`, which answers the ciphertext followed by the 16-byte tag, and `chacha20Poly1305Open`, which checks the whole tag before it decrypts anything and answers `null` for a message that does not authenticate. Beneath them `chacha20Block`, `chacha20`, `chacha20QuarterRound`, `poly1305` and `poly1305KeyGen`, and `chacha20HeaderMask`, QUIC's 5-byte header-protection mask. Every input of the wrong length answers `null`. What a call derives from the key — the ChaCha20 state, the keystream, the Poly1305 key, `r` and accumulator, and on open the tag it compared — is wiped with `wipe`'s volatile stores before the call returns, the refusal path included. Natively, on x86-64 with AVX2 or on AArch64, the compiler sends the keystream of a message of 256 bytes or more to the runtime's kernel instead (`runtime/runtime-chacha20.c`, WP38 S10), and `NISH_CHACHA20=soft` pins the Nish code | RFC 8439 §2, RFC 9001 §5.4.4, Wycheproof `chacha20_poly1305` (all 325 cases) |
105
105
  | [`crypto/aes.ts`](./crypto/aes.ts) | AES-128 and AES-256, bitsliced four blocks at a time with the Boyar–Peralta S-box circuit, so no table is read; natively, on a CPU with AES-NI and PCLMULQDQ or the ARMv8 AES and PMULL instructions, the compiler sends the key schedule, the block, GCTR and GHASH to the runtime's kernels instead (`runtime/runtime-aes.c`, WP38 S5), and `NISH_AES=soft` pins the bitsliced code: `aesKey(key)` expands a 16- or 32-byte key into an `AesKey` (or `aesKeyInto(key, out)` into one the caller holds, allocating nothing that outlives the call), then `aesEncryptBlock`, `aesGcmSeal` / `aesGcmOpen` (tag last, checked in full before anything is decrypted; any non-empty IV) and `aesHeaderMask`. AES-192 is out of scope, and a wrong length answers `null`, as does an `AesKey` not made by `aesKey`; one whose H is zero opens nothing. An `AesKey` holds its round keys in `roundKeys` and H in `h`, two `u64` words (high, low); `aesKeyWipe(key)` zeroes both with `wipe`'s volatile stores, the kernels' powers of H after the schedule included, and is how a holder drops a key. The bitsliced schedule's, block's and GCTR's working copies, the tag's E(K, J0) block, the tag open compared and J0 itself are wiped the same way before they are released | FIPS 197, SP 800-38D, RFC 9001 §5.4.3, Wycheproof `aes_gcm` (all 316 cases; the 103 AES-192 ones check that the key is refused) |
106
106
  | [`crypto/p256.ts`](./crypto/p256.ts) | ECDSA over P-256 with RFC 6979's deterministic nonces: `p256PublicKey(priv)` (65-byte uncompressed SEC1) from a `Secret<u8[]>` private key, `p256Sign` / `p256Verify` over a digest and `p256SignSha256` / `p256VerifySha256` over a message, and `p256SignSha256Into(scratch, priv, msg, off, len, out, at)`, the same signature written into the caller's window in a `P256SignScratch` made once, keeping nothing in the arena; a signature is `r` and `s`, 32 big-endian bytes each, not DER (`crypto/x509.ts`'s `x509DerSignatureRS` converts one). A malformed key, a point off the curve or an `r` or `s` out of range answers `null` or `false`, never a panic. ECDSA is malleable, and `p256Verify` accepts a high `s`: when `(r, s)` verifies, so does `(r, n − s)` | RFC 6979 A.2.5, Wycheproof `ecdsa_secp256r1_sha256`, `_sha256_p1363`, `_sha512` and `_sha512_p1363` |
107
107
  | [`crypto/p384.ts`](./crypto/p384.ts) | ECDSA verification over P-384: `p384Verify(pub, digest, sig)` over a digest from any hash (one longer than 48 bytes is truncated to its leftmost 384 bits) and `p384VerifySha384(pub, msg, sig)` over a message; the key is 97-byte uncompressed SEC1 and the signature `r` and `s`, 48 big-endian bytes each, not DER. It only verifies, so it holds no `Secret`. A malformed key, a point off the curve, an `r` or `s` out of range and a sum at the identity answer `false`, never a panic. ECDSA is malleable, and `p384Verify` accepts a high `s` | RFC 6979 A.2.6, Wycheproof `ecdsa_secp384r1_sha384`, `_sha384_p1363`, `_sha256`, `_sha512` and `_sha512_p1363` |
@@ -506,17 +506,19 @@ const http1StartRequest = (p: Http1Parser): void => {
506
506
  }
507
507
 
508
508
  /**
509
- * Whether any of the eight bytes of `x` is below 32 or is 127: a control
510
- * byte or DEL, which `http1TakeLine` stops at. Two of the bit tricks of
511
- * "Bit Twiddling Hacks" (S. E. Anderson, public domain): a byte below `n`
512
- * leaves its high bit set in `(x - n * ones) & ~x`, for `n` up to 128, and
513
- * `127` is the byte that is zero in `x ^ 127 * ones`. A byte from 128 up
514
- * has its high bit set in `x`, so `~x` clears it: obs-text never matches.
515
- * The answer is exact for the word, though not for which byte. Each
516
- * subtraction is masked to 64 bits because a `u64` is a BigInt under Node,
517
- * which does not wrap; natively the mask is a no-op.
509
+ * The bytes of `x` below 32 or equal to 127 — a control byte or DEL, which
510
+ * `http1TakeLine` stops at — as their high bits: zero when there is none,
511
+ * and else the lowest set bit is the first such byte's. Two of the bit
512
+ * tricks of "Bit Twiddling Hacks" (S. E. Anderson, public domain): a byte
513
+ * below `n` leaves its high bit set in `(x - n * ones) & ~x`, for `n` up to
514
+ * 128, and `127` is the byte that is zero in `x ^ 127 * ones`. A byte from
515
+ * 128 up has its high bit set in `x`, so `~x` clears it: obs-text never
516
+ * matches. A borrow only runs up from a byte that matched, so the bits above
517
+ * the first match may be false and the lowest is exact, in each mask and so
518
+ * in their union. Each subtraction is masked to 64 bits because a `u64` is a
519
+ * BigInt under Node, which does not wrap; natively the mask is a no-op.
518
520
  */
519
- const http1WordHasControl = (x: u64): boolean => {
521
+ const http1ControlBits = (x: u64): u64 => {
520
522
  const ones32: u64 = toU64(16843009)
521
523
  const ones: u64 = (ones32 << toU64(32)) | ones32
522
524
  const low32: u64 = 0xffffffff
@@ -525,7 +527,18 @@ const http1WordHasControl = (x: u64): boolean => {
525
527
  const below: u64 = (x - ones * toU64(32)) & all & ~x & high
526
528
  const y: u64 = x ^ (ones * toU64(127))
527
529
  const del: u64 = (y - ones) & all & ~y & high
528
- return (below | del) !== toU64(0)
530
+ return below | del
531
+ }
532
+
533
+ /** Which byte of a word `bits`'s lowest set bit is in, 0 to 7; `bits` is not zero. */
534
+ const http1FirstByte = (bits: u64): i32 => {
535
+ const mask32: u64 = 0xffffffff
536
+ const lo: u32 = toU32(bits & mask32)
537
+ if (lo !== toU32(0)) {
538
+ return (toI32(31) - toI32(Math.clz32(lo & (toU32(0) - lo)))) >> toI32(3)
539
+ }
540
+ const hi: u32 = toU32(bits >> toU64(32))
541
+ return toI32(4) + ((toI32(31) - toI32(Math.clz32(hi & (toU32(0) - hi)))) >> toI32(3))
529
542
  }
530
543
 
531
544
  /**
@@ -585,22 +598,39 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
585
598
  }
586
599
  while (i < stop) {
587
600
  // Most bytes are printable: sixteen at a time while a whole vector is,
588
- // then one at a time, with no test in either loop but the bytes' and the
589
- // bound's.
590
- while (i >= 0 && i <= stop - 16 && i <= toI32(buf.length) - 16) {
601
+ // and the vector that is not answers where its first control byte is.
602
+ // The bytes after a control byte, and a tail shorter than a vector, go
603
+ // one at a time.
604
+ let found: boolean = false
605
+ while (!found && i >= 0 && i <= stop - 16 && i <= toI32(buf.length) - 16) {
591
606
  const v: u8x16 = loadU8x16(buf, i)
592
- if (http1WordHasControl(http1Lanes(v, 0)) || http1WordHasControl(http1Lanes(v, 8))) {
593
- break
607
+ const low: u64 = http1ControlBits(http1Lanes(v, 0))
608
+ if (low !== toU64(0)) {
609
+ i += http1FirstByte(low)
610
+ found = true
611
+ } else {
612
+ const high: u64 = http1ControlBits(http1Lanes(v, 8))
613
+ if (high !== toU64(0)) {
614
+ i += 8 + http1FirstByte(high)
615
+ found = true
616
+ } else {
617
+ i += 16
618
+ }
594
619
  }
595
- i += 16
596
620
  }
597
- while (i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
621
+ while (!found && i >= 0 && i < stop && i < toI32(buf.length) && buf[i] >= 32 && buf[i] !== 127) {
598
622
  i += 1
599
623
  }
600
624
  if (i < 0 || i >= stop || i >= toI32(buf.length)) {
601
625
  break
602
626
  }
603
- const c: i32 = toI32(buf[i])
627
+ let c: i32 = toI32(buf[i])
628
+ if (c === 13 && i + 1 < stop && i + 1 < toI32(buf.length) && buf[i + 1] === 10) {
629
+ // CRLF, the end of almost every line: its LF is the next byte, so it
630
+ // is taken here rather than by another trip through the scan.
631
+ i += 1
632
+ c = 10
633
+ }
604
634
  if (c === 10) {
605
635
  if (i === p.start || buf[i - 1] !== 13) {
606
636
  return http1Fail(p, 400, "a line ended in a bare LF")
@@ -636,9 +666,11 @@ const http1TakeLine = (p: Http1Parser, limit: i32, overStatus: i32): i32 => {
636
666
  * `c - 64` and `c - 96` of each: `!` `#`-`'` `*` `+` `-` `.` and the digits
637
667
  * in the first (0x03ff6cfa); the upper-case letters, `^` and `_` in the
638
668
  * second (0xc7fffffe); `` ` ``, the lower-case letters, `|` and `~` in the
639
- * third (0x57ffffff). No byte below 33 or above 126 is a tchar. One shift
640
- * and mask replaces a chain of fourteen comparisons on the parser's hottest
641
- * path (`bench/techempower`).
669
+ * third (0x57ffffff). No byte below 33 or above 126 is a tchar.
670
+ * `http1IsTokenByte` reads them as two 64-bit masks, one for the bytes 0 to
671
+ * 63 and one for 64 to 127, so the test is a select and a shift with no
672
+ * branch on the byte: a name or method scan is the parser's hottest loop
673
+ * after the line scan (`bench/http1-parse`).
642
674
  */
643
675
  const HTTP1_TCHAR_32: i32 = 67071226
644
676
  const HTTP1_TCHAR_64: i32 = -939524098
@@ -646,16 +678,10 @@ const HTTP1_TCHAR_96: i32 = 1476395007
646
678
 
647
679
  /** Whether `c` is a tchar of RFC 9110 §5.6.2, the bytes a token is made of. */
648
680
  export const http1IsTokenByte = (c: i32): boolean => {
649
- if (c < 32 || c > 127) {
650
- return false
651
- }
652
- let mask: i32 = HTTP1_TCHAR_96
653
- if (c < 64) {
654
- mask = HTTP1_TCHAR_32
655
- } else if (c < 96) {
656
- mask = HTTP1_TCHAR_64
657
- }
658
- return ((mask >> (c & 31)) & 1) !== 0
681
+ const low: u64 = toU64(toU32(HTTP1_TCHAR_32)) << toU64(32)
682
+ const high: u64 = (toU64(toU32(HTTP1_TCHAR_96)) << toU64(32)) | toU64(toU32(HTTP1_TCHAR_64))
683
+ const word: u64 = c < 64 ? low : high
684
+ return c >= 0 && c < 128 && ((word >> toU64(c & 63)) & toU64(1)) !== toU64(0)
659
685
  }
660
686
 
661
687
  /** Whether `c` may appear in a field value (RFC 9110 §5.5): HTAB, SP, VCHAR or obs-text. */
@@ -978,9 +1004,30 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
978
1004
  if (a >= 0 && a < b && (buf[a] === 32 || buf[a] === 9)) {
979
1005
  return http1Fail(p, 400, "a field line that starts with whitespace (obs-fold)")
980
1006
  }
1007
+ // A kept line is copied into the head first, and its name is read there,
1008
+ // each upper-case letter lowered as the scan passes it: one walk over the
1009
+ // name, not a check of `buf` and then a second walk of the copy. The copy
1010
+ // of a line refused below is never read, since a refusal ends the parse.
1011
+ let at: i32 = 0
981
1012
  let i: i32 = a
982
- while (i >= 0 && i < b && i < toI32(buf.length) && http1IsTokenByte(toI32(buf[i]))) {
983
- i += 1
1013
+ if (keep) {
1014
+ at = http1HeadAppend(p, a, b)
1015
+ const head: u8[] = p.head
1016
+ let k: i32 = at
1017
+ const stop: i32 = at + (b - a)
1018
+ while (k >= 0 && k < stop && k < toI32(head.length)) {
1019
+ const c: i32 = toI32(head[k])
1020
+ if (!http1IsTokenByte(c)) {
1021
+ break
1022
+ }
1023
+ head[k] = toU8(c >= 65 && c <= 90 ? c + 32 : c)
1024
+ k += 1
1025
+ }
1026
+ i = a + (k - at)
1027
+ } else {
1028
+ while (i >= 0 && i < b && i < toI32(buf.length) && http1IsTokenByte(toI32(buf[i]))) {
1029
+ i += 1
1030
+ }
984
1031
  }
985
1032
  if (i === a || i >= b || buf[i] !== 58) {
986
1033
  return http1Fail(p, 400, "a malformed field name")
@@ -1005,13 +1052,6 @@ const http1FieldLine = (p: Http1Parser, keep: boolean): i32 => {
1005
1052
  while (valueEnd > valueStart && valueEnd - 1 >= 0 && http1IsOws(toI32(buf[valueEnd - 1]))) {
1006
1053
  valueEnd -= 1
1007
1054
  }
1008
- const at: i32 = http1HeadAppend(p, a, b)
1009
- const head: u8[] = p.head
1010
- for (let k: i32 = at; k < at + (nameEnd - a) && k < toI32(head.length); k += 1) {
1011
- if (k >= 0 && head[k] >= 65 && head[k] <= 90) {
1012
- head[k] = head[k] + 32
1013
- }
1014
- }
1015
1055
  const base: i32 = 4 * p.headerCount
1016
1056
  http1SpanSet(p, base + 3, at + (valueEnd - a))
1017
1057
  // The last of the four is set first, so the array has room for them all.
@@ -525,28 +525,19 @@ export class Http1OutputPool {
525
525
  * `output[outputStart .. outputEnd)` for its carrier to send.
526
526
  */
527
527
  export class Http1Connection {
528
- // References, then 64-bit fields, then 32-bit ones, then flags: the order
529
- // the layout packs without padding.
530
- config: Http1Config
531
- /**
532
- * The `Date` every final response carries. The connection makes its own;
533
- * `Http1Server` and `Http1TlsServer` put theirs here, so that every slot of a
534
- * pool shares one and the pool formats at most once a second. A program may
535
- * put another, to share it or to pin its clock.
536
- */
537
- date: HttpDate
528
+ // What a request reads and writes comes first, so that `inputRoom`, `feed`,
529
+ // `next`, `respond`, `write`, `end`, `consume`, `release` and `park` touch
530
+ // the front of the object: the parser, the output and its pool, the clocks,
531
+ // the state and the flags. What a WebSocket, an error or a configured cap
532
+ // needs follows. Within each part the layout packs as widest first does:
533
+ // the narrow fields of the front end on a 4-byte boundary and the cold
534
+ // 32-bit fields bring the references behind them to an 8-byte one.
538
535
  /**
539
536
  * The request parser: its head (`methodIs`, `targetIs`, `header`, spans) is
540
537
  * the current request's from its `H1_REQUEST` until its response is done,
541
538
  * `H1_END` included, however much of a pipelined request is fed behind it.
542
539
  */
543
540
  parser: Http1Parser
544
- /**
545
- * The frame decoder: made when the slot first switches to WebSocket, so a
546
- * slot that only ever speaks HTTP never holds its buffers, then kept for
547
- * the slot's later peers.
548
- */
549
- ws: WsDecoder | null
550
541
  /**
551
542
  * Bytes to send are `output[outputStart .. outputEnd)`. Empty until the
552
543
  * connection first needs room to write: then its own buffer, made once, or
@@ -560,27 +551,17 @@ export class Http1Connection {
560
551
  /** Where the parser's input and head buffers go while the slot rests, with `Http1Config.idleInput`; null otherwise. */
561
552
  inputs: Http1OutputPool | null
562
553
  heads: Http1OutputPool | null
563
- /** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
564
- data: u8[]
565
- /** A ping's payload, kept until its pong fits: empty until the first ping. */
566
- pong: u8[]
567
- /** No fields, for the responses the connection writes itself: made once. */
568
- noFields: u8[][]
554
+ /**
555
+ * The `Date` every final response carries. The connection makes its own;
556
+ * `Http1Server` and `Http1TlsServer` put theirs here, so that every slot of a
557
+ * pool shares one and the pool formats at most once a second. A program may
558
+ * put another, to share it or to pin its clock.
559
+ */
560
+ date: HttpDate
569
561
  /** When the carrier last moved a byte for the connection, in `monotonicNanos`. */
570
562
  lastActive: i64 = 0
571
563
  /** When the head being read began to arrive, in `monotonicNanos`; meaningful while `headTimed`. */
572
564
  headSince: i64 = 0
573
- outputStart: i32 = 0
574
- outputEnd: i32 = 0
575
- dataStart: i32 = 0
576
- dataLength: i32 = 0
577
- /** `H1_WS_MESSAGE`'s opcode: text or binary. */
578
- opcode: i32 = 0
579
- /** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
580
- status: i32 = 0
581
- /** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
582
- bufferLimit: i32 = 0
583
- pongLength: i32 = 0
584
565
  state: i32 = 0
585
566
  response: i32 = 0
586
567
  /**
@@ -590,12 +571,23 @@ export class Http1Connection {
590
571
  responseFraming: i32 = 0
591
572
  /** A response with a length: the bytes it still owes. */
592
573
  responseLeft: i32 = 0
593
- /** The response carries no body (HEAD, 204, 304): what is written is dropped. */
594
- responseEmpty: boolean = false
574
+ outputStart: i32 = 0
575
+ outputEnd: i32 = 0
576
+ /**
577
+ * `output.length`, kept beside the offsets so that `acquire` and `release`
578
+ * learn whether the connection holds a buffer without reading an array:
579
+ * the empty one is out of cache by the time a resting connection's next
580
+ * request comes. Only the connection assigns `output`, and with it this.
581
+ */
582
+ outputCap: i32 = 0
583
+ /** The most input the connection buffers, unread: the cap `inputRoom` holds it to. */
584
+ bufferLimit: i32 = 0
595
585
  /** A request's head has been read and its exchange is not over. */
596
586
  requestOpen: boolean = false
597
587
  /** The request's body has been read to its end. */
598
588
  requestEnded: boolean = false
589
+ /** The response carries no body (HEAD, 204, 304): what is written is dropped. */
590
+ responseEmpty: boolean = false
599
591
  /** The connection closes once this exchange is over. */
600
592
  closeAfter: boolean = false
601
593
  /** The program accepted the request's WebSocket handshake. */
@@ -607,6 +599,13 @@ export class Http1Connection {
607
599
  inputEnded: boolean = false
608
600
  /** A head has begun to arrive and is not read whole yet: `headSince` is its deadline's start. */
609
601
  headTimed: boolean = false
602
+ dataStart: i32 = 0
603
+ dataLength: i32 = 0
604
+ /** `H1_WS_MESSAGE`'s opcode: text or binary. */
605
+ opcode: i32 = 0
606
+ /** `H1_ERROR`'s HTTP status or WebSocket close code, or `H1_WS_CLOSE`'s close code. */
607
+ status: i32 = 0
608
+ pongLength: i32 = 0
610
609
  /**
611
610
  * The TCP maximum segment size the program sets for this connection, for
612
611
  * `respondEncoded` under `HttpCompressor.segmentMode`; 0, the default,
@@ -616,6 +615,19 @@ export class Http1Connection {
616
615
  mss: i32 = 0
617
616
  /** The MSS `Http1Server.accept` read from this connection's socket (`netMss`), or 0 when it reports none. */
618
617
  socketMss: i32 = 0
618
+ config: Http1Config
619
+ /**
620
+ * The frame decoder: made when the slot first switches to WebSocket, so a
621
+ * slot that only ever speaks HTTP never holds its buffers, then kept for
622
+ * the slot's later peers.
623
+ */
624
+ ws: WsDecoder | null
625
+ /** `H1_BODY`'s and `H1_WS_MESSAGE`'s bytes. */
626
+ data: u8[]
627
+ /** A ping's payload, kept until its pong fits: empty until the first ping. */
628
+ pong: u8[]
629
+ /** No fields, for the responses the connection writes itself: made once. */
630
+ noFields: u8[][]
619
631
 
620
632
  /** A connection under `config`. A cap outside what the connection can work with panics. */
621
633
  constructor(config: Http1Config) {
@@ -759,10 +771,15 @@ export class Http1Connection {
759
771
  if (this.writeHeld || this.inputEnded || this.state === H1_S_CLOSING || this.state === H1_S_FAILED) {
760
772
  return 0
761
773
  }
762
- const ws: WsDecoder | null = this.ws
763
- const held: i32 =
764
- this.state === H1_S_WEBSOCKET && ws !== null ? ws.end - ws.start : this.parser.buffered()
765
- const room: i32 = this.inputLimit() - held
774
+ let room: i32 = 0
775
+ if (this.state === H1_S_WEBSOCKET) {
776
+ // Only a WebSocket reads the decoder and the configured message cap,
777
+ // so a request's check stays on the connection's front fields.
778
+ const ws: WsDecoder | null = this.ws
779
+ room = this.inputLimit() - (ws !== null ? ws.end - ws.start : this.parser.buffered())
780
+ } else {
781
+ room = this.bufferLimit - this.parser.buffered()
782
+ }
766
783
  return room > 0 ? room : 0
767
784
  }
768
785
 
@@ -809,11 +826,13 @@ export class Http1Connection {
809
826
  * the pool has no free buffer, or for a connection's own, once.
810
827
  */
811
828
  acquire(): void {
812
- if (toI32(this.output.length) > 0) {
829
+ if (this.outputCap > 0) {
813
830
  return
814
831
  }
815
832
  const pool: Http1OutputPool | null = this.pool
816
- this.output = pool !== null ? pool.take() : new Array<u8>(this.config.outputSize)
833
+ const output: u8[] = pool !== null ? pool.take() : new Array<u8>(this.config.outputSize)
834
+ this.output = output
835
+ this.outputCap = toI32(output.length)
817
836
  this.outputStart = 0
818
837
  this.outputEnd = 0
819
838
  }
@@ -825,16 +844,12 @@ export class Http1Connection {
825
844
  */
826
845
  release(): void {
827
846
  const pool: Http1OutputPool | null = this.pool
828
- if (
829
- pool === null ||
830
- toI32(this.output.length) === 0 ||
831
- this.outputEnd > this.outputStart ||
832
- this.writeHeld
833
- ) {
847
+ if (pool === null || this.outputCap === 0 || this.outputEnd > this.outputStart || this.writeHeld) {
834
848
  return
835
849
  }
836
850
  pool.give(this.output)
837
851
  this.output = this.noOutput
852
+ this.outputCap = 0
838
853
  this.outputStart = 0
839
854
  this.outputEnd = 0
840
855
  }