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,323 @@
1
+ // Code generators for table operations. Both are scalar loops (each row goes
2
+ // to a different place, which SIMD can't do), but they still fuse the filter
3
+ // and every aggregation into one pass over memory.
4
+
5
+ import { I32, F64, TY, Fn, buildModule, get, set, i32c, block, loop, end, br, brIf, i32add, i32mul, i32and, i32geu } from './wasm.mjs';
6
+ import { Emitter, packedScalar, PACKED_BYTES } from './fuse.mjs';
7
+
8
+ const F64_LOAD = [0x2b, 3, 0], F64_STORE = [0x39, 3, 0];
9
+ const F64_OP = { add: [0xa0], min: [0xa4], max: [0xa5] };
10
+ const F64_PROMOTE_F32 = [0xbb], F32_DEMOTE_F64 = [0xb6], I32_EQZ = [0x45], I32_SHL = [0x74];
11
+ const TRUNC_SAT_U = { f64: [0xfc, 0x03], f32: [0xfc, 0x01] }; // float -> unsigned int, no traps
12
+
13
+ // Locals for each array (current row value) and scalar, shared by both kernels.
14
+ function bindValues(f, t, arrayNames, firstScalarParam, scalarNames) {
15
+ const ty = TY[t];
16
+ const env = { arrays: new Map(), scalars: new Map(), constVec: () => { throw new Error('vector constants are not used here'); } };
17
+ arrayNames.forEach((name, i) => env.arrays.set(name, { ptr: i, sca: f.local(ty.val) }));
18
+ scalarNames.forEach((name, i) => {
19
+ const sca = f.local(ty.val);
20
+ f.emit(get(firstScalarParam + i), t === 'f32' ? F32_DEMOTE_F64 : [], set(sca));
21
+ env.scalars.set(name, { sca });
22
+ });
23
+ return env;
24
+ }
25
+
26
+ // packed columns (a.enc, see pack.mjs) are decoded as they are read
27
+ function loadRow(f, ty, env) {
28
+ for (const a of env.arrays.values()) {
29
+ if (a.enc) { packedScalar(f, env.t, a.enc, a.ptr, env.raw, env.tmp); f.emit(set(a.sca)); } else f.emit(get(a.ptr), ty.load, set(a.sca));
30
+ }
31
+ }
32
+
33
+ function advance(f, ty, ptrs, encs = []) {
34
+ ptrs.forEach((p, i) => f.emit(get(p), i32c(encs[i] ? PACKED_BYTES[encs[i].kind] : ty.bytes), i32add, set(p)));
35
+ }
36
+
37
+ // Group-by: for every row (that passes `pred`), add each aggregation's value
38
+ // into its group's accumulator. acc is float64, laid out as J blocks of K*C
39
+ // slots: each group has C copies that consecutive rows rotate through, so a
40
+ // run of rows in the same group doesn't wait on its own previous update.
41
+ // Signature: (array ptrs..., key ptr, n, accBase, K, scalars...)
42
+ // ranged: two more params (lo, hi) after K; only rows whose group is in
43
+ // [lo, hi) are added (see runGroupRange). encs[i] / keyEnc: packed columns.
44
+ export function generateGroupBy(items, pred, t, arrayNames, scalarNames, C = 1, shared = true, ranged = false, encs = [], keyEnc = null, lookup = false) {
45
+ // lookup: the key column holds join keys, not group numbers. Three more
46
+ // f64 params after the scalars (lutPtr, lutMin, lutSize): a row that passes
47
+ // `pred` takes group lut[key - lutMin] (an int32 array) when key - lutMin
48
+ // is a whole number in [0, lutSize) and that entry is >= 0; else it is
49
+ // skipped (no match: an inner join drops it). See join.mjs lookupGroupsG.
50
+ const log2C = Math.log2(C);
51
+ if (!Number.isInteger(log2C)) throw new Error('copies must be a power of two');
52
+ const ty = TY[t];
53
+ const A = arrayNames.length;
54
+ const [key, n, accBase, K, lo, hi] = [A, A + 1, A + 2, A + 3, A + 4, A + 5];
55
+ const extra = ranged ? 2 : 0;
56
+ const S = scalarNames.length;
57
+ const f = new Fn('k', [...Array(A).fill(I32), I32, I32, I32, I32, ...Array(extra).fill(I32), ...Array(S + (lookup ? 3 : 0)).fill(F64)], []);
58
+ const env = bindValues(f, t, arrayNames, A + 4 + extra, scalarNames);
59
+ arrayNames.forEach((name, i) => { env.arrays.get(name).enc = encs[i] ?? null; });
60
+ env.t = t; env.raw = f.local(ty.val); env.tmp = f.local(ty.val);
61
+ const em = new Emitter(f, t, env);
62
+ em.prepare([...items.map((it) => it.ast), pred]); // (the loop body is emitted once: no reset needed)
63
+ const stop = f.local(I32), idx = f.local(I32), addr = f.local(I32), copy = f.local(I32);
64
+ const bases = items.map(() => f.local(I32));
65
+ const lut = lookup ? f.local(I32) : -1, size = lookup ? f.local(I32) : -1, kv = lookup ? f.local(F64) : -1, gi = lookup ? f.local(I32) : -1;
66
+ if (lookup) {
67
+ const at = A + 4 + extra + S;
68
+ f.emit(get(at), [0xfc, 0x03], set(lut), get(at + 2), [0xfc, 0x03], set(size)); // (f64 -> u32)
69
+ }
70
+
71
+ // base_j = accBase + j * K * C * 8
72
+ items.forEach((_, j) => f.emit(get(K), i32c(j * C * 8), [0x6c], get(accBase), i32add, set(bases[j])));
73
+ f.emit(get(n), i32c(keyEnc ? PACKED_BYTES[keyEnc.kind] : ty.bytes), [0x6c], get(key), i32add, set(stop));
74
+
75
+ f.emit(block, loop, get(key), get(stop), i32geu, brIf(1));
76
+ if (keyEnc) packedScalar(f, t, keyEnc, key, env.raw, env.tmp); else f.emit(get(key), ty.load);
77
+ if (lookup) {
78
+ if (t === 'f32') f.emit(F64_PROMOTE_F32);
79
+ f.emit(get(A + 4 + extra + S + 1), [0xa1], set(kv)); // kv = key - lutMin
80
+ f.emit(block);
81
+ loadRow(f, ty, env);
82
+ if (pred) { em.scaMask(pred); f.emit(I32_EQZ, brIf(0)); }
83
+ f.emit(get(kv), [0xfc, 0x02], set(gi)); // gi = trunc_sat_s(kv)
84
+ f.emit(get(gi), get(size), [0x49], get(gi), [0xb7], get(kv), [0x61], [0x71], I32_EQZ, brIf(0)); // in range and whole (NaN: never)
85
+ f.emit(get(gi), i32c(2), I32_SHL, get(lut), i32add, [0x28, 2, 0], set(gi)); // gi = lut[gi]
86
+ f.emit(get(gi), i32c(0), [0x48], brIf(0)); // -1: the right table lacks this key
87
+ f.emit(get(gi), i32c(log2C), I32_SHL, get(copy), i32add, set(idx));
88
+ if (ranged) f.emit(get(idx), get(lo), [0x6b], get(hi), get(lo), [0x6b], i32geu, brIf(0));
89
+ } else {
90
+ // slot = key * C + copy
91
+ f.emit(TRUNC_SAT_U[t], i32c(log2C), I32_SHL, get(copy), i32add, set(idx));
92
+ f.emit(block);
93
+ if (ranged) f.emit(get(idx), get(lo), [0x6b], get(hi), get(lo), [0x6b], i32geu, brIf(0)); // (idx - lo) >= (hi - lo), unsigned: not ours (its values aren't even read)
94
+ loadRow(f, ty, env);
95
+ if (pred) { em.scaMask(pred); f.emit(I32_EQZ, brIf(0)); }
96
+ }
97
+ items.forEach((it, j) => {
98
+ const op = it.reduce === 'min' ? 'min' : it.reduce === 'max' ? 'max' : 'add';
99
+ f.emit(get(bases[j]), get(idx), i32c(3), I32_SHL, i32add, set(addr));
100
+ f.emit(get(addr), get(addr), F64_LOAD);
101
+ em.sca(it.ast);
102
+ if (t === 'f32') f.emit(F64_PROMOTE_F32);
103
+ f.emit(F64_OP[op], F64_STORE);
104
+ });
105
+ f.emit(end);
106
+ if (C > 1) f.emit(get(copy), i32c(1), i32add, i32c(C - 1), [0x71], set(copy));
107
+ advance(f, ty, [...env.arrays.values()].map((a) => a.ptr), [...env.arrays.values()].map((a) => a.enc));
108
+ advance(f, ty, [key], [keyEnc]);
109
+ f.emit(br(0), end, end);
110
+ return buildModule([f], shared);
111
+ }
112
+
113
+ // Compact: copy the rows that pass `pred` from source columns to destination
114
+ // columns. copyFrom[d] is the index (into arrayNames) that destination d copies.
115
+ // Signature: (src ptrs..., n, dst ptrs..., scalars...)
116
+ export function generateCompact(pred, t, arrayNames, copyFrom, scalarNames, shared = true, encs = [], rowNumbers = false) {
117
+ // rowNumbers: one more destination (uint32) gets each kept row's number,
118
+ // from how far the first source pointer has moved (its start comes as one
119
+ // more scalar after the others). encs[i]: packed sources, decoded as read.
120
+ const ty = TY[t];
121
+ const A = arrayNames.length, D = copyFrom.length + (rowNumbers ? 1 : 0);
122
+ const n = A;
123
+ const dst = Array.from({ length: D }, (_, d) => A + 1 + d);
124
+ const S = scalarNames.length;
125
+ const f = new Fn('k', [...Array(A).fill(I32), I32, ...Array(D).fill(I32), ...Array(S + (rowNumbers ? 1 : 0)).fill(F64)], []);
126
+ const env = bindValues(f, t, arrayNames, A + 1 + D, scalarNames);
127
+ arrayNames.forEach((name, i) => { env.arrays.get(name).enc = encs[i] ?? null; });
128
+ env.t = t; env.raw = f.local(ty.val); env.tmp = f.local(ty.val);
129
+ const em = new Emitter(f, t, env);
130
+ em.prepare([pred]);
131
+ const stop = f.local(I32), start0 = f.local(I32);
132
+ const srcLocals = arrayNames.map((name) => env.arrays.get(name));
133
+ const bytes0 = encs[0] ? PACKED_BYTES[encs[0].kind] : ty.bytes;
134
+ if (rowNumbers) f.emit(get(A + 1 + D + S), [0xfc, 0x03], set(start0)); // (f64 -> u32)
135
+
136
+ f.emit(get(n), i32c(bytes0), [0x6c], get(0), i32add, set(stop));
137
+ f.emit(block, loop, get(0), get(stop), i32geu, brIf(1));
138
+ loadRow(f, ty, env);
139
+ f.emit(block);
140
+ em.scaMask(pred);
141
+ f.emit(I32_EQZ, brIf(0));
142
+ copyFrom.forEach((src, d) => f.emit(get(dst[d]), get(srcLocals[src].sca), ty.store));
143
+ if (rowNumbers) f.emit(get(dst[D - 1]), get(0), get(start0), [0x6b], i32c(Math.log2(bytes0)), [0x76], [0x36, 2, 0]); // (ptr0 - start0) >> log2(bytes): i32.store
144
+ advance(f, ty, dst.slice(0, copyFrom.length));
145
+ if (rowNumbers) f.emit(get(dst[D - 1]), i32c(4), i32add, set(dst[D - 1]));
146
+ f.emit(end);
147
+ advance(f, ty, srcLocals.map((a) => a.ptr), encs);
148
+ f.emit(br(0), end, end);
149
+ return buildModule([f], shared);
150
+ }
151
+
152
+ // Hash numbering for group-by on any keys: every thread runs this over its
153
+ // own rows, all inserting into ONE table in shared memory. A slot holds -1
154
+ // (empty), -2 (being filled), -3 (the table ran out of room) or a group
155
+ // number. A new key claims an empty slot with compare-and-swap, takes the next
156
+ // group number from a shared counter, stores its key bits and then publishes
157
+ // the number; a thread that meets a slot being filled waits for it. Group
158
+ // numbers are taken from the counter in growing batches (1, 2, 4... 64 per
159
+ // unit: one shared counter hit per group would be a queue of every thread);
160
+ // numbers a unit doesn't use stay empty groups, which have no rows and are
161
+ // dropped. Keys are
162
+ // compared as bits after -0 becomes 0 and every NaN the same NaN.
163
+ // Signature: (key ptrs..., ids ptr, n, slotsBase, mask, keysBase, limit,
164
+ // counterAddr) -> 0, or 1 if more than `limit` groups showed up.
165
+ const I64 = 0x7e;
166
+ const op = (...b) => b;
167
+ const ATOMIC = { load: [0xfe, 0x10, 2, 0], store: [0xfe, 0x17, 2, 0], add: [0xfe, 0x1e, 2, 0], cmpxchg: [0xfe, 0x48, 2, 0] };
168
+ const i64c = (v) => { // signed LEB128 of a BigInt
169
+ const out = [0x42];
170
+ for (;;) {
171
+ const b = Number(v & 0x7fn);
172
+ v >>= 7n;
173
+ if ((v === 0n && !(b & 0x40)) || (v === -1n && b & 0x40)) { out.push(b); return out; }
174
+ out.push(b | 0x80);
175
+ }
176
+ };
177
+ const f64c = (v) => [0x44, ...new Uint8Array(Float64Array.of(v).buffer)];
178
+ const CANON_NAN = 0x7ff8000000000000n, MIX = -0x61c8864680b583ebn; // 0x9E3779B97F4A7C15 as signed
179
+
180
+ // Key bits of the current row (params 0..nk-1 point at it) and its first
181
+ // slot. Shared by the numbering and lookup kernels, so both hash alike.
182
+ function keyHash(f, t, nk, bits, v, h, mk, slot) {
183
+ const ty = TY[t];
184
+ f.emit(i64c(MIX), set(h));
185
+ for (let k = 0; k < nk; k++) {
186
+ f.emit(get(k), ty.load, t === 'f32' ? [0xbb] : [], f64c(0), op(0xa0), set(v)); // v = key + 0 (-0 -> 0)
187
+ f.emit(get(v), op(0xbd), i64c(CANON_NAN), get(v), get(v), op(0x61), op(0x1b), set(bits[k])); // NaN -> one NaN
188
+ f.emit(get(h), get(bits[k]), op(0x85), i64c(MIX), op(0x7e), set(h));
189
+ }
190
+ f.emit(get(h), get(h), i64c(32n), op(0x88), op(0x85), i64c(MIX), op(0x7e), set(h));
191
+ f.emit(get(h), get(h), i64c(29n), op(0x88), op(0x85), op(0xa7), get(mk), i32and, set(slot));
192
+ }
193
+
194
+ export function generateHashGroups(t, nk, shared = true) {
195
+ const ty = TY[t];
196
+ const ids = nk, n = nk + 1, S = nk + 2; // S..S+4: slotsBase, mask, keysBase, limit, counter (f64)
197
+ const f = new Fn('k', [...Array(nk).fill(I32), I32, I32, F64, F64, F64, F64, F64], [F64]);
198
+ const [sb, mk, kb, lim, ctr] = [0, 1, 2, 3, 4].map(() => f.local(I32));
199
+ const stop = f.local(I32), slot = f.local(I32), s = f.local(I32), g = f.local(I32), addr = f.local(I32), kaddr = f.local(I32);
200
+ const v = f.local(F64), h = f.local(I64), next = f.local(I32), last = f.local(I32), batch = f.local(I32);
201
+ const bits = Array.from({ length: nk }, () => f.local(I64));
202
+ const IF = [0x04, 0x40], RETURN = [0x0f];
203
+
204
+ [sb, mk, kb, lim, ctr].forEach((l, i) => f.emit(get(S + i), [0xfc, 0x03], set(l))); // i32.trunc_sat_f64_u
205
+ f.emit(get(n), i32c(ty.bytes), i32mul, get(ids), i32add, set(stop));
206
+ f.emit(i32c(0), set(next), i32c(0), set(last), i32c(1), set(batch)); // no numbers reserved yet
207
+ f.emit(block, loop, get(ids), get(stop), i32geu, brIf(1));
208
+ keyHash(f, t, nk, bits, v, h, mk, slot);
209
+ // probe
210
+ f.emit(block, loop);
211
+ f.emit(get(slot), i32c(2), op(0x74), get(sb), i32add, set(addr));
212
+ f.emit(get(addr), ATOMIC.load, set(s));
213
+ f.emit(get(s), i32c(-1), op(0x46), IF); // empty: try to claim it
214
+ f.emit(get(addr), i32c(-1), i32c(-2), ATOMIC.cmpxchg, i32c(-1), op(0x46), IF);
215
+ f.emit(get(next), get(last), op(0x46), IF, get(ctr), get(batch), ATOMIC.add, set(next), get(next), get(batch), i32add, set(last)); // reserve more
216
+ f.emit(get(batch), i32c(1), op(0x74), set(batch), i32c(64), get(batch), get(batch), i32c(64), op(0x4b), op(0x1b), set(batch), end); // batch = min(2 * batch, 64): select(64, batch, batch > 64)
217
+ f.emit(get(next), set(g), get(next), i32c(1), i32add, set(next));
218
+ f.emit(get(g), get(lim), i32geu, IF, get(addr), i32c(-3), ATOMIC.store, f64c(1), RETURN, end);
219
+ f.emit(get(g), i32c(nk * 8), i32mul, get(kb), i32add, set(kaddr));
220
+ for (let k = 0; k < nk; k++) f.emit(get(kaddr), get(bits[k]), [0x37, 3, ...uleb(k * 8)]); // i64.store
221
+ f.emit(get(addr), get(g), ATOMIC.store, br(3)); // publish; found
222
+ f.emit(end, br(1), end); // lost the race: look at the slot again
223
+ f.emit(get(s), i32c(-2), op(0x46), brIf(0)); // being filled: wait
224
+ f.emit(get(s), i32c(-3), op(0x46), IF, f64c(1), RETURN, end);
225
+ f.emit(get(s), i32c(nk * 8), i32mul, get(kb), i32add, set(kaddr));
226
+ f.emit(block);
227
+ for (let k = 0; k < nk; k++) f.emit(get(kaddr), [0x29, 3, ...uleb(k * 8)], get(bits[k]), op(0x52), brIf(0)); // i64.load, i64.ne
228
+ f.emit(get(s), set(g), br(2)); // same key
229
+ f.emit(end);
230
+ f.emit(get(slot), i32c(1), i32add, get(mk), i32and, set(slot), br(0));
231
+ f.emit(end, end);
232
+ // group number out, next row
233
+ f.emit(get(ids), get(g), t === 'f32' ? op(0xb3) : op(0xb8), ty.store);
234
+ advance(f, ty, [...Array(nk).keys(), ids]);
235
+ f.emit(br(0), end, end, f64c(0));
236
+ return buildModule([f], shared);
237
+ }
238
+
239
+ function uleb(n) {
240
+ const out = [];
241
+ do { let b = n & 0x7f; n >>>= 7; if (n) b |= 0x80; out.push(b); } while (n);
242
+ return out;
243
+ }
244
+
245
+ // Lookup in a table filled by generateHashGroups (no inserts): out[i] = the
246
+ // group number of row i's key, or -1 if no row of the build side has it. A
247
+ // key with a NaN part never matches (as NULL in SQL).
248
+ // Signature: (key ptrs..., out ptr (int32), n, slotsBase, mask, keysBase).
249
+ export function generateHashProbe(t, nk, shared = true) {
250
+ const ty = TY[t];
251
+ const out = nk, n = nk + 1, [sb, mk, kb] = [nk + 2, nk + 3, nk + 4];
252
+ const f = new Fn('k', [...Array(nk).fill(I32), I32, I32, I32, I32, I32], []);
253
+ const stop = f.local(I32), slot = f.local(I32), s = f.local(I32), g = f.local(I32), kaddr = f.local(I32);
254
+ const v = f.local(F64), h = f.local(I64);
255
+ const bits = Array.from({ length: nk }, () => f.local(I64));
256
+ f.emit(get(n), i32c(4), i32mul, get(out), i32add, set(stop));
257
+ f.emit(block, loop, get(out), get(stop), i32geu, brIf(1));
258
+ keyHash(f, t, nk, bits, v, h, mk, slot);
259
+ f.emit(i32c(-1), set(g));
260
+ f.emit(block); // -> found or not
261
+ for (let k = 0; k < nk; k++) f.emit(get(bits[k]), i64c(CANON_NAN), op(0x51), brIf(0)); // NaN: no match
262
+ f.emit(loop);
263
+ f.emit(get(slot), i32c(2), op(0x74), get(sb), i32add, [0x28, 2, 0], set(s)); // i32.load
264
+ f.emit(get(s), i32c(-1), op(0x46), brIf(1)); // empty slot: not there
265
+ f.emit(get(s), i32c(nk * 8), i32mul, get(kb), i32add, set(kaddr));
266
+ f.emit(block);
267
+ for (let k = 0; k < nk; k++) f.emit(get(kaddr), [0x29, 3, ...uleb(k * 8)], get(bits[k]), op(0x52), brIf(0));
268
+ f.emit(get(s), set(g), br(2)); // same key
269
+ f.emit(end);
270
+ f.emit(get(slot), i32c(1), i32add, get(mk), i32and, set(slot), br(0));
271
+ f.emit(end, end);
272
+ f.emit(get(out), get(g), [0x36, 2, 0]); // i32.store
273
+ for (let k = 0; k < nk; k++) f.emit(get(k), i32c(ty.bytes), i32add, set(k));
274
+ f.emit(get(out), i32c(4), i32add, set(out), br(0), end, end);
275
+ return buildModule([f], shared);
276
+ }
277
+
278
+ // Distinct values per group, for whole numbers in a small range: byte
279
+ // map[g * width + (v - lo)] = 1 for each row's group g and value v (missing
280
+ // values skipped). Every thread writes into the one map; writing 1 where it
281
+ // may already be 1 is safe, and a byte already set is only read.
282
+ // Signature: (g ptr, v ptr, n, map, width, lo, hasG, step) -> values off the step (hasG 0: one group, g unread;
283
+ // step: read only by the stepped kernel, which marks (v - lo) / step and counts the values it doesn't divide).
284
+ export function generateMarkPairs(t, shared = true, stepped = false) { // stepped: (v - lo) / step
285
+ const ty = TY[t];
286
+ const f = new Fn('k', [I32, I32, I32, F64, F64, F64, F64, F64], [F64]);
287
+ const [gp, vp, n, mapP, widthP, lo, hasG, step] = [0, 1, 2, 3, 4, 5, 6, 7];
288
+ const map = f.local(I32), width = f.local(I32), stop = f.local(I32), addr = f.local(I32), v = f.local(F64), d = f.local(F64), q = f.local(F64), bad = f.local(F64);
289
+ const IF = [0x04, 0x40], LOAD8_U = [0x2d, 0, 0], STORE8 = [0x3a, 0, 0];
290
+ f.emit(get(mapP), TRUNC_SAT_U.f64, set(map), get(widthP), TRUNC_SAT_U.f64, set(width));
291
+ f.emit(get(n), i32c(ty.bytes), i32mul, get(vp), i32add, set(stop));
292
+ f.emit(block, loop, get(vp), get(stop), i32geu, brIf(1));
293
+ f.emit(get(vp), ty.load, ty.toF64, set(v));
294
+ f.emit(get(v), get(v), [0x61], IF); // present (not NaN)
295
+ if (!stepped) f.emit(get(v), get(lo), [0xa1], TRUNC_SAT_U.f64, get(map), i32add, set(addr)); // map + (v - lo)
296
+ else { // q = trunc((v - lo) / step) (in f64, exact below 2^53); a value off the step: bad + 1
297
+ f.emit(get(v), get(lo), [0xa1], set(d), get(d), get(step), [0xa3], [0x9d], set(q));
298
+ f.emit(get(q), get(step), [0xa2], get(d), [0x62], IF, get(bad), [0x44, 0, 0, 0, 0, 0, 0, 0xf0, 0x3f], [0xa0], set(bad), end);
299
+ f.emit(get(q), TRUNC_SAT_U.f64, get(map), i32add, set(addr));
300
+ }
301
+ f.emit(get(hasG), [0x44, 0, 0, 0, 0, 0, 0, 0, 0], [0x62], IF, get(gp), ty.load, ty.toF64, TRUNC_SAT_U.f64, get(width), i32mul, get(addr), i32add, set(addr), end);
302
+ f.emit(get(addr), LOAD8_U, I32_EQZ, IF, get(addr), i32c(1), STORE8, end);
303
+ f.emit(end);
304
+ f.emit(get(vp), i32c(ty.bytes), i32add, set(vp), get(gp), i32c(ty.bytes), i32add, set(gp), br(0), end, end);
305
+ f.emit(get(bad)); // (0 unless stepped)
306
+ return buildModule([f], shared);
307
+ }
308
+
309
+ // Keys stored side by side by the hash table (src[g * nk + k], float64) out
310
+ // to one column: dst[i] = src[keep[i] * nk + k] for the rows of a unit
311
+ // (OP.UNITS). Signature: (start, stop, unit, src, keep (uint32), dst, nk, k).
312
+ export function generateStridedKeys(shared = true) {
313
+ const f = new Fn('k', [I32, I32, I32, I32, I32, I32, I32, I32], []);
314
+ const [start, stop, , src, keep, dst, nk, k] = [0, 1, 2, 3, 4, 5, 6, 7];
315
+ const i = f.local(I32);
316
+ f.emit(get(start), set(i));
317
+ f.emit(block, loop, get(i), get(stop), i32geu, brIf(1));
318
+ f.emit(get(i), i32c(3), I32_SHL, get(dst), i32add);
319
+ f.emit(get(i), i32c(2), I32_SHL, get(keep), i32add, [0x28, 2, 0], get(nk), i32mul, get(k), i32add, i32c(3), I32_SHL, get(src), i32add, F64_LOAD);
320
+ f.emit(F64_STORE);
321
+ f.emit(get(i), i32c(1), i32add, set(i), br(0), end, end);
322
+ return buildModule([f], shared);
323
+ }