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.
- package/CHANGELOG.md +89 -0
- package/README.md +828 -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 +2247 -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,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
|
+
}
|