vectrojs 0.0.1 → 0.1.1

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.
Files changed (63) hide show
  1. package/CHANGELOG.md +89 -0
  2. package/README.md +828 -4
  3. package/bin/vectro.mjs +82 -0
  4. package/dist/vectro.wasm +0 -0
  5. package/mcp/server.mjs +288 -0
  6. package/package.json +84 -6
  7. package/src/arrow.mjs +905 -0
  8. package/src/bitpack.mjs +135 -0
  9. package/src/browser.mjs +129 -0
  10. package/src/bytes.mjs +60 -0
  11. package/src/csv-kernel.mjs +205 -0
  12. package/src/csv.mjs +270 -0
  13. package/src/dataset.mjs +1763 -0
  14. package/src/dates.mjs +139 -0
  15. package/src/emitter.mjs +109 -0
  16. package/src/engine.mjs +881 -0
  17. package/src/files-browser.mjs +104 -0
  18. package/src/files-node.mjs +59 -0
  19. package/src/frames.mjs +191 -0
  20. package/src/fuse.mjs +512 -0
  21. package/src/interp.mjs +305 -0
  22. package/src/join.mjs +534 -0
  23. package/src/kernels.mjs +312 -0
  24. package/src/ndjson.mjs +136 -0
  25. package/src/node.mjs +120 -0
  26. package/src/pack.mjs +114 -0
  27. package/src/parallel-sort.mjs +99 -0
  28. package/src/partition-kernels.mjs +388 -0
  29. package/src/partition.mjs +395 -0
  30. package/src/plan.mjs +551 -0
  31. package/src/portable.mjs +21 -0
  32. package/src/rank-kernels.mjs +81 -0
  33. package/src/rows.mjs +91 -0
  34. package/src/serve/catalog.mjs +148 -0
  35. package/src/serve/flight.mjs +270 -0
  36. package/src/serve/formats.mjs +87 -0
  37. package/src/serve/http.mjs +182 -0
  38. package/src/serve/index.mjs +9 -0
  39. package/src/serve/odata.mjs +291 -0
  40. package/src/serve/pg.mjs +588 -0
  41. package/src/serve/pgcatalog.mjs +220 -0
  42. package/src/serve/pgtypes.mjs +199 -0
  43. package/src/sketch.mjs +75 -0
  44. package/src/sort-kernels.mjs +117 -0
  45. package/src/sort.mjs +102 -0
  46. package/src/split-worker-core.mjs +252 -0
  47. package/src/split-worker-node.mjs +8 -0
  48. package/src/split-worker.mjs +7 -0
  49. package/src/split.mjs +741 -0
  50. package/src/sql.mjs +1558 -0
  51. package/src/sqltext.mjs +132 -0
  52. package/src/static-kernels.mjs +164 -0
  53. package/src/stream-worker.mjs +553 -0
  54. package/src/table-kernels.mjs +323 -0
  55. package/src/table.mjs +2247 -0
  56. package/src/textmap.mjs +159 -0
  57. package/src/vectors.mjs +138 -0
  58. package/src/wasm.mjs +254 -0
  59. package/src/window.mjs +160 -0
  60. package/src/worker-browser.mjs +21 -0
  61. package/src/worker-loop.mjs +106 -0
  62. package/src/worker.mjs +22 -0
  63. package/src/zones.mjs +75 -0
