vectrojs 0.0.1 → 0.1.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/CHANGELOG.md +71 -0
- package/README.md +821 -4
- package/bin/vectro.mjs +82 -0
- package/dist/vectro.wasm +0 -0
- package/mcp/server.mjs +288 -0
- package/package.json +84 -6
- package/src/arrow.mjs +905 -0
- package/src/bitpack.mjs +135 -0
- package/src/browser.mjs +129 -0
- package/src/bytes.mjs +60 -0
- package/src/csv-kernel.mjs +205 -0
- package/src/csv.mjs +270 -0
- package/src/dataset.mjs +1763 -0
- package/src/dates.mjs +139 -0
- package/src/emitter.mjs +109 -0
- package/src/engine.mjs +881 -0
- package/src/files-browser.mjs +104 -0
- package/src/files-node.mjs +59 -0
- package/src/frames.mjs +191 -0
- package/src/fuse.mjs +512 -0
- package/src/interp.mjs +305 -0
- package/src/join.mjs +534 -0
- package/src/kernels.mjs +312 -0
- package/src/ndjson.mjs +136 -0
- package/src/node.mjs +120 -0
- package/src/pack.mjs +114 -0
- package/src/parallel-sort.mjs +99 -0
- package/src/partition-kernels.mjs +388 -0
- package/src/partition.mjs +395 -0
- package/src/plan.mjs +551 -0
- package/src/portable.mjs +21 -0
- package/src/rank-kernels.mjs +81 -0
- package/src/rows.mjs +91 -0
- package/src/serve/catalog.mjs +148 -0
- package/src/serve/flight.mjs +270 -0
- package/src/serve/formats.mjs +87 -0
- package/src/serve/http.mjs +182 -0
- package/src/serve/index.mjs +9 -0
- package/src/serve/odata.mjs +291 -0
- package/src/serve/pg.mjs +588 -0
- package/src/serve/pgcatalog.mjs +220 -0
- package/src/serve/pgtypes.mjs +199 -0
- package/src/sketch.mjs +75 -0
- package/src/sort-kernels.mjs +117 -0
- package/src/sort.mjs +102 -0
- package/src/split-worker-core.mjs +252 -0
- package/src/split-worker-node.mjs +8 -0
- package/src/split-worker.mjs +7 -0
- package/src/split.mjs +741 -0
- package/src/sql.mjs +1558 -0
- package/src/sqltext.mjs +132 -0
- package/src/static-kernels.mjs +164 -0
- package/src/stream-worker.mjs +553 -0
- package/src/table-kernels.mjs +323 -0
- package/src/table.mjs +2206 -0
- package/src/textmap.mjs +159 -0
- package/src/vectors.mjs +138 -0
- package/src/wasm.mjs +254 -0
- package/src/window.mjs +160 -0
- package/src/worker-browser.mjs +21 -0
- package/src/worker-loop.mjs +106 -0
- package/src/worker.mjs +22 -0
- 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
|
+
}
|