@amritk/nish-x86_64-linux 0.31.0 → 0.33.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/bin/nish CHANGED
Binary file
package/docs/AI.md CHANGED
@@ -49,7 +49,7 @@ stdout, nothing on stderr, every field 1-based with `endLine`/`endColumn`
49
49
  exclusive:
50
50
 
51
51
  ```json
52
- {"file":"p.ts","line":3,"column":17,"endLine":3,"endColumn":20,"severity":"error","code":"NL2249","message":"Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill)"}
52
+ {"file":"p.ts","line":3,"column":17,"endLine":3,"endColumn":20,"severity":"error","code":"NL2249","message":"Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill, sort)"}
53
53
  ```
54
54
 
55
55
  - **Key on `code`, never on `message`.** A code is a promise: `NL2249` means
@@ -159,7 +159,7 @@ rejects. This table is the highest-value part of the page.
159
159
  | `x == y` | `Loose equality is forbidden` | `x === y` |
160
160
  | `throw new Error(m)` | `` `throw` is forbidden `` | `return Err(m)`, or `panic(m)` to end the process |
161
161
  | `try { … } catch { … }` | `` `try`/`catch`/`finally` is forbidden `` | `Result<T, E>` and `isErr()` |
162
- | `xs.map(f)`, `.filter`, `.reduce`, `.forEach`, `.slice`, `.sort`, `.shift` | `` Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill) `` | a `for` loop, or a top-level `map(xs, f)` with a [function parameter](#function-parameters) |
162
+ | `xs.map(f)`, `.filter`, `.reduce`, `.forEach`, `.slice`, `.shift` | `` Unknown method `map` on i32[] (supported: push, pop, indexOf, join, set, fill, sort) `` | a `for` loop, or a top-level `map(xs, f)` with a [function parameter](#function-parameters) |
163
163
  | `s.toUpperCase()`, `s.split()`, `s.trim()`, `s.replace()` | `` Unknown method … on string `` | index bytes with `charCodeAt` / `substring` |
164
164
  | `a?.b`, `a ?? b` | `?.` only in a chain that ends in `??`; `??` only on a `T \| null`, a chain or `m.get(k)` | `a?.b ?? d`, `a ?? d`, or `if (a !== null)` first |
165
165
  | `x as T`, `<T>x`, `x satisfies T` | `Unsupported expression in Phase 1: AsExpression` (to `any` or `unknown`: `` Type assertion to `any` is forbidden ``); `x!` is a syntax error | there are no casts; `implements` is the only widening |
@@ -852,7 +852,8 @@ export const main = (): i32 => {
852
852
  (`` The arrow reads `k`, a `let` of the function it is written in: ... bind the value to a `const` before the call ``),
853
853
  read `this` (bind `const self = this`), or read a function parameter of its
854
854
  function. An arrow inside another arrow, or given to a method, a
855
- constructor, `expose` or `exposeWith`, captures nothing.
855
+ constructor, `expose` or `exposeWith`, captures nothing; `xs.sort(cmp)`'s
856
+ comparator is the exception, and captures as an arrow argument does.
856
857
  - **The argument's type must match exactly**: `(x: i32) => i64` for a
857
858
  `(x: i32) => i32` parameter is refused. A generic function cannot be passed
858
859
  by name — write `(x) => identity(x)`.
@@ -2138,7 +2139,12 @@ there is no `undefined` to return), `a.indexOf(v)`, `a.join(sep)` — **`join` i
2138
2139
  `string[]` only** — and, on an array of numbers only, `dst.set(src[, offset])`
2139
2140
  (one `memmove`; a range past the end panics) and `a.fill(v[, start[, end]])`
2140
2141
  (ends clamped and counted back from the end when negative, as in JavaScript;
2141
- never panics). Both are statements. That is every array method there is. A
2142
+ never panics). Both are statements. `a.sort(cmp)` sorts in place, stably, and
2143
+ answers `a` — the same order Node gives; the comparator is required (`a.sort()`
2144
+ is refused, since JavaScript would compare strings), a readonly array and
2145
+ `process.argv` are refused, `a - b` panics on `i32` values a full range apart
2146
+ (compare with `a > b ? 1 : a < b ? -1 : 0` there), and the comparator may read the caller's `const` locals and parameters but not
2147
+ a `let`, as any arrow argument may. That is every array method there is. A
2142
2148
  window into a buffer is `(buf, offset, length)`; there is no view type.
2143
2149
 
2144
2150
  **Strings.** `s.length` (**bytes**), `s.charCodeAt(i)` (the byte, bounds-checked),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@amritk/nish-x86_64-linux",
3
- "version": "0.31.0",
3
+ "version": "0.33.0",
4
4
  "description": "The nish native compiler for Linux on x86_64",
5
5
  "license": "MIT",
6
6
  "os": [
package/std/README.md CHANGED
@@ -12,6 +12,7 @@ whatever program imports it, and subject to the same rules as `examples/` or
12
12
  | [`testing.ts`](./testing.ts) | a test runner: a `Suite` a program drives with straight-line assertions, printing the `PASS` / `FAIL` / `SKIP` lines the repository's own harness prints, and answering the exit code |
13
13
  | [`text.ts`](./text.ts) | the string operations a program would otherwise write inline: `splitLines`, `splitWhitespace`, `trim` and its halves, `contains`, `replaceAll`, `firstDifference` over two arrays of lines, `codePointLength`, the number of Unicode code points (what JSON Schema's `minLength` counts, where `.length` counts UTF-8 bytes natively and UTF-16 units under Node; it answers the same on both by asking which it is reading), and `indexOfAny(text, bytes, from)`, the first index at or after `from` whose byte is one of 1 to 16 ASCII `bytes`. Its walker is the meaning and what runs under Node; natively the compiler replaces the one call to it with the runtime kernel `nish_str_index_of_any`, which reads 16 or 32 bytes at a time and is variable-time ([`docs/LANGUAGE.md`](../docs/LANGUAGE.md#byte-search-indexofany-in-nishtext)). `tests/link/std_text_index_of_any*` are its programs |
14
14
  | [`math.ts`](./math.ts) | `Math.tan`, `Math.atan2` and `Math.hypot` on `f64`, ported from fdlibm (`LICENSE-fdlibm`) so that every target, wasm included, answers alike without libm. A program never imports it: a call to one of the three loads it, and the compiler copies the functions the call reaches into the calling module as `internal` ones. JavaScript's answer for every NaN, signed zero and infinity, and within one ulp elsewhere (0.83, 0.84 and 0.93 ulp measured against mpmath). `tests/cases/math_std_edges` pins the special values |
15
+ | [`sort.ts`](./sort.ts) | `xs.sort(cmp)` on any array: `sortWith`, a stable merge sort over runs of 32 sorted by insertion — one scratch array released before it returns, a pair of runs already in order copied without merging, no bounds check left in it — so its order is element for element Node's `Array.prototype.sort`. Like `math.ts` it is one the compiler knows about: a program never imports it, a `.sort(cmp)` call loads it, the checker rewrites the call to `sortWith`, and each comparator's instantiation — the comparator called directly, no function pointer — is copied into the module that sorts. Under Node the call stays the platform's own. `tests/cases/arr_sort_cmp*` are its programs, a 10,000-element sort with ties among them |
15
16
  | [`regex.ts`](./regex.ts) | Regular expressions in JavaScript's syntax with the `u` flag's strictness, matched by a Pike VM in time linear in the input whatever the pattern; finding every match is one search per match, so `findAll` and its kin can be quadratic when a long branch is preferred to a short one that wins ([`docs/security/regex.md`](../docs/security/regex.md)). `regexCompile(pattern, flags)` answers a `Regex` or the error text; `test`, `exec` into a reused `RegexMatch`, `find`, `findAll`, `count`, `replace` and `replaceAll` with `$&`, `$1` and `$<name>`, and `split`. Flags `i`, `m`, `s` and `u`; `i` folds by Unicode's simple case folding, ASCII letters only to ASCII. `\p{…}` and `\P{…}` take Unicode 16.0's General_Category, Script, Script_Extensions and binary properties by JavaScript's names, from tables `scripts/gen-regex-unicode.mjs` generates into the module and checks against Node's `RegExp`; backreferences and lookaround are refused at compile time. Offsets are UTF-8 bytes. A search allocates nothing beyond its answer: the thread lists and capture tables are the `Regex`'s, so one `Regex` serves one thread at a time. A leading literal is found with `indexOf`, a small leading set with `indexOfAny`. `tests/link/std_regex` and `tests/link/std_regex_unicode` check it against JavaScript's own answers |
16
17
  | [`json.ts`](./json.ts) | `jsonField(object, name)`: the value of one field of one flat JSON object, which is the shape the compiler's own `--json` diagnostics have. A reader and not a parser — it answers text, answers `null` for a field that is not there, and does not validate. `JsonWriter` is the other direction: any JSON value written into a caller-owned `u8[]`, commas and colons included, byte for byte what `JSON.stringify` writes, and allocating nothing per call except an `f64`'s digits, a temporary string the call gives back before it returns (`text()` and `jsonStringifyString` build strings by design); misuse and a full buffer set a sticky negative `JSON_WRITE_*` code in `failed` rather than panicking. `setIndent(n)` and `setIndentTab()` write what `JSON.stringify(v, null, n)` or `(v, null, "\t")` writes, with no allocation for the indent, and `setStrict(true)` makes NaN and the infinities the sticky `JSON_WRITE_NON_FINITE` instead of `null`; both modes are off by default and kept by `reset`. `jsonStringifyString(s)` is the string rule answered as a string. `stringBytes(bytes, off, len)` and `keyBytes(bytes, off, len)` write a window of a byte array as `string` and `key` write a string of those bytes, allocating nothing, and refuse a window outside the array with `JSON_WRITE_BAD_WINDOW` |
17
18
  | [`json-tokenizer.ts`](./json-tokenizer.ts) | `JsonTokenizer`: a validating, streaming tokenizer for RFC 8259 JSON held in a window of a `u8[]`. `next()` answers one `JSON_TOK_*` token at a time — object and array openers and closers, keys, strings, numbers and the three literals — then `JSON_TOK_END`, or a negative `JSON_TOK_ERR_*` code that sticks in `failed`, with the byte in `errorAt`. Strings are checked as UTF-8 and a lone surrogate escape is refused; `stringInto` decodes the current string into the caller's array and `keyEquals` compares it with a name, neither allocating. `numberF64` is correctly rounded; `isInteger` and `numberI64` read an integer that fits an `i64`, and `isU64` and `numberU64` one that fits a `u64`. `isIntegral` asks whether a number's value is whole however it is written (`1.0`, `1e2`, `1e400`), JSON Schema's "integer", from the text and at any exponent. `skipValue` steps over a value. `JsonLimits` caps nesting depth, string, number and total bytes, each with its own code; `reset` reuses the tokenizer, so a reused body keeps the arena flat. Duplicate keys are reported as tokens. No input panics and no byte outside the window is read; [`docs/security/json.md`](../docs/security/json.md) is its record, and `tests/link/std_json_tokenizer` runs JSONTestSuite and a seeded fuzz |
@@ -95,6 +96,7 @@ is listed in [`SECURITY.md`](../SECURITY.md#threat-model).
95
96
  | [`crypto/jwt.ts`](./crypto/jwt.ts) | JSON Web Tokens in the JWS compact form. `JwtVerifier(maxToken)` holds its buffers, sized once, and `verify` / `verifyString(key, token, now)` answer `JWT_OK` or a `JWT_ERR_*` code: the key decides the algorithm (`alg` must name it exactly, so `none` and an HS header in front of an RSA key are refused), the header holds `alg` once and no `crit` or `b64`, base64url is strict, the signature is checked before the payload is read, and then `exp`, `nbf` and `iat` with a leeway, and `iss`, `aud` and `sub`, are checked. Keys: `jwtHmacKey` (HS256/384/512, keyed once through `HmacKey`), `jwtRsaKey` / `jwtRsaKeyParts` (RS256/384/512, 2048 bits or more), `jwtEcKey` (ES256, ES384), `jwtEd25519Key` (EdDSA), and the signing `jwtEs256SigningKey` / `jwtEd25519SigningKey`, each from a `Secret<u8[]>` it copies; `JwtKey.wipe()` zeroes the copy. `jwtSign` / `jwtSignString` / `jwtSignKid` mint a token under an HS, ES256 or EdDSA key. `peekKid` reads the unauthenticated `kid`, to choose a key | RFC 7515 Appendix A, RFC 7519, RFC 7518, RFC 8037 A.4, RFC 8725 |
96
97
  | [`crypto/ct.ts`](./crypto/ct.ts) | `timingSafeEqual(a, b)`, which reads every byte whatever it holds, and `timingSafeEqualAt(a, aOff, b, bOff, len)` over two windows, which answers `false` for a window outside its array. Two lengths that differ answer `false` at once, because a length is public, and so does an array longer than 2^31 − 1 bytes | — |
97
98
  | [`crypto/bcrypt.ts`](./crypto/bcrypt.ts) | `bcryptHash(password, salt, cost)`, the 60-character `$2b$` hash of a `Secret<u8[]>` password under a 16-byte salt and a cost of 4 to 31, and `bcryptVerify(password, hash)`, which reads `$2a$`, `$2b$` and `$2y$` hashes, refuses `$2x$`, and compares in constant time. The first 72 bytes of a password count, up to its first NUL, as under libxcrypt's `crypt_r` (where other implementations differ: [the interoperability table](../docs/security/crypto-bcrypt.md#interoperability)); the Blowfish state is wiped before either returns. `bcryptHash4(p0, p1, p2, p3, salts, cost)` and `bcryptVerify4(p0, p1, p2, p3, hashes)` do up to four at once, interleaved (four in the time of about 1.2 single hashes on one core), each lane the single path's answer; verify groups lanes by cost, and a malformed lane makes the whole call an `Err` naming it. `BCRYPT_MAX_PASSWORD`, `BCRYPT_HASH_LENGTH`, `BCRYPT_SALT_SIZE`, `BCRYPT_MIN_COST`, `BCRYPT_MAX_COST`. A `std/threads` task may call all four ([BCRYPT-9](../docs/security/crypto-bcrypt.md#findings)) | OpenBSD `bcrypt(3)`; crypt_blowfish 1.3 for `$2a$` and `$2y$` |
99
+ | [`crypto/bcrypt-batch.ts`](./crypto/bcrypt-batch.ts) | `bcryptBatcher({ width, holdNanos, cores, cost })`, a login batcher: `verify(password, hash, now)` and `hash(password, salt, now)` queue a request and answer its ticket, `waitNanos(now)` says when a run is due (a full batch, or the oldest request held `holdNanos`), and `run(now)` sends up to `width × cores` requests as `bcryptVerifyN` / `bcryptHashN` batches on one `std/threads` task per core and answers a `BcryptAnswer` (`ticket`, `ok`, `hash`, `error`) for each. It keeps and wipes its own copy of each password; a damaged stored hash answers its `Err` on its own row ([the record](../docs/security/crypto-bcrypt.md#the-login-batcher)) | — |
98
100
  | [`crypto/base64url.ts`](./crypto/base64url.ts) | `base64urlEncode(data)` and `base64urlDecode(text)`, unpadded; `base64StdPadEncode(data)` and `base64StdPadDecode(text)`, the padded standard alphabet that SCRAM and PEM use, constant-time in both directions. These are not `nish/base64`, which encodes only, is not constant-time and is for pixels; the names differ because std modules share the program's one namespace. Decoding is strict, so every byte string has one spelling. `base64urlDecode` answers `null` for any `=`, a character outside the alphabet, a length of 1 mod 4 or nonzero unused low bits; `base64StdPadDecode` requires the padding, answering `null` unless the text is whole four-character groups with `=` only as the last one or two characters, every other character in the alphabet and the unused bits zero. Both refuse a text longer than 2^31 − 1 bytes, and `base64urlEncode` panics on such an array | RFC 4648 §4, §5, §10 (`tests/link/std_base64_padded`) |
99
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 |
100
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` |
@@ -0,0 +1,702 @@
1
+ /**
2
+ * `nish/crypto/bcrypt-batch` — a login batcher: it queues bcrypt verifies and
3
+ * hashes as they arrive, sends them as `bcryptVerifyN` / `bcryptHashN`
4
+ * batches, and runs the batches on `std/threads` tasks, one per core.
5
+ *
6
+ * import { bcryptCalibrate } from "nish/crypto/bcrypt"
7
+ * import { BcryptAnswer, BcryptBatcher, bcryptBatcher } from "nish/crypto/bcrypt-batch"
8
+ * import { availableParallelism, monotonicNanos } from "nish:process"
9
+ *
10
+ * const noHold: i64 = 0
11
+ * const made = bcryptBatcher({ width: bcryptCalibrate(), holdNanos: noHold, cores: availableParallelism(), cost: 10 })
12
+ * const batcher: BcryptBatcher = made.value
13
+ * // In the server's loop, for each login that arrives:
14
+ * const ticket: i32 = batcher.verify(password, storedHash, monotonicNanos())
15
+ * // ... wait at most batcher.waitNanos(now) for the next readiness event, then:
16
+ * for (const a of batcher.run(monotonicNanos())) { reply(a.ticket, a.ok) }
17
+ *
18
+ * **Why.** How full a batch is moves the time per password by more than any
19
+ * instruction-set work does: on one core a full six-lane cost-10 batch takes
20
+ * 15.7 ms per hash and a lone hash 56.9 ms (docs/security/crypto-bcrypt.md,
21
+ * "Speed"). A server whose logins arrive one at a time never fills a batch by
22
+ * itself; this module holds them until there are enough, or until a short
23
+ * window has passed.
24
+ *
25
+ * **When a batch is sent.** `run(now)` sends work when the queue holds at
26
+ * least `width` requests, or when the oldest has waited `holdNanos`; a hold
27
+ * of 0 sends whatever is queued at every `run`. It takes at most
28
+ * `width × cores` requests a run, the oldest first, and spreads them evenly
29
+ * over `min(cores, taken)` tasks, each running its share as one batch, so a
30
+ * run takes about one batch's time and the event loop gets control back
31
+ * between runs. Spreading rather than filling is deliberate: while a run is
32
+ * going nothing else can use the free cores, and a batch of two on each of
33
+ * three cores ends sooner than one of six on one. Under load the requests
34
+ * that arrive during a run fill the next one, so the batches fill on their
35
+ * own; at light load each request runs alone on a core, as a task per login
36
+ * would. `waitNanos(now)` says how long the loop may sleep before a `run` is
37
+ * due.
38
+ *
39
+ * **`run` blocks.** It opens a scope, runs the shares, one on the calling
40
+ * thread, and returns once every share has finished: about one batch, 60–95
41
+ * ms of cost 10 on the core this was measured on. That is the pause the
42
+ * password-hashing guide describes for a scope on the event loop's thread;
43
+ * give the batcher a thread of its own to avoid it.
44
+ *
45
+ * **The batcher keeps its own copy of each password.** `verify` and `hash`
46
+ * borrow the caller's `Secret<u8[]>`, as any function taking one does: they
47
+ * copy it, inside `exposeWith`, into a holder of the batcher's own (a class
48
+ * with one `readonly` `Secret` field), and the caller wipes its password as
49
+ * it would after `bcryptVerify`. `run` calls each copy's `wipe()` on the
50
+ * calling thread once the scope that read it has joined, so no task can
51
+ * still be reading it. A request that never runs (a batcher dropped with
52
+ * requests queued) is not wiped: call `run` until `pending()` is 0 before
53
+ * dropping one. The tasks call only `bcryptVerifyN`, `bcryptHashN` and
54
+ * `bcryptVerify`, which wipe everything they make
55
+ * (docs/security/crypto-bcrypt.md, BCRYPT-9, BCRYPT-11).
56
+ *
57
+ * **Every request gets its own answer.** A verify against a hash string that
58
+ * is not one makes `bcryptVerifyN` an `Err` for the whole batch; the share
59
+ * then verifies each of that batch's lanes alone, so only the damaged row
60
+ * answers the `Err` and the rest answer as they would have. A `hash` whose
61
+ * salt is not 16 bytes is answered with its `Err` at the next `run`, without
62
+ * being batched. The answers of a run come back in the order their requests
63
+ * were taken; match them to requests by `ticket`.
64
+ *
65
+ * Names carry the `bcryptBatch` prefix because a `std/` module's private
66
+ * functions share the importing program's flat symbol namespace
67
+ * (`docs/wp26-stdlib.md` §3e).
68
+ */
69
+
70
+ import {
71
+ BCRYPT_MAX_COST,
72
+ BCRYPT_MIN_COST,
73
+ bcryptHashN,
74
+ bcryptVerify,
75
+ bcryptVerifyN,
76
+ } from "nish/crypto/bcrypt"
77
+ import { Secret, expose, exposeWith, secret, wipe } from "nish:secret"
78
+ import { scope } from "nish/threads"
79
+
80
+ /** A request's kind: a verify of a password against a stored hash. */
81
+ const BCRYPT_BATCH_VERIFY: i32 = 0
82
+
83
+ /** A request's kind: a new hash of a password under a salt. */
84
+ const BCRYPT_BATCH_HASH: i32 = 1
85
+
86
+ /** The most lanes `bcryptVerifyN` and `bcryptHashN` take. */
87
+ const BCRYPT_BATCH_MAX_WIDTH: i32 = 6
88
+
89
+ /** How a batcher sends its work: see `bcryptBatcher`. */
90
+ export interface BcryptBatchConfig {
91
+ /** Lanes per batch, 1 to 6: `bcryptCalibrate()`'s answer. */
92
+ width: i32
93
+ /** How long the oldest request may wait for a batch to fill, in nanoseconds; 0 never waits. */
94
+ holdNanos: i64
95
+ /** How many tasks a run may use, at least 1: `availableParallelism()`. */
96
+ cores: i32
97
+ /** The cost `hash` requests are made at, 4 to 31. Verifies take theirs from the hash. */
98
+ cost: i32
99
+ }
100
+
101
+ /** What a request answered, under the ticket `verify` or `hash` gave it. */
102
+ export class BcryptAnswer {
103
+ /** The ticket of the request. */
104
+ ticket: i32
105
+ /** A verify: whether the password is the hash's. A hash: whether it was made. False with an error. */
106
+ ok: boolean
107
+ /** A hash request's `$2b$` hash; "" for a verify and for an error. */
108
+ hash: string
109
+ /** "" unless the request is an `Err`, as `bcryptVerify` or `bcryptHash` words it. */
110
+ error: string
111
+
112
+ constructor(ticket: i32, ok: boolean, hash: string, error: string) {
113
+ this.ticket = ticket
114
+ this.ok = ok
115
+ this.hash = hash
116
+ this.error = error
117
+ }
118
+ }
119
+
120
+ /**
121
+ * The batcher's copy of one queued password: a holder, so it may sit in the
122
+ * queue's array, made at the password's length and filled by `bcryptBatchTake`.
123
+ */
124
+ class BcryptBatchKey {
125
+ readonly key: Secret<u8[]>
126
+
127
+ constructor(length: i32) {
128
+ this.key = secret(new Array<u8>(length))
129
+ }
130
+
131
+ wipe(): void {
132
+ wipe(this.key)
133
+ }
134
+ }
135
+
136
+ /** A password's length, the one fact about it the batcher reads outside a batch. */
137
+ const bcryptBatchLengthOf = (bytes: u8[]): i32 => toI32(bytes.length)
138
+
139
+ /** Copies `from` into the exposed key `into`, which has its length. */
140
+ const bcryptBatchCopy = (into: u8[], from: u8[]): i32 => {
141
+ for (let i: i32 = 0; i < toI32(into.length) && i < toI32(from.length); i++) {
142
+ into[i] = from[i]
143
+ }
144
+ return 0
145
+ }
146
+
147
+ /** Copies the exposed password `bytes` into `held`'s key. */
148
+ const bcryptBatchFill = (bytes: u8[], held: BcryptBatchKey): i32 =>
149
+ exposeWith(held.key, bytes, bcryptBatchCopy)
150
+
151
+ /** A new holder with a copy of `password`; the caller keeps, and wipes, its own. */
152
+ const bcryptBatchTake = (password: Secret<u8[]>): BcryptBatchKey => {
153
+ const held: BcryptBatchKey = new BcryptBatchKey(expose(password, bcryptBatchLengthOf))
154
+ exposeWith(password, held, bcryptBatchFill)
155
+ return held
156
+ }
157
+
158
+ /**
159
+ * One task's work: requests in the order the share takes them. The task
160
+ * reads it and writes none of it; its answer is one string a request, "1"
161
+ * or "0" for a verify, the hash for a hash, and "!" and the error for an
162
+ * `Err`.
163
+ */
164
+ class BcryptBatchShare {
165
+ passwords: BcryptBatchKey[]
166
+ kinds: i32[]
167
+ /** A verify's stored hash, "" for a hash. */
168
+ hashes: string[]
169
+ /** 16 bytes a request; a verify's are zeros and are not read. */
170
+ salts: u8[]
171
+ cost: i32
172
+ width: i32
173
+
174
+ constructor(cost: i32, width: i32) {
175
+ this.passwords = []
176
+ this.kinds = []
177
+ this.hashes = []
178
+ this.salts = []
179
+ this.cost = cost
180
+ this.width = width
181
+ }
182
+ }
183
+
184
+ /** The holder at `list[i]` of `share`, or at `list[0]` past the batch's end (that lane is then passed as null). */
185
+ const bcryptBatchLane = (share: BcryptBatchShare, list: i32[], i: i32): BcryptBatchKey => {
186
+ const at: i32 = i < toI32(list.length) ? list[i] : list[0]
187
+ return share.passwords[at]
188
+ }
189
+
190
+ /** The answer string of one verify alone, for a lane of a batch that answered an `Err`. */
191
+ const bcryptBatchVerifyAlone = (held: BcryptBatchKey, hash: string): string => {
192
+ const said: Result<boolean, string> = bcryptVerify(held.key, hash)
193
+ if (said.isErr()) {
194
+ return `!${said.error}`
195
+ }
196
+ return said.value ? "1" : "0"
197
+ }
198
+
199
+ /**
200
+ * One `bcryptVerifyN` over the first `n` of `list`: a holder's field is
201
+ * passed only as a call's argument, so each lane count is its own call.
202
+ */
203
+ const bcryptBatchVerifyLanes = (
204
+ share: BcryptBatchShare,
205
+ list: i32[],
206
+ n: i32,
207
+ hashes: string[]
208
+ ): Result<boolean[], string> => {
209
+ const a: BcryptBatchKey = bcryptBatchLane(share, list, 0)
210
+ const b: BcryptBatchKey = bcryptBatchLane(share, list, 1)
211
+ const c: BcryptBatchKey = bcryptBatchLane(share, list, 2)
212
+ const d: BcryptBatchKey = bcryptBatchLane(share, list, 3)
213
+ const e: BcryptBatchKey = bcryptBatchLane(share, list, 4)
214
+ const f: BcryptBatchKey = bcryptBatchLane(share, list, 5)
215
+ if (n <= 1) {
216
+ return bcryptVerifyN(a.key, null, null, null, null, null, hashes)
217
+ }
218
+ if (n === 2) {
219
+ return bcryptVerifyN(a.key, b.key, null, null, null, null, hashes)
220
+ }
221
+ if (n === 3) {
222
+ return bcryptVerifyN(a.key, b.key, c.key, null, null, null, hashes)
223
+ }
224
+ if (n === 4) {
225
+ return bcryptVerifyN(a.key, b.key, c.key, d.key, null, null, hashes)
226
+ }
227
+ if (n === 5) {
228
+ return bcryptVerifyN(a.key, b.key, c.key, d.key, e.key, null, hashes)
229
+ }
230
+ return bcryptVerifyN(a.key, b.key, c.key, d.key, e.key, f.key, hashes)
231
+ }
232
+
233
+ /** One `bcryptHashN` over the first `n` of `list`, as `bcryptBatchVerifyLanes` is for verifies. */
234
+ const bcryptBatchHashLanes = (
235
+ share: BcryptBatchShare,
236
+ list: i32[],
237
+ n: i32,
238
+ salts: u8[]
239
+ ): Result<string[], string> => {
240
+ const a: BcryptBatchKey = bcryptBatchLane(share, list, 0)
241
+ const b: BcryptBatchKey = bcryptBatchLane(share, list, 1)
242
+ const c: BcryptBatchKey = bcryptBatchLane(share, list, 2)
243
+ const d: BcryptBatchKey = bcryptBatchLane(share, list, 3)
244
+ const e: BcryptBatchKey = bcryptBatchLane(share, list, 4)
245
+ const f: BcryptBatchKey = bcryptBatchLane(share, list, 5)
246
+ if (n <= 1) {
247
+ return bcryptHashN(a.key, null, null, null, null, null, salts, share.cost)
248
+ }
249
+ if (n === 2) {
250
+ return bcryptHashN(a.key, b.key, null, null, null, null, salts, share.cost)
251
+ }
252
+ if (n === 3) {
253
+ return bcryptHashN(a.key, b.key, c.key, null, null, null, salts, share.cost)
254
+ }
255
+ if (n === 4) {
256
+ return bcryptHashN(a.key, b.key, c.key, d.key, null, null, salts, share.cost)
257
+ }
258
+ if (n === 5) {
259
+ return bcryptHashN(a.key, b.key, c.key, d.key, e.key, null, salts, share.cost)
260
+ }
261
+ return bcryptHashN(a.key, b.key, c.key, d.key, e.key, f.key, salts, share.cost)
262
+ }
263
+
264
+ /** The verifies of `share` at `list[from..from + n)` as one `bcryptVerifyN`, each lane's answer into `out` at its index. */
265
+ const bcryptBatchVerifyRun = (
266
+ share: BcryptBatchShare,
267
+ list: i32[],
268
+ from: i32,
269
+ n: i32,
270
+ out: string[]
271
+ ): void => {
272
+ const lanes: i32[] = []
273
+ for (let i: i32 = from; i < from + n && i < toI32(list.length); i++) {
274
+ if (i >= 0) {
275
+ lanes.push(list[i])
276
+ }
277
+ }
278
+ if (toI32(lanes.length) === 0) {
279
+ return
280
+ }
281
+ const hashes: string[] = ["", "", "", "", "", ""]
282
+ for (let s: i32 = 0; s < toI32(lanes.length) && s < 6; s++) {
283
+ const at: i32 = lanes[s]
284
+ if (at >= 0 && at < toI32(share.hashes.length)) {
285
+ hashes[s] = share.hashes[at]
286
+ }
287
+ }
288
+ const said: Result<boolean[], string> = bcryptBatchVerifyLanes(share, lanes, toI32(lanes.length), hashes)
289
+ for (let s: i32 = 0; s < toI32(lanes.length); s++) {
290
+ const at: i32 = lanes[s]
291
+ let line: string = "0"
292
+ if (said.isErr()) {
293
+ // One damaged hash fails the whole call: answer each lane alone.
294
+ if (at >= 0 && at < toI32(share.passwords.length) && s < toI32(hashes.length)) {
295
+ line = bcryptBatchVerifyAlone(share.passwords[at], hashes[s])
296
+ }
297
+ } else {
298
+ const ok: boolean[] = said.value
299
+ line = s < toI32(ok.length) && ok[s] ? "1" : "0"
300
+ }
301
+ if (at >= 0 && at < toI32(out.length)) {
302
+ out[at] = line
303
+ }
304
+ }
305
+ }
306
+
307
+ /** The hashes of `share` at `list[from..from + n)` as one `bcryptHashN`, each lane's answer into `out` at its index. */
308
+ const bcryptBatchHashRun = (share: BcryptBatchShare, list: i32[], from: i32, n: i32, out: string[]): void => {
309
+ const lanes: i32[] = []
310
+ for (let i: i32 = from; i < from + n && i < toI32(list.length); i++) {
311
+ if (i >= 0) {
312
+ lanes.push(list[i])
313
+ }
314
+ }
315
+ if (toI32(lanes.length) === 0) {
316
+ return
317
+ }
318
+ const salts: u8[] = new Array<u8>(96)
319
+ for (let s: i32 = 0; s < toI32(lanes.length) && s < 6; s++) {
320
+ const at: i32 = lanes[s]
321
+ for (let j: i32 = 0; j < 16 && 16 * at + j < toI32(share.salts.length); j++) {
322
+ if (at >= 0) {
323
+ salts[16 * s + j] = share.salts[16 * at + j]
324
+ }
325
+ }
326
+ }
327
+ const made: Result<string[], string> = bcryptBatchHashLanes(share, lanes, toI32(lanes.length), salts)
328
+ for (let s: i32 = 0; s < toI32(lanes.length); s++) {
329
+ const at: i32 = lanes[s]
330
+ let line: string = ""
331
+ if (made.isErr()) {
332
+ line = `!${made.error}`
333
+ } else {
334
+ const all: string[] = made.value
335
+ line = s < toI32(all.length) ? all[s] : ""
336
+ }
337
+ if (at >= 0 && at < toI32(out.length)) {
338
+ out[at] = line
339
+ }
340
+ }
341
+ }
342
+
343
+ /** The indices of `share`'s requests of `kind`, in order. */
344
+ const bcryptBatchOfKind = (share: BcryptBatchShare, kind: i32): i32[] => {
345
+ const list: i32[] = []
346
+ for (let i: i32 = 0; i < toI32(share.kinds.length); i++) {
347
+ if (share.kinds[i] === kind) {
348
+ list.push(i)
349
+ }
350
+ }
351
+ return list
352
+ }
353
+
354
+ /**
355
+ * One task: every request of `share`, the verifies and then the hashes, in
356
+ * batches of up to `width`. It reads `share` and writes only arrays it made,
357
+ * and answers one line a request, joined by "\n": a task hands back a string,
358
+ * not an array of them. No answer holds a newline: a `$2b$` hash is base-64
359
+ * and bcrypt's errors are one line.
360
+ */
361
+ const bcryptBatchShareRun = (share: BcryptBatchShare): string => {
362
+ const out: string[] = []
363
+ for (let i: i32 = 0; i < toI32(share.kinds.length); i++) {
364
+ out.push("")
365
+ }
366
+ const width: i32 = share.width < 1 ? 1 : share.width
367
+ const verifies: i32[] = bcryptBatchOfKind(share, BCRYPT_BATCH_VERIFY)
368
+ for (let i: i32 = 0; i < toI32(verifies.length); i += width) {
369
+ bcryptBatchVerifyRun(share, verifies, i, width, out)
370
+ }
371
+ const hashes: i32[] = bcryptBatchOfKind(share, BCRYPT_BATCH_HASH)
372
+ for (let i: i32 = 0; i < toI32(hashes.length); i += width) {
373
+ bcryptBatchHashRun(share, hashes, i, width, out)
374
+ }
375
+ return out.join("\n")
376
+ }
377
+
378
+ /**
379
+ * The cost a verify's stored hash names, read from its fourth and fifth
380
+ * characters, or -1 when they are not two digits; only the order of a run
381
+ * reads it, so that one cost's verifies share a batch.
382
+ */
383
+ const bcryptBatchCostOf = (hash: string): i32 => {
384
+ if (toI32(hash.length) < 7) {
385
+ return -1
386
+ }
387
+ const tens: i32 = toI32(hash.charCodeAt(4)) - 48
388
+ const ones: i32 = toI32(hash.charCodeAt(5)) - 48
389
+ if (tens < 0 || tens > 9 || ones < 0 || ones > 9) {
390
+ return -1
391
+ }
392
+ return tens * 10 + ones
393
+ }
394
+
395
+ /** The lines of a task's answer, split at "\n". */
396
+ const bcryptBatchLines = (joined: string): string[] => {
397
+ const lines: string[] = []
398
+ let from: i32 = 0
399
+ while (from >= 0 && from <= toI32(joined.length)) {
400
+ const at: i32 = toI32(joined.indexOf("\n", from))
401
+ if (at < from || at > toI32(joined.length)) {
402
+ lines.push(joined.slice(from))
403
+ return lines
404
+ }
405
+ lines.push(joined.slice(from, at))
406
+ from = at + 1
407
+ }
408
+ return lines
409
+ }
410
+
411
+ /** The answer a share's string stands for. */
412
+ const bcryptBatchAnswerOf = (ticket: i32, kind: i32, said: string): BcryptAnswer => {
413
+ if (said.startsWith("!")) {
414
+ return new BcryptAnswer(ticket, false, "", said.substring(1))
415
+ }
416
+ if (kind === BCRYPT_BATCH_HASH) {
417
+ return new BcryptAnswer(ticket, said !== "", said, "")
418
+ }
419
+ return new BcryptAnswer(ticket, said === "1", "", "")
420
+ }
421
+
422
+ /**
423
+ * A queue of bcrypt requests and the policy that sends them: made by
424
+ * `bcryptBatcher`. `verify` and `hash` queue, `run` sends and answers,
425
+ * `waitNanos` says when the next `run` is due.
426
+ */
427
+ export class BcryptBatcher {
428
+ holdNanos: i64
429
+ /** The queue, from `head`: each request's copy of its password, kind, stored hash, salt (16 bytes each), ticket and arrival. */
430
+ passwords: BcryptBatchKey[]
431
+ kinds: i32[]
432
+ hashes: string[]
433
+ salts: u8[]
434
+ tickets: i32[]
435
+ arrived: i64[]
436
+ /** Answers made without a batch (a salt of the wrong length), handed out by the next `run`. */
437
+ early: BcryptAnswer[]
438
+ width: i32
439
+ cores: i32
440
+ cost: i32
441
+ head: i32
442
+ /** The next ticket. */
443
+ next: i32
444
+
445
+ constructor(config: BcryptBatchConfig) {
446
+ this.width = config.width
447
+ this.holdNanos = config.holdNanos
448
+ this.cores = config.cores
449
+ this.cost = config.cost
450
+ this.passwords = []
451
+ this.kinds = []
452
+ this.hashes = []
453
+ this.salts = []
454
+ this.tickets = []
455
+ this.arrived = []
456
+ this.head = 0
457
+ this.early = []
458
+ this.next = 0
459
+ }
460
+
461
+ /** How many requests are queued or answered and not yet handed out by `run`. */
462
+ pending(): i32 {
463
+ return toI32(this.kinds.length) - this.head + toI32(this.early.length)
464
+ }
465
+
466
+ /**
467
+ * Queues a verify of `password` against `hash`, which arrived at `now`
468
+ * (`monotonicNanos()`), and answers its ticket. The batcher queues a copy
469
+ * of `password` and wipes the copy once the verify has run; the caller
470
+ * wipes its own.
471
+ */
472
+ verify(password: Secret<u8[]>, hash: string, now: i64): i32 {
473
+ const ticket: i32 = this.next
474
+ this.next = this.next + 1
475
+ this.enqueue(password, BCRYPT_BATCH_VERIFY, hash, new Array<u8>(16), ticket, now)
476
+ return ticket
477
+ }
478
+
479
+ /**
480
+ * Queues a new hash of `password` under `salt` (16 bytes, fresh from
481
+ * `crypto.getRandomValues`) at the batcher's cost, which arrived at `now`,
482
+ * and answers its ticket. The batcher queues a copy of `password` and
483
+ * wipes the copy once the hash is made; a salt of another length is
484
+ * answered with its `Err` by the next `run`, and no copy is made.
485
+ */
486
+ hash(password: Secret<u8[]>, salt: u8[], now: i64): i32 {
487
+ const ticket: i32 = this.next
488
+ this.next = this.next + 1
489
+ if (toI32(salt.length) !== 16) {
490
+ this.early.push(new BcryptAnswer(ticket, false, "", "bcrypt: the salt is not 16 bytes"))
491
+ return ticket
492
+ }
493
+ this.enqueue(password, BCRYPT_BATCH_HASH, "", salt, ticket, now)
494
+ return ticket
495
+ }
496
+
497
+ /** Appends one request to the queue. */
498
+ enqueue(password: Secret<u8[]>, kind: i32, hash: string, salt: u8[], ticket: i32, now: i64): void {
499
+ this.passwords.push(bcryptBatchTake(password))
500
+ this.kinds.push(kind)
501
+ this.hashes.push(hash)
502
+ for (let j: i32 = 0; j < 16 && j < toI32(salt.length); j++) {
503
+ this.salts.push(salt[j])
504
+ }
505
+ this.tickets.push(ticket)
506
+ this.arrived.push(now)
507
+ }
508
+
509
+ /**
510
+ * Nanoseconds from `now` until a `run` is due: 0 when it is due now (a full
511
+ * batch is queued, the oldest request has waited `holdNanos`, or an answer
512
+ * is waiting), -1 when nothing is queued.
513
+ */
514
+ waitNanos(now: i64): i64 {
515
+ if (toI32(this.early.length) > 0) {
516
+ return 0
517
+ }
518
+ const queued: i32 = toI32(this.kinds.length) - this.head
519
+ if (queued <= 0) {
520
+ return -1
521
+ }
522
+ if (queued >= this.width || this.head >= toI32(this.arrived.length)) {
523
+ return 0
524
+ }
525
+ const waited: i64 = now - this.arrived[this.head]
526
+ return waited >= this.holdNanos ? 0 : this.holdNanos - waited
527
+ }
528
+
529
+ /**
530
+ * Sends what is due and answers it: nothing (bar answers made without a
531
+ * batch) until `waitNanos(now)` is 0, then up to `width × cores` of the
532
+ * oldest requests, spread evenly over up to `cores` tasks, each running
533
+ * its share as batches of up to `width`. Returns once every task has
534
+ * finished, with the copy of each password it ran wiped.
535
+ */
536
+ run(now: i64): BcryptAnswer[] {
537
+ const answers: BcryptAnswer[] = []
538
+ for (const a of this.early) {
539
+ answers.push(a)
540
+ }
541
+ this.early = []
542
+ if (this.waitNanos(now) !== 0) {
543
+ return answers
544
+ }
545
+ const queued: i32 = toI32(this.kinds.length) - this.head
546
+ if (queued <= 0) {
547
+ return answers
548
+ }
549
+ const most: i32 = this.width * this.cores
550
+ const taken: i32 = queued < most ? queued : most
551
+ const order: i32[] = this.orderOf(taken)
552
+ const tasks: i32 = taken < this.cores ? taken : this.cores
553
+ const shares: BcryptBatchShare[] = []
554
+ // Share k takes order[taken·k/tasks, taken·(k+1)/tasks): contiguous, so one cost's verifies stay together.
555
+ const owner: i32[] = []
556
+ for (let k: i32 = 0; k < tasks; k++) {
557
+ const share: BcryptBatchShare = new BcryptBatchShare(this.cost, this.width)
558
+ const lo: i32 = (taken * k) / tasks
559
+ const hi: i32 = (taken * (k + 1)) / tasks
560
+ for (let i: i32 = lo; i >= 0 && i < hi && i < toI32(order.length); i++) {
561
+ const at: i32 = order[i]
562
+ if (at >= 0 && at < toI32(this.passwords.length) && at < toI32(this.kinds.length)) {
563
+ share.passwords.push(this.passwords[at])
564
+ share.kinds.push(this.kinds[at])
565
+ share.hashes.push(at < toI32(this.hashes.length) ? this.hashes[at] : "")
566
+ for (let j: i32 = 0; j < 16; j++) {
567
+ share.salts.push(16 * at + j < toI32(this.salts.length) ? this.salts[16 * at + j] : toU8(0))
568
+ }
569
+ owner.push(at)
570
+ }
571
+ }
572
+ shares.push(share)
573
+ }
574
+ const said: string[] = []
575
+ for (let k: i32 = 0; k < tasks; k++) {
576
+ said.push("")
577
+ }
578
+ {
579
+ using s = scope()
580
+ for (let k: i32 = 0; k < toI32(shares.length); k++) {
581
+ s.spawn(bcryptBatchShareRun, shares[k], said, k)
582
+ }
583
+ }
584
+ let flat: i32 = 0
585
+ for (let k: i32 = 0; k < toI32(said.length); k++) {
586
+ const one: string[] = bcryptBatchLines(said[k])
587
+ for (let i: i32 = 0; i < toI32(one.length) && flat < toI32(owner.length); i++) {
588
+ const at: i32 = owner[flat]
589
+ flat++
590
+ if (at >= 0 && at < toI32(this.tickets.length) && at < toI32(this.kinds.length)) {
591
+ answers.push(bcryptBatchAnswerOf(this.tickets[at], this.kinds[at], one[i]))
592
+ }
593
+ }
594
+ }
595
+ for (const at of order) {
596
+ if (at >= 0 && at < toI32(this.passwords.length)) {
597
+ const held: BcryptBatchKey = this.passwords[at]
598
+ held.wipe()
599
+ }
600
+ }
601
+ this.head = this.head + taken
602
+ this.compact()
603
+ return answers
604
+ }
605
+
606
+ /**
607
+ * The queue indices of the `taken` oldest requests, verifies by the cost
608
+ * their hash names and then hashes, each group in arrival order.
609
+ */
610
+ orderOf(taken: i32): i32[] {
611
+ const keyed: i32[] = []
612
+ const keys: i32[] = []
613
+ for (let i: i32 = this.head; i < this.head + taken && i < toI32(this.kinds.length); i++) {
614
+ keyed.push(i)
615
+ let key: i32 = 1000
616
+ if (this.kinds[i] !== BCRYPT_BATCH_HASH) {
617
+ key = i < toI32(this.hashes.length) ? bcryptBatchCostOf(this.hashes[i]) : -1
618
+ }
619
+ keys.push(key)
620
+ }
621
+ // Stable: one pass a distinct key, lowest first, each in arrival order.
622
+ // A run takes at most 36 requests, of a few costs.
623
+ const sorted: i32[] = []
624
+ let last: i32 = -2
625
+ while (toI32(sorted.length) < toI32(keyed.length)) {
626
+ let low: i32 = 1001
627
+ for (const key of keys) {
628
+ if (key > last && key < low) {
629
+ low = key
630
+ }
631
+ }
632
+ if (low > 1000) {
633
+ return sorted
634
+ }
635
+ for (let i: i32 = 0; i < toI32(keyed.length) && i < toI32(keys.length); i++) {
636
+ if (keys[i] === low) {
637
+ sorted.push(keyed[i])
638
+ }
639
+ }
640
+ last = low
641
+ }
642
+ return sorted
643
+ }
644
+
645
+ /** Drops the requests before `head` once they are most of the queue, so a long-lived batcher's arrays stay small. */
646
+ compact(): void {
647
+ if (this.head < 64 || this.head * 2 < toI32(this.kinds.length)) {
648
+ return
649
+ }
650
+ const passwords: BcryptBatchKey[] = []
651
+ const kinds: i32[] = []
652
+ const hashes: string[] = []
653
+ const salts: u8[] = []
654
+ const tickets: i32[] = []
655
+ const arrived: i64[] = []
656
+ for (let i: i32 = this.head; i < toI32(this.kinds.length); i++) {
657
+ if (
658
+ i < toI32(this.passwords.length) &&
659
+ i < toI32(this.hashes.length) &&
660
+ i < toI32(this.tickets.length) &&
661
+ i < toI32(this.arrived.length)
662
+ ) {
663
+ passwords.push(this.passwords[i])
664
+ kinds.push(this.kinds[i])
665
+ hashes.push(this.hashes[i])
666
+ for (let j: i32 = 0; j < 16; j++) {
667
+ salts.push(16 * i + j < toI32(this.salts.length) ? this.salts[16 * i + j] : toU8(0))
668
+ }
669
+ tickets.push(this.tickets[i])
670
+ arrived.push(this.arrived[i])
671
+ }
672
+ }
673
+ this.passwords = passwords
674
+ this.kinds = kinds
675
+ this.hashes = hashes
676
+ this.salts = salts
677
+ this.tickets = tickets
678
+ this.arrived = arrived
679
+ this.head = 0
680
+ }
681
+ }
682
+
683
+ /**
684
+ * A new batcher for `config`, or an `Err` naming the field out of range: a
685
+ * width outside 1 to 6, a negative hold, fewer than one core, or a cost
686
+ * outside 4 to 31.
687
+ */
688
+ export const bcryptBatcher = (config: BcryptBatchConfig): Result<BcryptBatcher, string> => {
689
+ if (config.width < 1 || config.width > BCRYPT_BATCH_MAX_WIDTH) {
690
+ return Err("bcrypt: the batch width is outside 1 to 6")
691
+ }
692
+ if (config.holdNanos < 0) {
693
+ return Err("bcrypt: the hold is negative")
694
+ }
695
+ if (config.cores < 1) {
696
+ return Err("bcrypt: a batcher needs at least one core")
697
+ }
698
+ if (config.cost < BCRYPT_MIN_COST || config.cost > BCRYPT_MAX_COST) {
699
+ return Err("bcrypt: the cost is outside 4 to 31")
700
+ }
701
+ return Ok(new BcryptBatcher(config))
702
+ }
@@ -1024,7 +1024,9 @@ const bcryptLoadSlot = (state: Secret<u32[]>, password: Secret<u8[]>, slot: i32)
1024
1024
  // the lane count changed (bench/crypto/bcrypt/gen-body.mjs writes them), not
1025
1025
  // one loop over a lane count: the lanes' l and r must live in registers and
1026
1026
  // their loads be scheduled together, which a loop over an array of lanes
1027
- // does not give.
1027
+ // does not give. Two lanes take each byte by its own shift and write every
1028
+ // lane's bytes before any lane's update, the form all widths had before the
1029
+ // rotated copy, which measured slower there.
1028
1030
 
1029
1031
  /** `bcryptBody4`'s interleave on two lanes. */
1030
1032
  const bcryptBody2 = (st: u32[]): void => {
@@ -1089,35 +1091,41 @@ const bcryptBody3 = (st: u32[]): void => {
1089
1091
  l1 ^= st[1042]
1090
1092
  l2 ^= st[2084]
1091
1093
  for (let n: i32 = 1; n < 17; n += 2) {
1094
+ const l0e: u32 = ctSelect((l0 >>> 8) | (l0 << 24), 0xffffffff, 0)
1092
1095
  const l0a: i32 = toI32(l0 >>> 24) & 0xff
1093
- const l0b: i32 = toI32(l0 >>> 16) & 0xff
1094
- const l0c: i32 = toI32(l0 >>> 8) & 0xff
1096
+ const l0b: i32 = toI32(l0e >>> 8) & 0xff
1097
+ const l0c: i32 = toI32(l0e) & 0xff
1095
1098
  const l0d: i32 = toI32(l0) & 0xff
1099
+ r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
1100
+ const l1e: u32 = ctSelect((l1 >>> 8) | (l1 << 24), 0xffffffff, 0)
1096
1101
  const l1a: i32 = toI32(l1 >>> 24) & 0xff
1097
- const l1b: i32 = toI32(l1 >>> 16) & 0xff
1098
- const l1c: i32 = toI32(l1 >>> 8) & 0xff
1102
+ const l1b: i32 = toI32(l1e >>> 8) & 0xff
1103
+ const l1c: i32 = toI32(l1e) & 0xff
1099
1104
  const l1d: i32 = toI32(l1) & 0xff
1105
+ r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
1106
+ const l2e: u32 = ctSelect((l2 >>> 8) | (l2 << 24), 0xffffffff, 0)
1100
1107
  const l2a: i32 = toI32(l2 >>> 24) & 0xff
1101
- const l2b: i32 = toI32(l2 >>> 16) & 0xff
1102
- const l2c: i32 = toI32(l2 >>> 8) & 0xff
1108
+ const l2b: i32 = toI32(l2e >>> 8) & 0xff
1109
+ const l2c: i32 = toI32(l2e) & 0xff
1103
1110
  const l2d: i32 = toI32(l2) & 0xff
1104
- r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
1105
- r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
1106
1111
  r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
1112
+ const r0e: u32 = ctSelect((r0 >>> 8) | (r0 << 24), 0xffffffff, 0)
1107
1113
  const r0a: i32 = toI32(r0 >>> 24) & 0xff
1108
- const r0b: i32 = toI32(r0 >>> 16) & 0xff
1109
- const r0c: i32 = toI32(r0 >>> 8) & 0xff
1114
+ const r0b: i32 = toI32(r0e >>> 8) & 0xff
1115
+ const r0c: i32 = toI32(r0e) & 0xff
1110
1116
  const r0d: i32 = toI32(r0) & 0xff
1117
+ l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
1118
+ const r1e: u32 = ctSelect((r1 >>> 8) | (r1 << 24), 0xffffffff, 0)
1111
1119
  const r1a: i32 = toI32(r1 >>> 24) & 0xff
1112
- const r1b: i32 = toI32(r1 >>> 16) & 0xff
1113
- const r1c: i32 = toI32(r1 >>> 8) & 0xff
1120
+ const r1b: i32 = toI32(r1e >>> 8) & 0xff
1121
+ const r1c: i32 = toI32(r1e) & 0xff
1114
1122
  const r1d: i32 = toI32(r1) & 0xff
1123
+ l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
1124
+ const r2e: u32 = ctSelect((r2 >>> 8) | (r2 << 24), 0xffffffff, 0)
1115
1125
  const r2a: i32 = toI32(r2 >>> 24) & 0xff
1116
- const r2b: i32 = toI32(r2 >>> 16) & 0xff
1117
- const r2c: i32 = toI32(r2 >>> 8) & 0xff
1126
+ const r2b: i32 = toI32(r2e >>> 8) & 0xff
1127
+ const r2c: i32 = toI32(r2e) & 0xff
1118
1128
  const r2d: i32 = toI32(r2) & 0xff
1119
- l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
1120
- l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
1121
1129
  l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
1122
1130
  }
1123
1131
  const t0: u32 = r0
@@ -1143,6 +1151,19 @@ const bcryptBody3 = (st: u32[]): void => {
1143
1151
  * place, the four encryption chains interleaved round by round so that one
1144
1152
  * lane's S-box loads overlap the others' instead of waiting on their own. The
1145
1153
  * hot loop of the batch: 2^(cost+1) calls of 521 blocks per lane.
1154
+ *
1155
+ * Each lane's half-round is written whole, its bytes and then its update,
1156
+ * before the next lane's. The two middle index bytes come from one copy of the
1157
+ * word rotated right by 8 (`e`: byte 1 is its low byte, byte 2 its second),
1158
+ * behind the value barrier: `ctSelect(v, 0xffffffff, 0)` is `v`, opaque, so
1159
+ * LLVM cannot fold the two bytes back into two shifts of the word, and copies
1160
+ * it once instead of twice; on a BMI2 target the copy is one `rorx`. Three
1161
+ * and five lanes rotate the copy the same way; six shift it right by 8, which
1162
+ * has the same two low bytes and measured faster there. On a
1163
+ * Xeon Platinum 8260 that is 6% less time a hash at the best width, 9% under
1164
+ * `--cpu x86-64-v3` (docs/security/crypto-bcrypt.md, "Cascade Lake"). The
1165
+ * barrier is an identity on a register: it adds no branch and no load, and
1166
+ * the round reads the same four S-box words.
1146
1167
  */
1147
1168
  const bcryptBody4 = (st: u32[]): void => {
1148
1169
  if (st.length < 4168) {
@@ -1162,45 +1183,53 @@ const bcryptBody4 = (st: u32[]): void => {
1162
1183
  l2 ^= st[2084]
1163
1184
  l3 ^= st[3126]
1164
1185
  for (let n: i32 = 1; n < 17; n += 2) {
1186
+ const l0e: u32 = ctSelect((l0 >>> 8) | (l0 << 24), 0xffffffff, 0)
1165
1187
  const l0a: i32 = toI32(l0 >>> 24) & 0xff
1166
- const l0b: i32 = toI32(l0 >>> 16) & 0xff
1167
- const l0c: i32 = toI32(l0 >>> 8) & 0xff
1188
+ const l0b: i32 = toI32(l0e >>> 8) & 0xff
1189
+ const l0c: i32 = toI32(l0e) & 0xff
1168
1190
  const l0d: i32 = toI32(l0) & 0xff
1191
+ r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
1192
+ const l1e: u32 = ctSelect((l1 >>> 8) | (l1 << 24), 0xffffffff, 0)
1169
1193
  const l1a: i32 = toI32(l1 >>> 24) & 0xff
1170
- const l1b: i32 = toI32(l1 >>> 16) & 0xff
1171
- const l1c: i32 = toI32(l1 >>> 8) & 0xff
1194
+ const l1b: i32 = toI32(l1e >>> 8) & 0xff
1195
+ const l1c: i32 = toI32(l1e) & 0xff
1172
1196
  const l1d: i32 = toI32(l1) & 0xff
1197
+ r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
1198
+ const l2e: u32 = ctSelect((l2 >>> 8) | (l2 << 24), 0xffffffff, 0)
1173
1199
  const l2a: i32 = toI32(l2 >>> 24) & 0xff
1174
- const l2b: i32 = toI32(l2 >>> 16) & 0xff
1175
- const l2c: i32 = toI32(l2 >>> 8) & 0xff
1200
+ const l2b: i32 = toI32(l2e >>> 8) & 0xff
1201
+ const l2c: i32 = toI32(l2e) & 0xff
1176
1202
  const l2d: i32 = toI32(l2) & 0xff
1203
+ r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
1204
+ const l3e: u32 = ctSelect((l3 >>> 8) | (l3 << 24), 0xffffffff, 0)
1177
1205
  const l3a: i32 = toI32(l3 >>> 24) & 0xff
1178
- const l3b: i32 = toI32(l3 >>> 16) & 0xff
1179
- const l3c: i32 = toI32(l3 >>> 8) & 0xff
1206
+ const l3b: i32 = toI32(l3e >>> 8) & 0xff
1207
+ const l3c: i32 = toI32(l3e) & 0xff
1180
1208
  const l3d: i32 = toI32(l3) & 0xff
1181
- r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
1182
- r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
1183
- r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
1184
1209
  r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
1210
+ const r0e: u32 = ctSelect((r0 >>> 8) | (r0 << 24), 0xffffffff, 0)
1185
1211
  const r0a: i32 = toI32(r0 >>> 24) & 0xff
1186
- const r0b: i32 = toI32(r0 >>> 16) & 0xff
1187
- const r0c: i32 = toI32(r0 >>> 8) & 0xff
1212
+ const r0b: i32 = toI32(r0e >>> 8) & 0xff
1213
+ const r0c: i32 = toI32(r0e) & 0xff
1188
1214
  const r0d: i32 = toI32(r0) & 0xff
1215
+ l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
1216
+ const r1e: u32 = ctSelect((r1 >>> 8) | (r1 << 24), 0xffffffff, 0)
1189
1217
  const r1a: i32 = toI32(r1 >>> 24) & 0xff
1190
- const r1b: i32 = toI32(r1 >>> 16) & 0xff
1191
- const r1c: i32 = toI32(r1 >>> 8) & 0xff
1218
+ const r1b: i32 = toI32(r1e >>> 8) & 0xff
1219
+ const r1c: i32 = toI32(r1e) & 0xff
1192
1220
  const r1d: i32 = toI32(r1) & 0xff
1221
+ l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
1222
+ const r2e: u32 = ctSelect((r2 >>> 8) | (r2 << 24), 0xffffffff, 0)
1193
1223
  const r2a: i32 = toI32(r2 >>> 24) & 0xff
1194
- const r2b: i32 = toI32(r2 >>> 16) & 0xff
1195
- const r2c: i32 = toI32(r2 >>> 8) & 0xff
1224
+ const r2b: i32 = toI32(r2e >>> 8) & 0xff
1225
+ const r2c: i32 = toI32(r2e) & 0xff
1196
1226
  const r2d: i32 = toI32(r2) & 0xff
1227
+ l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
1228
+ const r3e: u32 = ctSelect((r3 >>> 8) | (r3 << 24), 0xffffffff, 0)
1197
1229
  const r3a: i32 = toI32(r3 >>> 24) & 0xff
1198
- const r3b: i32 = toI32(r3 >>> 16) & 0xff
1199
- const r3c: i32 = toI32(r3 >>> 8) & 0xff
1230
+ const r3b: i32 = toI32(r3e >>> 8) & 0xff
1231
+ const r3c: i32 = toI32(r3e) & 0xff
1200
1232
  const r3d: i32 = toI32(r3) & 0xff
1201
- l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
1202
- l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
1203
- l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
1204
1233
  l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
1205
1234
  }
1206
1235
  const t0: u32 = r0
@@ -1248,55 +1277,65 @@ const bcryptBody5 = (st: u32[]): void => {
1248
1277
  l3 ^= st[3126]
1249
1278
  l4 ^= st[4168]
1250
1279
  for (let n: i32 = 1; n < 17; n += 2) {
1280
+ const l0e: u32 = ctSelect((l0 >>> 8) | (l0 << 24), 0xffffffff, 0)
1251
1281
  const l0a: i32 = toI32(l0 >>> 24) & 0xff
1252
- const l0b: i32 = toI32(l0 >>> 16) & 0xff
1253
- const l0c: i32 = toI32(l0 >>> 8) & 0xff
1282
+ const l0b: i32 = toI32(l0e >>> 8) & 0xff
1283
+ const l0c: i32 = toI32(l0e) & 0xff
1254
1284
  const l0d: i32 = toI32(l0) & 0xff
1285
+ r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
1286
+ const l1e: u32 = ctSelect((l1 >>> 8) | (l1 << 24), 0xffffffff, 0)
1255
1287
  const l1a: i32 = toI32(l1 >>> 24) & 0xff
1256
- const l1b: i32 = toI32(l1 >>> 16) & 0xff
1257
- const l1c: i32 = toI32(l1 >>> 8) & 0xff
1288
+ const l1b: i32 = toI32(l1e >>> 8) & 0xff
1289
+ const l1c: i32 = toI32(l1e) & 0xff
1258
1290
  const l1d: i32 = toI32(l1) & 0xff
1291
+ r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
1292
+ const l2e: u32 = ctSelect((l2 >>> 8) | (l2 << 24), 0xffffffff, 0)
1259
1293
  const l2a: i32 = toI32(l2 >>> 24) & 0xff
1260
- const l2b: i32 = toI32(l2 >>> 16) & 0xff
1261
- const l2c: i32 = toI32(l2 >>> 8) & 0xff
1294
+ const l2b: i32 = toI32(l2e >>> 8) & 0xff
1295
+ const l2c: i32 = toI32(l2e) & 0xff
1262
1296
  const l2d: i32 = toI32(l2) & 0xff
1297
+ r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
1298
+ const l3e: u32 = ctSelect((l3 >>> 8) | (l3 << 24), 0xffffffff, 0)
1263
1299
  const l3a: i32 = toI32(l3 >>> 24) & 0xff
1264
- const l3b: i32 = toI32(l3 >>> 16) & 0xff
1265
- const l3c: i32 = toI32(l3 >>> 8) & 0xff
1300
+ const l3b: i32 = toI32(l3e >>> 8) & 0xff
1301
+ const l3c: i32 = toI32(l3e) & 0xff
1266
1302
  const l3d: i32 = toI32(l3) & 0xff
1303
+ r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
1304
+ const l4e: u32 = ctSelect((l4 >>> 8) | (l4 << 24), 0xffffffff, 0)
1267
1305
  const l4a: i32 = toI32(l4 >>> 24) & 0xff
1268
- const l4b: i32 = toI32(l4 >>> 16) & 0xff
1269
- const l4c: i32 = toI32(l4 >>> 8) & 0xff
1306
+ const l4b: i32 = toI32(l4e >>> 8) & 0xff
1307
+ const l4c: i32 = toI32(l4e) & 0xff
1270
1308
  const l4d: i32 = toI32(l4) & 0xff
1271
- r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
1272
- r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
1273
- r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
1274
- r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
1275
1309
  r4 ^= st[4168 + n] ^ (((st[4186 + l4a] + st[4442 + l4b]) ^ st[4698 + l4c]) + st[4954 + l4d])
1310
+ const r0e: u32 = ctSelect((r0 >>> 8) | (r0 << 24), 0xffffffff, 0)
1276
1311
  const r0a: i32 = toI32(r0 >>> 24) & 0xff
1277
- const r0b: i32 = toI32(r0 >>> 16) & 0xff
1278
- const r0c: i32 = toI32(r0 >>> 8) & 0xff
1312
+ const r0b: i32 = toI32(r0e >>> 8) & 0xff
1313
+ const r0c: i32 = toI32(r0e) & 0xff
1279
1314
  const r0d: i32 = toI32(r0) & 0xff
1315
+ l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
1316
+ const r1e: u32 = ctSelect((r1 >>> 8) | (r1 << 24), 0xffffffff, 0)
1280
1317
  const r1a: i32 = toI32(r1 >>> 24) & 0xff
1281
- const r1b: i32 = toI32(r1 >>> 16) & 0xff
1282
- const r1c: i32 = toI32(r1 >>> 8) & 0xff
1318
+ const r1b: i32 = toI32(r1e >>> 8) & 0xff
1319
+ const r1c: i32 = toI32(r1e) & 0xff
1283
1320
  const r1d: i32 = toI32(r1) & 0xff
1321
+ l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
1322
+ const r2e: u32 = ctSelect((r2 >>> 8) | (r2 << 24), 0xffffffff, 0)
1284
1323
  const r2a: i32 = toI32(r2 >>> 24) & 0xff
1285
- const r2b: i32 = toI32(r2 >>> 16) & 0xff
1286
- const r2c: i32 = toI32(r2 >>> 8) & 0xff
1324
+ const r2b: i32 = toI32(r2e >>> 8) & 0xff
1325
+ const r2c: i32 = toI32(r2e) & 0xff
1287
1326
  const r2d: i32 = toI32(r2) & 0xff
1327
+ l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
1328
+ const r3e: u32 = ctSelect((r3 >>> 8) | (r3 << 24), 0xffffffff, 0)
1288
1329
  const r3a: i32 = toI32(r3 >>> 24) & 0xff
1289
- const r3b: i32 = toI32(r3 >>> 16) & 0xff
1290
- const r3c: i32 = toI32(r3 >>> 8) & 0xff
1330
+ const r3b: i32 = toI32(r3e >>> 8) & 0xff
1331
+ const r3c: i32 = toI32(r3e) & 0xff
1291
1332
  const r3d: i32 = toI32(r3) & 0xff
1333
+ l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
1334
+ const r4e: u32 = ctSelect((r4 >>> 8) | (r4 << 24), 0xffffffff, 0)
1292
1335
  const r4a: i32 = toI32(r4 >>> 24) & 0xff
1293
- const r4b: i32 = toI32(r4 >>> 16) & 0xff
1294
- const r4c: i32 = toI32(r4 >>> 8) & 0xff
1336
+ const r4b: i32 = toI32(r4e >>> 8) & 0xff
1337
+ const r4c: i32 = toI32(r4e) & 0xff
1295
1338
  const r4d: i32 = toI32(r4) & 0xff
1296
- l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
1297
- l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
1298
- l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
1299
- l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
1300
1339
  l4 ^= st[4168 + n + 1] ^ (((st[4186 + r4a] + st[4442 + r4b]) ^ st[4698 + r4c]) + st[4954 + r4d])
1301
1340
  }
1302
1341
  const t0: u32 = r0
@@ -1352,65 +1391,77 @@ const bcryptBody6 = (st: u32[]): void => {
1352
1391
  l4 ^= st[4168]
1353
1392
  l5 ^= st[5210]
1354
1393
  for (let n: i32 = 1; n < 17; n += 2) {
1394
+ const l0e: u32 = ctSelect(l0 >>> 8, 0xffffffff, 0)
1355
1395
  const l0a: i32 = toI32(l0 >>> 24) & 0xff
1356
- const l0b: i32 = toI32(l0 >>> 16) & 0xff
1357
- const l0c: i32 = toI32(l0 >>> 8) & 0xff
1396
+ const l0b: i32 = toI32(l0e >>> 8) & 0xff
1397
+ const l0c: i32 = toI32(l0e) & 0xff
1358
1398
  const l0d: i32 = toI32(l0) & 0xff
1399
+ r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
1400
+ const l1e: u32 = ctSelect(l1 >>> 8, 0xffffffff, 0)
1359
1401
  const l1a: i32 = toI32(l1 >>> 24) & 0xff
1360
- const l1b: i32 = toI32(l1 >>> 16) & 0xff
1361
- const l1c: i32 = toI32(l1 >>> 8) & 0xff
1402
+ const l1b: i32 = toI32(l1e >>> 8) & 0xff
1403
+ const l1c: i32 = toI32(l1e) & 0xff
1362
1404
  const l1d: i32 = toI32(l1) & 0xff
1405
+ r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
1406
+ const l2e: u32 = ctSelect(l2 >>> 8, 0xffffffff, 0)
1363
1407
  const l2a: i32 = toI32(l2 >>> 24) & 0xff
1364
- const l2b: i32 = toI32(l2 >>> 16) & 0xff
1365
- const l2c: i32 = toI32(l2 >>> 8) & 0xff
1408
+ const l2b: i32 = toI32(l2e >>> 8) & 0xff
1409
+ const l2c: i32 = toI32(l2e) & 0xff
1366
1410
  const l2d: i32 = toI32(l2) & 0xff
1411
+ r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
1412
+ const l3e: u32 = ctSelect(l3 >>> 8, 0xffffffff, 0)
1367
1413
  const l3a: i32 = toI32(l3 >>> 24) & 0xff
1368
- const l3b: i32 = toI32(l3 >>> 16) & 0xff
1369
- const l3c: i32 = toI32(l3 >>> 8) & 0xff
1414
+ const l3b: i32 = toI32(l3e >>> 8) & 0xff
1415
+ const l3c: i32 = toI32(l3e) & 0xff
1370
1416
  const l3d: i32 = toI32(l3) & 0xff
1417
+ r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
1418
+ const l4e: u32 = ctSelect(l4 >>> 8, 0xffffffff, 0)
1371
1419
  const l4a: i32 = toI32(l4 >>> 24) & 0xff
1372
- const l4b: i32 = toI32(l4 >>> 16) & 0xff
1373
- const l4c: i32 = toI32(l4 >>> 8) & 0xff
1420
+ const l4b: i32 = toI32(l4e >>> 8) & 0xff
1421
+ const l4c: i32 = toI32(l4e) & 0xff
1374
1422
  const l4d: i32 = toI32(l4) & 0xff
1423
+ r4 ^= st[4168 + n] ^ (((st[4186 + l4a] + st[4442 + l4b]) ^ st[4698 + l4c]) + st[4954 + l4d])
1424
+ const l5e: u32 = ctSelect(l5 >>> 8, 0xffffffff, 0)
1375
1425
  const l5a: i32 = toI32(l5 >>> 24) & 0xff
1376
- const l5b: i32 = toI32(l5 >>> 16) & 0xff
1377
- const l5c: i32 = toI32(l5 >>> 8) & 0xff
1426
+ const l5b: i32 = toI32(l5e >>> 8) & 0xff
1427
+ const l5c: i32 = toI32(l5e) & 0xff
1378
1428
  const l5d: i32 = toI32(l5) & 0xff
1379
- r0 ^= st[n] ^ (((st[18 + l0a] + st[274 + l0b]) ^ st[530 + l0c]) + st[786 + l0d])
1380
- r1 ^= st[1042 + n] ^ (((st[1060 + l1a] + st[1316 + l1b]) ^ st[1572 + l1c]) + st[1828 + l1d])
1381
- r2 ^= st[2084 + n] ^ (((st[2102 + l2a] + st[2358 + l2b]) ^ st[2614 + l2c]) + st[2870 + l2d])
1382
- r3 ^= st[3126 + n] ^ (((st[3144 + l3a] + st[3400 + l3b]) ^ st[3656 + l3c]) + st[3912 + l3d])
1383
- r4 ^= st[4168 + n] ^ (((st[4186 + l4a] + st[4442 + l4b]) ^ st[4698 + l4c]) + st[4954 + l4d])
1384
1429
  r5 ^= st[5210 + n] ^ (((st[5228 + l5a] + st[5484 + l5b]) ^ st[5740 + l5c]) + st[5996 + l5d])
1430
+ const r0e: u32 = ctSelect(r0 >>> 8, 0xffffffff, 0)
1385
1431
  const r0a: i32 = toI32(r0 >>> 24) & 0xff
1386
- const r0b: i32 = toI32(r0 >>> 16) & 0xff
1387
- const r0c: i32 = toI32(r0 >>> 8) & 0xff
1432
+ const r0b: i32 = toI32(r0e >>> 8) & 0xff
1433
+ const r0c: i32 = toI32(r0e) & 0xff
1388
1434
  const r0d: i32 = toI32(r0) & 0xff
1435
+ l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
1436
+ const r1e: u32 = ctSelect(r1 >>> 8, 0xffffffff, 0)
1389
1437
  const r1a: i32 = toI32(r1 >>> 24) & 0xff
1390
- const r1b: i32 = toI32(r1 >>> 16) & 0xff
1391
- const r1c: i32 = toI32(r1 >>> 8) & 0xff
1438
+ const r1b: i32 = toI32(r1e >>> 8) & 0xff
1439
+ const r1c: i32 = toI32(r1e) & 0xff
1392
1440
  const r1d: i32 = toI32(r1) & 0xff
1441
+ l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
1442
+ const r2e: u32 = ctSelect(r2 >>> 8, 0xffffffff, 0)
1393
1443
  const r2a: i32 = toI32(r2 >>> 24) & 0xff
1394
- const r2b: i32 = toI32(r2 >>> 16) & 0xff
1395
- const r2c: i32 = toI32(r2 >>> 8) & 0xff
1444
+ const r2b: i32 = toI32(r2e >>> 8) & 0xff
1445
+ const r2c: i32 = toI32(r2e) & 0xff
1396
1446
  const r2d: i32 = toI32(r2) & 0xff
1447
+ l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
1448
+ const r3e: u32 = ctSelect(r3 >>> 8, 0xffffffff, 0)
1397
1449
  const r3a: i32 = toI32(r3 >>> 24) & 0xff
1398
- const r3b: i32 = toI32(r3 >>> 16) & 0xff
1399
- const r3c: i32 = toI32(r3 >>> 8) & 0xff
1450
+ const r3b: i32 = toI32(r3e >>> 8) & 0xff
1451
+ const r3c: i32 = toI32(r3e) & 0xff
1400
1452
  const r3d: i32 = toI32(r3) & 0xff
1453
+ l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
1454
+ const r4e: u32 = ctSelect(r4 >>> 8, 0xffffffff, 0)
1401
1455
  const r4a: i32 = toI32(r4 >>> 24) & 0xff
1402
- const r4b: i32 = toI32(r4 >>> 16) & 0xff
1403
- const r4c: i32 = toI32(r4 >>> 8) & 0xff
1456
+ const r4b: i32 = toI32(r4e >>> 8) & 0xff
1457
+ const r4c: i32 = toI32(r4e) & 0xff
1404
1458
  const r4d: i32 = toI32(r4) & 0xff
1459
+ l4 ^= st[4168 + n + 1] ^ (((st[4186 + r4a] + st[4442 + r4b]) ^ st[4698 + r4c]) + st[4954 + r4d])
1460
+ const r5e: u32 = ctSelect(r5 >>> 8, 0xffffffff, 0)
1405
1461
  const r5a: i32 = toI32(r5 >>> 24) & 0xff
1406
- const r5b: i32 = toI32(r5 >>> 16) & 0xff
1407
- const r5c: i32 = toI32(r5 >>> 8) & 0xff
1462
+ const r5b: i32 = toI32(r5e >>> 8) & 0xff
1463
+ const r5c: i32 = toI32(r5e) & 0xff
1408
1464
  const r5d: i32 = toI32(r5) & 0xff
1409
- l0 ^= st[n + 1] ^ (((st[18 + r0a] + st[274 + r0b]) ^ st[530 + r0c]) + st[786 + r0d])
1410
- l1 ^= st[1042 + n + 1] ^ (((st[1060 + r1a] + st[1316 + r1b]) ^ st[1572 + r1c]) + st[1828 + r1d])
1411
- l2 ^= st[2084 + n + 1] ^ (((st[2102 + r2a] + st[2358 + r2b]) ^ st[2614 + r2c]) + st[2870 + r2d])
1412
- l3 ^= st[3126 + n + 1] ^ (((st[3144 + r3a] + st[3400 + r3b]) ^ st[3656 + r3c]) + st[3912 + r3d])
1413
- l4 ^= st[4168 + n + 1] ^ (((st[4186 + r4a] + st[4442 + r4b]) ^ st[4698 + r4c]) + st[4954 + r4d])
1414
1465
  l5 ^= st[5210 + n + 1] ^ (((st[5228 + r5a] + st[5484 + r5b]) ^ st[5740 + r5c]) + st[5996 + r5d])
1415
1466
  }
1416
1467
  const t0: u32 = r0
package/std/sort.ts ADDED
@@ -0,0 +1,178 @@
1
+ /**
2
+ * `std/sort` — `xs.sort(cmp)` on an array, in Nish: a stable merge sort,
3
+ * compiled once per comparator.
4
+ *
5
+ * A program does not import this module. A `.sort(cmp)` call is what loads it
6
+ * (the checker's `importSort`), and the checker routes the call to `sortWith`
7
+ * here. `cmp` is a compile-time function parameter, so each comparator is an
8
+ * instantiation of its own whose body calls the comparator directly — there is
9
+ * no function pointer. Under Node and `tsc` the call is the platform's
10
+ * `Array.prototype.sort`, so nothing here runs there.
11
+ *
12
+ * **Same order as Node.** `Array.prototype.sort` is stable (ES2019), so for a
13
+ * comparator that is a consistent total preorder there is exactly one answer,
14
+ * and any stable sort gives it. A comparator's result is read only as "greater
15
+ * than zero or not": a later element moves before an earlier one only when
16
+ * the comparator answers a positive number for (earlier, later), so a negative
17
+ * zero and NaN are zero, as Node treats them.
18
+ *
19
+ * **Shape.** The array is copied once into a scratch array of the same
20
+ * length, the only allocation, which the function's arena scope gives back
21
+ * before it returns, so a sort leaves `Arena.used()` where it was. Runs of
22
+ * `RUN` are sorted by insertion from the scratch into the array (a strictly
23
+ * descending run is copied reversed instead), then merged
24
+ * bottom-up, alternating between the two, and copied back once if the last
25
+ * pass ended in the scratch. A pair of runs already in order is copied without
26
+ * a merge. Every merge takes from the left run on a tie, which is what makes
27
+ * it stable.
28
+ *
29
+ * **No element is held across a write.** An `interface` element is stored
30
+ * inline, and `const v = xs[i]` is a pointer into the array, so an insertion
31
+ * sort in place would overwrite the element it is inserting. Every step here
32
+ * reads from one array and writes the other, and moves a whole slot, which for
33
+ * an inline record is one copy of its bytes.
34
+ *
35
+ * **Proofs.** Every index is proven in range by the guard written beside it,
36
+ * so no access keeps its bounds check. The run arithmetic wraps only where
37
+ * the operands are proven to lie in `0..n`, so it cannot wrap: a difference
38
+ * of two of them, or a sum taken only when it stays below `n`.
39
+ */
40
+
41
+ import { wrappingAdd, wrappingSub } from "nish:unsafe"
42
+
43
+ /** The length of the runs insertion sort makes before the first merge. */
44
+ const RUN: i32 = 32
45
+
46
+ /**
47
+ * `lo + step`, or `n` when that is not below `n`. Every caller has
48
+ * `0 <= lo <= n`, so `n - lo` cannot wrap, and the sum is taken only below `n`.
49
+ */
50
+ const stepTo = (lo: i32, step: i32, n: i32): i32 => (wrappingSub(n, lo) > step ? wrappingAdd(lo, step) : n)
51
+
52
+ /**
53
+ * `src[lo..hi)` sorted by insertion into `dst[lo..hi)`. The comparator call
54
+ * forgets every array length, so each access after it repeats its guard.
55
+ */
56
+ const insertionRun = <T>(src: T[], dst: T[], cmp: (a: T, b: T) => number, lo: i32, hi: i32): void => {
57
+ // A strictly descending run is copied reversed: no two of its elements are
58
+ // equal, so reversing it keeps the sort stable, and costs one comparison each.
59
+ // `lo < hi`, and `d` steps only while below `hi`, so neither `+ 1` wraps.
60
+ let d = wrappingAdd(lo, 1)
61
+ while (d > 0 && d < hi && d < toI32(src.length)) {
62
+ const p = d - 1
63
+ if (!(cmp(src[p], src[d]) > 0)) {
64
+ break
65
+ }
66
+ d = wrappingAdd(d, 1)
67
+ }
68
+ if (d >= hi) {
69
+ // `hi > lo >= 0`, so `hi - 1` does not wrap, and `r` steps down only from `r >= 0`.
70
+ let r = wrappingSub(hi, 1)
71
+ for (let k: i32 = lo; k >= 0 && k < hi && k < toI32(dst.length) && r >= 0 && r < toI32(src.length); k++) {
72
+ dst[k] = src[r]
73
+ r = wrappingSub(r, 1)
74
+ }
75
+ return
76
+ }
77
+ for (let i: i32 = lo; i >= 0 && i < hi; i++) {
78
+ let j = i
79
+ while (j > lo && j > 0 && j < toI32(dst.length) && i < toI32(src.length)) {
80
+ const k = j - 1
81
+ if (!(cmp(dst[k], src[i]) > 0) || k < 0 || k >= toI32(dst.length) || j >= toI32(dst.length)) {
82
+ break
83
+ }
84
+ dst[j] = dst[k]
85
+ j = k
86
+ }
87
+ if (i < toI32(src.length) && j >= 0 && j < toI32(dst.length)) {
88
+ dst[j] = src[i]
89
+ }
90
+ }
91
+ }
92
+
93
+ /** `src[lo..hi)` copied into `dst[lo..hi)`. */
94
+ const copyRun = <T>(src: T[], dst: T[], lo: i32, hi: i32): void => {
95
+ for (let k: i32 = lo; k >= 0 && k < hi && k < toI32(src.length) && k < toI32(dst.length); k++) {
96
+ dst[k] = src[k]
97
+ }
98
+ }
99
+
100
+ /**
101
+ * The sorted runs `src[lo..mid)` and `src[mid..hi)` merged into `dst[lo..hi)`,
102
+ * or copied when the last of the left run is not after the first of the right.
103
+ */
104
+ const mergeRuns = <T>(src: T[], dst: T[], cmp: (a: T, b: T) => number, lo: i32, mid: i32, hi: i32): void => {
105
+ if (mid > 0 && mid < hi && mid < toI32(src.length)) {
106
+ const last = mid - 1
107
+ if (last < toI32(src.length) && !(cmp(src[last], src[mid]) > 0)) {
108
+ copyRun(src, dst, lo, hi)
109
+ return
110
+ }
111
+ }
112
+ let i = lo
113
+ let j = mid
114
+ for (let k: i32 = lo; k >= 0 && k < hi; k++) {
115
+ let right = i >= mid
116
+ if (!right && j < hi && i >= 0 && i < toI32(src.length) && j >= 0 && j < toI32(src.length)) {
117
+ right = cmp(src[i], src[j]) > 0
118
+ }
119
+ if (right) {
120
+ if (j >= 0 && j < toI32(src.length) && k < toI32(dst.length)) {
121
+ dst[k] = src[j]
122
+ }
123
+ // `j` steps at most `hi - mid` times from `mid`, so it stays at most `hi`.
124
+ j = wrappingAdd(j, 1)
125
+ } else {
126
+ if (i >= 0 && i < toI32(src.length) && k < toI32(dst.length)) {
127
+ dst[k] = src[i]
128
+ }
129
+ // `i` steps at most `mid - lo` times from `lo`, so it stays at most `mid`.
130
+ i = wrappingAdd(i, 1)
131
+ }
132
+ }
133
+ }
134
+
135
+ /** One bottom-up pass: every pair of adjacent `width`-runs of `src` merged into `dst`. */
136
+ const mergePass = <T>(src: T[], dst: T[], cmp: (a: T, b: T) => number, n: i32, width: i32): void => {
137
+ let lo: i32 = 0
138
+ while (lo >= 0 && lo < n) {
139
+ const mid = stepTo(lo, width, n)
140
+ const hi = stepTo(mid, width, n)
141
+ mergeRuns(src, dst, cmp, lo, mid, hi)
142
+ lo = hi
143
+ }
144
+ }
145
+
146
+ /** `xs` sorted in place by `cmp`, stably, and answered: what `xs.sort(cmp)` compiles to. */
147
+ export const sortWith = <T>(xs: T[], cmp: (a: T, b: T) => number): T[] => {
148
+ const n: i32 = toI32(xs.length)
149
+ if (n < 2) {
150
+ return xs
151
+ }
152
+ // One allocation at full size, every slot written before any is read: the
153
+ // checker lets this `new Array<T>(n)` hold any `T`, and neither emitter
154
+ // zero-fills it (`isSortInstance`).
155
+ const tmp = new Array<T>(n)
156
+ copyRun(xs, tmp, 0, n)
157
+ let lo: i32 = 0
158
+ while (lo >= 0 && lo < n) {
159
+ const hi = stepTo(lo, RUN, n)
160
+ insertionRun(tmp, xs, cmp, lo, hi)
161
+ lo = hi
162
+ }
163
+ let inTmp = false
164
+ let width: i32 = RUN
165
+ while (width > 0 && width < n) {
166
+ if (inTmp) {
167
+ mergePass(tmp, xs, cmp, n, width)
168
+ } else {
169
+ mergePass(xs, tmp, cmp, n, width)
170
+ }
171
+ inTmp = !inTmp
172
+ width = stepTo(width, width, n)
173
+ }
174
+ if (inTmp) {
175
+ copyRun(tmp, xs, 0, n)
176
+ }
177
+ return xs
178
+ }