@@ -0,0 +1,99 @@
1
+ // Parallel stable sort on every thread: LSD radix sort, 11-bit digits, the
2
+ // last key first (see sort-kernels.mjs for the kernels).
3
+ //
4
+ // For each key: one pass turns the key (in the current order) into two
5
+ // sortable words per row and counts every digit in each unit. Digits that
6
+ // are the same in every row are skipped. For each remaining digit, each unit
7
+ // counts its rows per bucket (the first digit reuses the counts above), this
8
+ // thread turns the counts into each unit's starting positions (bucket by
9
+ // bucket, unit by unit: that keeps the sort stable), and every unit moves its
10
+ // rows (index and words) to those positions at once.
11
+
12
+ import { OP, unitCount } from './kernels.mjs';
13
+ import { prepModule, countModule, moveModule, gatherModule, DIGITS, BUCKETS } from './sort-kernels.mjs';
14
+
15
+ function kernel(engine, name, make) {
16
+ engine._sortKernels ??= new Map();
17
+ let v = engine._sortKernels.get(name);
18
+ if (!v) { v = engine._register(make()); engine._sortKernels.set(name, v); }
19
+ return v;
20
+ }
21
+
22
+ // keys: engine arrays of the table's type ('f64' | 'f32'), first key most
23
+ // important; ascending, NaN last, ties in row order. Returns the order as an
24
+ // engine-allocated Uint32Array of n row numbers (free it when done).
25
+ export function* sortPermG(engine, keys, n, t) {
26
+ const shared = engine.isShared;
27
+ const prep = kernel(engine, `prep|${t}`, () => prepModule(t, shared));
28
+ const count = kernel(engine, 'count', () => countModule(shared));
29
+ const move = kernel(engine, 'move', () => moveModule(shared));
30
+ const units = unitCount(OP.UNITS, engine.T, n, null);
31
+ let idx = engine.alloc(Uint32Array, n), idx2 = engine.alloc(Uint32Array, n);
32
+ let lo = engine.alloc(Uint32Array, n), lo2 = engine.alloc(Uint32Array, n);
33
+ let hi = engine.alloc(Uint32Array, n), hi2 = engine.alloc(Uint32Array, n);
34
+ const hist6 = engine.alloc(Uint32Array, units * 6 * BUCKETS), off = engine.alloc(Uint32Array, units * BUCKETS);
35
+ const p = (a) => engine.live(a).byteOffset;
36
+ try {
37
+ const id = engine.live(idx);
38
+ for (let i = 0; i < n; i++) id[i] = i;
39
+ for (let k = keys.length - 1; k >= 0; k--) {
40
+ // whole numbers within 2^53 of their minimum sort as that offset
41
+ const [mn, mx, frac] = yield* engine.evalG(['min(k)', 'max(k)', 'count(k != floor(k))'], { k: keys[k] });
42
+ const whole = frac === 0 && mx - mn < 2 ** 53;
43
+ engine.live(hist6).fill(0);
44
+ const ordered = k < keys.length - 1; // later keys: read in the current order
45
+ yield* engine._unitsG(prep, n, [p(keys[k]), p(idx), ordered ? 1 : 0, p(lo), p(hi), whole ? 1 : 0, p(hist6), whole ? mn : 0]);
46
+ // rows per bucket of every digit, over all units
47
+ const h6 = engine.live(hist6);
48
+ const skip = DIGITS.map((_, d) => {
49
+ const tot = new Float64Array(BUCKETS);
50
+ for (let u = 0; u < units; u++) {
51
+ const base = (u * 6 + d) * BUCKETS;
52
+ for (let b = 0; b < BUCKETS; b++) tot[b] += h6[base + b];
53
+ }
54
+ return tot.some((c) => c === n); // one bucket holds every row
55
+ });
56
+ let fresh = true; // hist6 still describes the current order
57
+ for (let d = 0; d < DIGITS.length; d++) {
58
+ if (skip[d]) continue;
59
+ const [word, shift] = DIGITS[d];
60
+ if (fresh) {
61
+ const o = engine.live(off), h = engine.live(hist6);
62
+ for (let u = 0; u < units; u++) o.set(h.subarray((u * 6 + d) * BUCKETS, (u * 6 + d + 1) * BUCKETS), u * BUCKETS);
63
+ } else {
64
+ engine.live(off).fill(0);
65
+ yield* engine._unitsG(count, n, [p(word ? hi : lo), shift, p(off)]);
66
+ }
67
+ fresh = false;
68
+ // each unit's first position per bucket: bucket by bucket, unit by unit
69
+ const o = engine.live(off);
70
+ let at = 0;
71
+ for (let b = 0; b < BUCKETS; b++) {
72
+ for (let u = 0; u < units; u++) { const c = o[u * BUCKETS + b]; o[u * BUCKETS + b] = at; at += c; }
73
+ }
74
+ const carryLo = DIGITS.slice(d + 1).some(([w], j) => w === 0 && !skip[d + 1 + j]) ? 1 : 0;
75
+ yield* engine._unitsG(move, n, [word, shift, p(idx), p(lo), p(hi), p(idx2), p(lo2), p(hi2), p(off), carryLo]);
76
+ [idx, idx2] = [idx2, idx];
77
+ [lo, lo2] = [lo2, lo];
78
+ [hi, hi2] = [hi2, hi];
79
+ }
80
+ }
81
+ const out = idx;
82
+ idx = null;
83
+ return out;
84
+ } finally {
85
+ for (const a of [idx, idx2, lo, lo2, hi, hi2, hist6, off]) if (a) engine.free(a);
86
+ }
87
+ }
88
+
89
+ // dst[i] = src[perm[i]] for i < n, on every thread; src and the result are
90
+ // engine arrays of type t, or with enc a packed column (its codes are copied
91
+ // as they are; the result keeps the same packing, with room after the end).
92
+ export function* gatherG(engine, src, perm, n, t, enc = null) {
93
+ const s = engine.live(src);
94
+ const bytes = s.BYTES_PER_ELEMENT;
95
+ const g = kernel(engine, `gather|${bytes}`, () => gatherModule(bytes, engine.isShared));
96
+ const dst = engine.alloc(enc ? s.constructor : t === 'f32' ? Float32Array : Float64Array, n + (enc ? 16 / bytes : 0));
97
+ yield* engine._unitsG(g, n, [engine.live(src).byteOffset, engine.live(perm).byteOffset, engine.live(dst).byteOffset]);
98
+ return engine.live(dst);
99
+ }
@@ -0,0 +1,388 @@
1
+ // WebAssembly kernels for group-by with many groups (see partition.mjs).
2
+ // Pointers are byte offsets into the engine's memory. Every kernel takes one
3
+ // of them: a block of parameters (layouts below), so keys, columns and
4
+ // aggregations aren't limited by the dispatcher's scalars.
5
+ //
6
+ // A row's key becomes r: 1-4 uint64 words that compare (word by word, as
7
+ // unsigned numbers) like the keys do: the first key first, text in dictionary
8
+ // order, numbers ascending, NaN last. Each key is a bit field: whole numbers
9
+ // (and text codes) as their offset from the minimum, NaN as the code after
10
+ // the largest; other numbers as their float bits with the sign flipped
11
+ // (negatives: every bit; -0 as 0; NaN: all ones). Fields never straddle
12
+ // words. A field's lo, NaN code, width and place come in the parameters, so
13
+ // a kernel serves any data of the same shape.
14
+
15
+ import { I32, F64, TY, Fn, buildModule, get, set, i32c, block, loop, end, br, brIf, i32add, i32mul, i32and, i32geu } from './wasm.mjs';
16
+ import { Emitter, packedScalar, PACKED_BYTES } from './fuse.mjs';
17
+
18
+ const I64 = 0x7e;
19
+ const uleb = (n) => { const out = []; do { let b = n & 0x7f; n >>>= 7; if (n) b |= 0x80; out.push(b); } while (n); return out; };
20
+ const i64c = (v) => { // signed LEB128
21
+ v = BigInt.asIntN(64, BigInt(v));
22
+ const out = [0x42];
23
+ for (;;) {
24
+ const b = Number(v & 0x7fn);
25
+ v >>= 7n;
26
+ if ((v === 0n && !(b & 0x40)) || (v === -1n && b & 0x40)) { out.push(b); return out; }
27
+ out.push(b | 0x80);
28
+ }
29
+ };
30
+ const f64c = (v) => [0x44, ...new Uint8Array(Float64Array.of(v).buffer)];
31
+ const tee = (i) => [0x22, ...uleb(i)];
32
+ const L32 = (o = 0) => [0x28, 2, ...uleb(o)], S32 = (o = 0) => [0x36, 2, ...uleb(o)];
33
+ const L64 = (o = 0) => [0x29, 3, ...uleb(o)], S64 = (o = 0) => [0x37, 3, ...uleb(o)];
34
+ const LF64 = (o = 0) => [0x2b, 3, ...uleb(o)], SF64 = (o = 0) => [0x39, 3, ...uleb(o)];
35
+ const L16U = (o = 0) => [0x2f, 1, ...uleb(o)];
36
+ const X = {
37
+ i32sub: [0x6b], i32shl: [0x74], i32shru: [0x76], i32gtu: [0x4b], i32eq: [0x46], i32ne: [0x47], i32eqz: [0x45], i32or: [0x72], i32clz: [0x67], i32ctz: [0x68], i32divu: [0x6e],
38
+ i64sub: [0x7d], i64mul: [0x7e], i64and: [0x83], i64or: [0x84], i64xor: [0x85], i64shl: [0x86], i64shru: [0x88], i64eq: [0x51], i64lts: [0x53], i64ltu: [0x54], i64gtu: [0x56], i64clz: [0x79],
39
+ wrap: [0xa7], extu: [0xad], truncSatU64: [0xfc, 0x07], f64FromU64: [0xba], i64bits: [0xbd], f64bits: [0xbf],
40
+ f64add: [0xa0], f64sub: [0xa1], f64ne: [0x62], f64min: [0xa4], f64max: [0xa5], promote: [0xbb], select: [0x1b],
41
+ fill: [0xfc, 0x0b, 0x00], copy: [0xfc, 0x0a, 0x00, 0x00],
42
+ };
43
+ const IF = [0x04, 0x40], RETURN = [0x0f];
44
+ const MIX = 0x9e3779b97f4a7c15n, MIX2 = 0xc2b2ae3d27d4eb4fn, SIGN = 0x8000000000000000n;
45
+ const align8 = (x) => (x + 7) & ~7;
46
+ const addr4 = (base, i) => [...get(i), ...i32c(2), ...X.i32shl, ...get(base), ...i32add]; // base + i * 4
47
+
48
+ export const NB = 65_536; // lookup buckets: (word 0 - base) >> shift, the last one open-ended
49
+ export const FIELD = 40; // parameters per key: lo (f64), NaN code, width, place in its word, mask (u64)
50
+ export const DIGIT_BITS = 11; // radix sort of a partition's groups
51
+ const OP_OF = { add: X.f64add, min: X.f64min, max: X.f64max };
52
+ const IDENT = { add: 0, min: Infinity, max: -Infinity };
53
+
54
+ // field i's parameters, loaded once into locals
55
+ function fieldLocals(f, prm, at, fields) {
56
+ return fields.map((k, i) => {
57
+ const o = at + FIELD * i, x = { lo: f.local(F64), nan: f.local(I64), bits: f.local(I64), shift: f.local(I64), mask: f.local(I64) };
58
+ f.emit(get(prm), LF64(o), set(x.lo), get(prm), L64(o + 8), set(x.nan), get(prm), L64(o + 16), set(x.bits), get(prm), L64(o + 24), set(x.shift), get(prm), L64(o + 32), set(x.mask));
59
+ return x;
60
+ });
61
+ }
62
+
63
+ // ---- rows: count, then scatter (OP.UNITS) ------------------------------------
64
+ // rows(start, end, unit, prm) over the table's rows [start, end). prm: +0 the
65
+ // lookup (u16[NB]: bucket -> partition), +4 counters (u32 [units][P]; for
66
+ // the scatter, each unit's next record in each partition), +8 records, +12
67
+ // P, +16 base (u64), +24 shift, +32 each array's address, then (8-aligned)
68
+ // a FIELD block per key, then the scalars (f64).
69
+ // count: counters[unit][partition]++. scatter: the row's record [r words |
70
+ // the values to add up (f64), at their accumulators' places] at
71
+ // records + RB * counters[unit][partition]++ (rows in order, so every
72
+ // partition holds its rows in row order).
73
+ export function rowsLayout(nArrays, nKeys, nScalars) {
74
+ const FIELDS = align8(32 + 4 * nArrays), SC = FIELDS + FIELD * nKeys;
75
+ return { FIELDS, SC, bytes: SC + 8 * nScalars };
76
+ }
77
+
78
+ export function rowsModule({ t, arrays, encs, fields, W, vals, scalarNames, RB }, mode, shared) {
79
+ const ty = TY[t], lay = rowsLayout(arrays.length, fields.length, scalarNames.length);
80
+ const f = new Fn('k', [I32, I32, I32, I32], []);
81
+ const [start, stop, unit, prm] = [0, 1, 2, 3];
82
+ const env = { arrays: new Map(), scalars: new Map(), constVec: () => { throw new Error('vector constants are not used here'); } };
83
+ arrays.forEach((name, i) => env.arrays.set(name, { ptr: f.local(I32), sca: f.local(ty.val), enc: encs[i] ?? null }));
84
+ env.t = t; env.raw = f.local(ty.val); env.tmp = f.local(ty.val);
85
+ scalarNames.forEach((name, i) => {
86
+ const sca = f.local(ty.val);
87
+ f.emit(get(prm), LF64(lay.SC + 8 * i), t === 'f32' ? [0xb6] : [], set(sca)); // (f32 kernels: demoted)
88
+ env.scalars.set(name, { sca });
89
+ });
90
+ const em = new Emitter(f, t, env);
91
+ if (mode === 'scatter') em.prepare(vals.map((v) => v.ast));
92
+ const endP = f.local(I32), lut = f.local(I32), cnt = f.local(I32), recs = f.local(I32), a = f.local(I32), pos = f.local(I32), rec = f.local(I32);
93
+ const base = f.local(I64), sh = f.local(I64), d = f.local(I64), b = f.local(I64);
94
+ const v = f.local(F64);
95
+ const r = Array.from({ length: W }, () => f.local(I64));
96
+ const fl = fieldLocals(f, prm, lay.FIELDS, fields);
97
+ const bytesOf = (i) => (encs[i] ? PACKED_BYTES[encs[i].kind] : ty.bytes);
98
+
99
+ arrays.forEach((name, i) => f.emit(get(prm), L32(32 + 4 * i), get(start), i32c(bytesOf(i)), i32mul, i32add, set(env.arrays.get(name).ptr)));
100
+ const p0 = env.arrays.get(arrays[0]).ptr;
101
+ f.emit(get(stop), get(start), X.i32sub, i32c(bytesOf(0)), i32mul, get(p0), i32add, set(endP));
102
+ f.emit(get(prm), L32(0), set(lut), get(prm), L32(8), set(recs));
103
+ f.emit(get(prm), L32(4), get(unit), get(prm), L32(12), i32mul, i32c(2), X.i32shl, i32add, set(cnt));
104
+ f.emit(get(prm), L64(16), set(base), get(prm), L32(24), X.extu, set(sh));
105
+
106
+ const read = arrays.filter((name) => mode === 'scatter' || fields.some((k) => k.arr === name)); // (counting reads only the keys)
107
+ f.emit(block, loop, get(p0), get(endP), i32geu, brIf(1));
108
+ for (const name of read) {
109
+ const x = env.arrays.get(name);
110
+ if (x.enc) { packedScalar(f, t, x.enc, x.ptr, env.raw, env.tmp); f.emit(set(x.sca)); } else f.emit(get(x.ptr), ty.load, set(x.sca));
111
+ }
112
+ for (let w = 0; w < W; w++) f.emit(i64c(0), set(r[w]));
113
+ fields.forEach((k, i) => {
114
+ f.emit(get(env.arrays.get(k.arr).sca), t === 'f32' ? X.promote : [], set(v));
115
+ if (k.kind === 'int') { // r = (r << width) | (v - lo), NaN as its code
116
+ f.emit(get(r[k.word]), get(fl[i].bits), X.i64shl);
117
+ f.emit(get(fl[i].nan), get(v), get(fl[i].lo), X.f64sub, X.truncSatU64, get(v), get(v), X.f64ne, X.select);
118
+ f.emit(X.i64or, set(r[k.word]));
119
+ } else { // the whole word: float bits that sort as unsigned
120
+ f.emit(get(v), f64c(0), X.f64add, X.i64bits, set(b)); // (-0 as 0)
121
+ f.emit(get(b), i64c(-1), X.i64xor, get(b), i64c(SIGN), X.i64or, get(b), i64c(0), X.i64lts, X.select, set(b));
122
+ f.emit(i64c(-1), get(b), get(v), get(v), X.f64ne, X.select, set(r[k.word]));
123
+ }
124
+ });
125
+ // partition: lookup[min((r0 - base) >> shift, NB - 1)] (below base: bucket 0)
126
+ f.emit(i64c(0), get(r[0]), get(base), X.i64sub, get(r[0]), get(base), X.i64ltu, X.select, get(sh), X.i64shru, set(d));
127
+ f.emit(i64c(NB - 1), get(d), get(d), i64c(NB - 1), X.i64gtu, X.select, X.wrap, i32c(1), X.i32shl, get(lut), i32add, L16U(), i32c(2), X.i32shl, get(cnt), i32add, set(a));
128
+ if (mode === 'count') f.emit(get(a), get(a), L32(), i32c(1), i32add, S32());
129
+ else {
130
+ f.emit(get(a), get(a), L32(), tee(pos), i32c(1), i32add, S32());
131
+ f.emit(get(pos), i32c(RB), i32mul, get(recs), i32add, set(rec));
132
+ for (let w = 0; w < W; w++) f.emit(get(rec), get(r[w]), S64(8 * w));
133
+ for (const x of vals) {
134
+ f.emit(get(rec));
135
+ em.sca(x.ast);
136
+ if (t === 'f32') f.emit(X.promote);
137
+ f.emit(SF64(8 * W + 8 * x.slot));
138
+ }
139
+ }
140
+ for (const name of read) { const p = env.arrays.get(name).ptr; f.emit(get(p), i32c(bytesOf(arrays.indexOf(name))), i32add, set(p)); }
141
+ f.emit(br(0), end, end);
142
+ return buildModule([f], shared);
143
+ }
144
+
145
+ // ---- aggregate: one partition per task (OP.TASKS) ----------------------------
146
+ // aggregate(task, thread, prm): the partition order[task] (biggest first).
147
+ // prm: +0 records, +4 partition starts (u32[P + 1]), +8 groups out (u32[P];
148
+ // 0xffffffff: more than gMax groups, records left as they were), +12 scratch
149
+ // (64-aligned), +16 scratch bytes per thread, +20 capMax (a power of 2), +24
150
+ // gMax, +28 order (u32[P]).
151
+ // The partition's rows are added up in a hash table in this thread's
152
+ // scratch (rows in order: the same sums on any number of threads), with
153
+ // twice as many slots as rows, or capMax slots (then it holds up to gMax
154
+ // groups; more: the partition is split again). Its groups are then sorted by r (LSD
155
+ // radix, 11-bit digits, only the bits that differ) and copied, in order,
156
+ // over the partition's first records: a group is a record (its accumulators
157
+ // in the value places), and a partition has at least as many rows as groups.
158
+ // Scratch: slots i32[capMax] | groups [gMax * RB] | K u64[gMax] | K2 u64[gMax]
159
+ // | I u32[gMax] | I2 u32[gMax] | counts u32[2^11].
160
+ // spec: { W, RB, accs: [{ op: 'add' | 'min' | 'max', stored: bool (else adds 1) }] }
161
+ // branchFree: every row stores its group (found or new) without a branch on
162
+ // which it was, best when many rows start a new group (a branch would guess wrong).
163
+ export const scratchBytes = (capMax, gMax, RB) => Math.ceil((capMax * 4 + gMax * RB + gMax * 24 + (1 << DIGIT_BITS) * 4) / 64) * 64;
164
+
165
+ export function aggregateModule({ W, RB, accs }, branchFree, shared, countOnly = false) { // countOnly: groups[part] = G, no sort, records left as they are
166
+ const f = new Fn('k', [I32, I32, I32], []);
167
+ const [task, self, prm] = [0, 1, 2];
168
+ const L = (type) => f.local(type);
169
+ const part = L(I32), recs = L(I32), s = L(I32), e = L(I32), rows = L(I32), slots = L(I32), dense = L(I32), K = L(I32), K2 = L(I32), I = L(I32), I2 = L(I32), hist = L(I32);
170
+ const cap = L(I32), lg = L(I32), mask = L(I32), G = L(I32), gMax = L(I32), capMax = L(I32), rec = L(I32), stopRec = L(I32), h = L(I32), idx = L(I32), sa = L(I32), d = L(I32), tmp = L(I32), k = L(I32), dg = L(I32), at = L(I32), nb = L(I32), q = L(I32), lim = L(I32), empty = L(I32), cnt = L(I32);
171
+ const r = Array.from({ length: W }, () => L(I64)), rr = Array.from({ length: W }, () => L(I64));
172
+ const x = L(I64), y = L(I64), sh64 = L(I64), digitMask = L(I64), dbits = L(I32), passBits = L(I32), shiftW = L(I32);
173
+ const groupsOut = (val) => [...get(prm), ...L32(8), ...get(part), ...i32c(2), ...X.i32shl, ...i32add, ...val, ...S32()]; // groups[part] = val
174
+ const hashOf = (words) => { // h = the top lg bits of a mix of every word
175
+ f.emit(get(words[0]));
176
+ for (let w = 1; w < W; w++) f.emit(i64c(MIX2), X.i64mul, get(words[w]), X.i64xor);
177
+ f.emit(i64c(MIX), X.i64mul, i64c(64), get(lg), X.extu, X.i64sub, X.i64shru, X.wrap, set(h));
178
+ };
179
+ const setCap = () => { // cap = 1 << lg, slots cleared; lim = cap / 2, or gMax once the table can't grow
180
+ f.emit(i32c(1), get(lg), X.i32shl, set(cap), get(cap), i32c(1), X.i32sub, set(mask));
181
+ f.emit(get(slots), i32c(0xff), get(cap), i32c(2), X.i32shl, X.fill);
182
+ f.emit(get(cap), i32c(1), X.i32shru, set(lim), get(cap), get(capMax), i32geu, IF, get(gMax), set(lim), end);
183
+ };
184
+ const grow = () => { // twice the slots, every group rehashed (rare); full: give up on this partition
185
+ f.emit(get(cap), get(capMax), i32geu, IF, groupsOut(i32c(-1)), RETURN, end);
186
+ f.emit(get(lg), i32c(1), i32add, set(lg));
187
+ setCap();
188
+ f.emit(i32c(0), set(k), block, loop, get(k), get(G), i32geu, brIf(1));
189
+ for (let w = 0; w < W; w++) f.emit(get(k), i32c(RB), i32mul, get(dense), i32add, L64(8 * w), set(rr[w]));
190
+ hashOf(rr);
191
+ f.emit(block, loop, get(h), i32c(2), X.i32shl, get(slots), i32add, tee(sa), L32(), i32c(-1), X.i32ne, IF, get(h), i32c(1), i32add, get(mask), i32and, set(h), br(1), end, end, end);
192
+ f.emit(get(sa), get(k), S32());
193
+ f.emit(get(k), i32c(1), i32add, set(k), br(0), end, end);
194
+ };
195
+ const sameKey = () => { // the group at d has key r
196
+ f.emit(get(d), L64(0), get(r[0]), X.i64eq);
197
+ for (let w = 1; w < W; w++) f.emit(get(d), L64(8 * w), get(r[w]), X.i64eq, i32and);
198
+ };
199
+ const value = (ac, j) => (ac.stored ? [...get(rec), ...LF64(8 * W + 8 * j)] : f64c(1));
200
+
201
+ f.emit(get(prm), L32(28), get(task), i32c(2), X.i32shl, i32add, L32(), set(part));
202
+ f.emit(get(prm), L32(0), set(recs));
203
+ f.emit(get(prm), L32(4), get(part), i32c(2), X.i32shl, i32add, set(tmp), get(tmp), L32(0), set(s), get(tmp), L32(4), set(e));
204
+ f.emit(get(e), get(s), X.i32sub, set(rows));
205
+ f.emit(get(rows), X.i32eqz, IF, groupsOut(i32c(0)), RETURN, end);
206
+ f.emit(get(prm), L32(12), get(self), get(prm), L32(16), i32mul, i32add, set(slots));
207
+ f.emit(get(prm), L32(20), set(capMax), get(prm), L32(24), set(gMax));
208
+ f.emit(get(capMax), i32c(2), X.i32shl, get(slots), i32add, set(dense));
209
+ f.emit(get(gMax), i32c(RB), i32mul, get(dense), i32add, set(K), get(gMax), i32c(3), X.i32shl, get(K), i32add, set(K2));
210
+ f.emit(get(gMax), i32c(3), X.i32shl, get(K2), i32add, set(I), get(gMax), i32c(2), X.i32shl, get(I), i32add, set(I2), get(gMax), i32c(2), X.i32shl, get(I2), i32add, set(hist));
211
+ // room for every row to be a group (it never grows then; a partition's
212
+ // rows fit in cache anyway): lg = log2 of twice the rows, within [4, log2 capMax]
213
+ f.emit(i32c(32), get(rows), i32c(1), X.i32shl, i32c(1), X.i32sub, X.i32clz, X.i32sub, set(lg));
214
+ f.emit(get(lg), i32c(4), get(lg), i32c(4), X.i32gtu, X.select, set(lg));
215
+ f.emit(get(capMax), X.i32ctz, set(tmp), get(tmp), get(lg), get(lg), get(tmp), X.i32gtu, X.select, set(lg));
216
+ setCap();
217
+ f.emit(i32c(0), set(G));
218
+ f.emit(get(s), i32c(RB), i32mul, get(recs), i32add, set(rec), get(e), i32c(RB), i32mul, get(recs), i32add, set(stopRec));
219
+
220
+ // rows -> groups
221
+ f.emit(block, loop, get(rec), get(stopRec), i32geu, brIf(1));
222
+ if (branchFree) {
223
+ for (let w = 0; w < W; w++) f.emit(get(rec), L64(8 * w), set(r[w]));
224
+ hashOf(r);
225
+ // probe until an empty slot (the next group number) or the same key;
226
+ // a new group with the table half full: grow first, then probe again
227
+ // (dense has room for group G < lim: its key is compared, not trusted)
228
+ f.emit(block, loop);
229
+ f.emit(block, loop);
230
+ f.emit(get(h), i32c(2), X.i32shl, get(slots), i32add, tee(sa), L32(), tee(idx), i32c(-1), X.i32eq, set(empty));
231
+ f.emit(get(G), get(idx), get(empty), X.select, tee(idx), i32c(RB), i32mul, get(dense), i32add, set(d));
232
+ sameKey();
233
+ f.emit(get(empty), X.i32or, brIf(1));
234
+ f.emit(get(h), i32c(1), i32add, get(mask), i32and, set(h), br(0));
235
+ f.emit(end, end);
236
+ f.emit(get(empty), get(G), get(lim), i32geu, i32and, IF); grow(); hashOf(r); f.emit(br(1), end);
237
+ f.emit(br(1), end, end);
238
+ f.emit(get(sa), get(idx), S32());
239
+ for (let w = 0; w < W; w++) f.emit(get(d), get(r[w]), S64(8 * w));
240
+ f.emit(get(G), get(empty), i32add, set(G));
241
+ accs.forEach((ac, j) => { // a new group starts from the identity
242
+ const o = 8 * W + 8 * j;
243
+ f.emit(get(d), f64c(IDENT[ac.op]), get(d), LF64(o), get(empty), X.select, value(ac, j), OP_OF[ac.op], SF64(o));
244
+ });
245
+ } else {
246
+ for (let w = 0; w < W; w++) f.emit(get(rec), L64(8 * w), set(r[w]));
247
+ hashOf(r);
248
+ f.emit(block, loop); // probe
249
+ f.emit(get(h), i32c(2), X.i32shl, get(slots), i32add, tee(sa), L32(), tee(idx), i32c(-1), X.i32eq, IF);
250
+ f.emit(get(G), get(lim), i32geu, IF); grow(); hashOf(r); f.emit(br(2), end); // (then probe the bigger table)
251
+ f.emit(get(sa), get(G), S32(), get(G), i32c(RB), i32mul, get(dense), i32add, set(d), get(G), i32c(1), i32add, set(G));
252
+ for (let w = 0; w < W; w++) f.emit(get(d), get(r[w]), S64(8 * w));
253
+ accs.forEach((ac, j) => f.emit(get(d), f64c(IDENT[ac.op]), SF64(8 * W + 8 * j)));
254
+ f.emit(br(2), end); // a new group
255
+ f.emit(get(idx), i32c(RB), i32mul, get(dense), i32add, set(d));
256
+ sameKey();
257
+ f.emit(brIf(1));
258
+ f.emit(get(h), i32c(1), i32add, get(mask), i32and, set(h), br(0));
259
+ f.emit(end, end);
260
+ accs.forEach((ac, j) => {
261
+ const o = 8 * W + 8 * j;
262
+ f.emit(get(d), get(d), LF64(o), value(ac, j), OP_OF[ac.op], SF64(o));
263
+ });
264
+ }
265
+ f.emit(get(rec), i32c(RB), i32add, set(rec), br(0), end, end);
266
+ if (countOnly) { f.emit(groupsOut(get(G))); return buildModule([f], shared); }
267
+
268
+ // sort: I = 0..G-1; per key word, last first: K = that word of group I[k],
269
+ // then LSD radix passes over the bits that differ, moving (K, I) pairs
270
+ f.emit(i32c(0), set(k), block, loop, get(k), get(G), i32geu, brIf(1), addr4(I, k), get(k), S32(), get(k), i32c(1), i32add, set(k), br(0), end, end);
271
+ for (let w = W - 1; w >= 0; w--) {
272
+ f.emit(i64c(0), set(x), get(dense), L64(8 * w), set(y));
273
+ f.emit(i32c(0), set(k), block, loop, get(k), get(G), i32geu, brIf(1));
274
+ f.emit(get(k), i32c(3), X.i32shl, get(K), i32add, addr4(I, k), L32(), i32c(RB), i32mul, get(dense), i32add, L64(8 * w), tee(sh64), S64());
275
+ f.emit(get(x), get(sh64), get(y), X.i64xor, X.i64or, set(x));
276
+ f.emit(get(k), i32c(1), i32add, set(k), br(0), end, end);
277
+ // bits = 64 - clz(x); passes = ceil(bits / 11); bits per pass = ceil(bits / passes)
278
+ f.emit(i32c(64), get(x), X.i64clz, X.wrap, X.i32sub, set(dbits));
279
+ f.emit(get(dbits), i32c(DIGIT_BITS - 1), i32add, i32c(DIGIT_BITS), X.i32divu, set(tmp));
280
+ f.emit(get(tmp), IF, get(dbits), get(tmp), i32add, i32c(1), X.i32sub, get(tmp), X.i32divu, set(passBits), end);
281
+ f.emit(i32c(0), set(shiftW));
282
+ f.emit(block, loop, get(shiftW), get(dbits), i32geu, brIf(1));
283
+ f.emit(i32c(1), get(passBits), X.i32shl, set(nb), i64c(1), get(passBits), X.extu, X.i64shl, i64c(1), X.i64sub, set(digitMask), get(shiftW), X.extu, set(sh64));
284
+ f.emit(get(hist), i32c(0), get(nb), i32c(2), X.i32shl, X.fill);
285
+ const digitOf = () => f.emit(get(k), i32c(3), X.i32shl, get(K), i32add, L64(), set(y), get(y), get(sh64), X.i64shru, get(digitMask), X.i64and, X.wrap, set(dg));
286
+ f.emit(i32c(0), set(k), block, loop, get(k), get(G), i32geu, brIf(1));
287
+ digitOf();
288
+ f.emit(addr4(hist, dg), tee(tmp), get(tmp), L32(), i32c(1), i32add, S32());
289
+ f.emit(get(k), i32c(1), i32add, set(k), br(0), end, end);
290
+ f.emit(i32c(0), set(at), i32c(0), set(q), block, loop, get(q), get(nb), i32geu, brIf(1)); // counts -> first places
291
+ f.emit(addr4(hist, q), tee(tmp), L32(), set(cnt), get(tmp), get(at), S32(), get(at), get(cnt), i32add, set(at));
292
+ f.emit(get(q), i32c(1), i32add, set(q), br(0), end, end);
293
+ f.emit(i32c(0), set(k), block, loop, get(k), get(G), i32geu, brIf(1));
294
+ digitOf();
295
+ f.emit(addr4(hist, dg), tee(tmp), L32(), set(q), get(tmp), get(q), i32c(1), i32add, S32());
296
+ f.emit(get(q), i32c(3), X.i32shl, get(K2), i32add, get(y), S64());
297
+ f.emit(addr4(I2, q), addr4(I, k), L32(), S32());
298
+ f.emit(get(k), i32c(1), i32add, set(k), br(0), end, end);
299
+ f.emit(get(K), set(tmp), get(K2), set(K), get(tmp), set(K2), get(I), set(tmp), get(I2), set(I), get(tmp), set(I2));
300
+ f.emit(get(shiftW), get(passBits), i32add, set(shiftW), br(0), end, end);
301
+ }
302
+ // the groups, sorted, over the partition's first records
303
+ f.emit(i32c(0), set(k), get(s), i32c(RB), i32mul, get(recs), i32add, set(rec));
304
+ f.emit(block, loop, get(k), get(G), i32geu, brIf(1));
305
+ f.emit(get(rec), addr4(I, k), L32(), i32c(RB), i32mul, get(dense), i32add, i32c(RB), X.copy);
306
+ f.emit(get(rec), i32c(RB), i32add, set(rec), get(k), i32c(1), i32add, set(k), br(0), end, end);
307
+ f.emit(groupsOut(get(G)));
308
+ return buildModule([f], shared);
309
+ }
310
+
311
+ // ---- write: each partition's groups into the result columns (OP.TASKS) -------
312
+ // write(task, thread, prm): partition `task`. prm: +0 records, +4 partition
313
+ // starts, +8 groups per partition, +12 output offsets (u32[P]), +16 each
314
+ // key column's address, then each accumulator column's, then (8-aligned) a
315
+ // FIELD block per key. Keys are decoded from r; the columns are float64.
316
+ export function writeLayout(nKeys, J) {
317
+ const KO = 16, AO = KO + 4 * nKeys, FIELDS = align8(AO + 4 * J);
318
+ return { KO, AO, FIELDS, bytes: FIELDS + FIELD * nKeys };
319
+ }
320
+
321
+ export function writeModule({ W, RB, fields, J }, shared) {
322
+ const lay = writeLayout(fields.length, J);
323
+ const f = new Fn('k', [I32, I32, I32], []);
324
+ const [task, , prm] = [0, 1, 2];
325
+ const L = (type) => f.local(type);
326
+ const rec = L(I32), stopRec = L(I32), o8 = L(I32);
327
+ const r = Array.from({ length: W }, () => L(I64));
328
+ const code = L(I64);
329
+ const kp = fields.map(() => L(I32)), ap = Array.from({ length: J }, () => L(I32));
330
+ const fl = fieldLocals(f, prm, lay.FIELDS, fields);
331
+ f.emit(get(prm), L32(4), get(task), i32c(2), X.i32shl, i32add, L32(), i32c(RB), i32mul, get(prm), L32(0), i32add, set(rec));
332
+ f.emit(get(prm), L32(8), get(task), i32c(2), X.i32shl, i32add, L32(), i32c(RB), i32mul, get(rec), i32add, set(stopRec));
333
+ f.emit(get(prm), L32(12), get(task), i32c(2), X.i32shl, i32add, L32(), i32c(3), X.i32shl, set(o8));
334
+ fields.forEach((_, i) => f.emit(get(prm), L32(lay.KO + 4 * i), get(o8), i32add, set(kp[i])));
335
+ for (let j = 0; j < J; j++) f.emit(get(prm), L32(lay.AO + 4 * j), get(o8), i32add, set(ap[j]));
336
+ f.emit(block, loop, get(rec), get(stopRec), i32geu, brIf(1));
337
+ for (let w = 0; w < W; w++) f.emit(get(rec), L64(8 * w), set(r[w]));
338
+ fields.forEach((k, i) => {
339
+ f.emit(get(kp[i]));
340
+ if (k.kind === 'int') { // lo + code; the NaN code: NaN
341
+ f.emit(get(r[k.word]), get(fl[i].shift), X.i64shru, get(fl[i].mask), X.i64and, set(code));
342
+ f.emit(f64c(NaN), get(code), X.f64FromU64, get(fl[i].lo), X.f64add, get(code), get(fl[i].nan), X.i64eq, X.select);
343
+ } else { // back from sortable bits (top bit set: it was positive); all ones: NaN
344
+ f.emit(f64c(NaN), get(r[k.word]), i64c(SIGN), X.i64xor, get(r[k.word]), i64c(-1), X.i64xor, get(r[k.word]), i64c(0), X.i64lts, X.select, X.f64bits);
345
+ f.emit(get(r[k.word]), i64c(-1), X.i64eq, X.select);
346
+ }
347
+ f.emit(SF64(0), get(kp[i]), i32c(8), i32add, set(kp[i]));
348
+ });
349
+ for (let j = 0; j < J; j++) f.emit(get(ap[j]), get(rec), LF64(8 * W + 8 * j), SF64(0), get(ap[j]), i32c(8), i32add, set(ap[j]));
350
+ f.emit(get(rec), i32c(RB), i32add, set(rec), br(0), end, end);
351
+ return buildModule([f], shared);
352
+ }
353
+
354
+ // ---- split one partition again (OP.UNITS over its records) -------------------
355
+ // split(start, end, unit, prm) over records s0 + [start, end) of src, by
356
+ // one word of r (only once the words before it are the same in every
357
+ // record here): as rows(), from that word. count: counters[unit][part]++ and
358
+ // each unit's min and max of the word (unsigned; u64 [units][2]), so a word
359
+ // that is the same everywhere shows; copy: each record to dst at
360
+ // counters[unit][part]++. prm: +0 lookup, +4 counters, +8 src, +12 P, +16
361
+ // base (u64), +24 shift, +32 dst, +36 s0, +40 the word's byte offset in a
362
+ // record, +44 min/max (count).
363
+ export function splitModule(RB, mode, shared) {
364
+ const f = new Fn('k', [I32, I32, I32, I32], []);
365
+ const [start, stop, unit, prm] = [0, 1, 2, 3];
366
+ const L = (type) => f.local(type);
367
+ const rec = L(I32), endR = L(I32), lut = L(I32), cnt = L(I32), a = L(I32), pos = L(I32), dst = L(I32), wo = L(I32), mm = L(I32);
368
+ const base = L(I64), sh = L(I64), r0 = L(I64), d = L(I64), mn = L(I64), mx = L(I64);
369
+ f.emit(get(prm), L32(36), get(start), i32add, i32c(RB), i32mul, get(prm), L32(8), i32add, set(rec));
370
+ f.emit(get(prm), L32(36), get(stop), i32add, i32c(RB), i32mul, get(prm), L32(8), i32add, set(endR));
371
+ f.emit(get(prm), L32(0), set(lut), get(prm), L32(40), set(wo), get(prm), L32(32), set(dst));
372
+ f.emit(get(prm), L32(4), get(unit), get(prm), L32(12), i32mul, i32c(2), X.i32shl, i32add, set(cnt));
373
+ f.emit(get(prm), L64(16), set(base), get(prm), L32(24), X.extu, set(sh));
374
+ f.emit(i64c(-1), set(mn), i64c(0), set(mx));
375
+ f.emit(block, loop, get(rec), get(endR), i32geu, brIf(1));
376
+ f.emit(get(rec), get(wo), i32add, L64(), set(r0));
377
+ if (mode === 'count') f.emit(get(r0), get(mn), get(r0), get(mn), X.i64ltu, X.select, set(mn), get(r0), get(mx), get(r0), get(mx), X.i64gtu, X.select, set(mx));
378
+ f.emit(i64c(0), get(r0), get(base), X.i64sub, get(r0), get(base), X.i64ltu, X.select, get(sh), X.i64shru, set(d));
379
+ f.emit(i64c(NB - 1), get(d), get(d), i64c(NB - 1), X.i64gtu, X.select, X.wrap, i32c(1), X.i32shl, get(lut), i32add, L16U(), i32c(2), X.i32shl, get(cnt), i32add, set(a));
380
+ if (mode === 'count') f.emit(get(a), get(a), L32(), i32c(1), i32add, S32());
381
+ else {
382
+ f.emit(get(a), get(a), L32(), tee(pos), i32c(1), i32add, S32());
383
+ f.emit(get(pos), i32c(RB), i32mul, get(dst), i32add, get(rec), i32c(RB), X.copy);
384
+ }
385
+ f.emit(get(rec), i32c(RB), i32add, set(rec), br(0), end, end);
386
+ if (mode === 'count') f.emit(get(prm), L32(44), get(unit), i32c(4), X.i32shl, i32add, set(mm), get(mm), get(mn), S64(0), get(mm), get(mx), S64(8));
387
+ return buildModule([f], shared);
388
+ }