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
package/src/sqltext.mjs
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
// SQL's text functions as plain JavaScript on one set of values (sql.mjs
|
|
2
|
+
// runs them through Table._textmap: once per distinct value, not per row).
|
|
3
|
+
// Positions count characters (code points) from 1, as in DuckDB; text
|
|
4
|
+
// missing is '' (as everywhere in Vectro), numbers missing are NaN.
|
|
5
|
+
import { parseDateText } from './dates.mjs';
|
|
6
|
+
|
|
7
|
+
// a value as text: numbers shortest (whole ones without ".0"), dates as
|
|
8
|
+
// 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS[.mmm]'
|
|
9
|
+
export function toText(v, kind) {
|
|
10
|
+
if (kind === 'text') return v;
|
|
11
|
+
if (v !== v || v === null || v === undefined) return '';
|
|
12
|
+
if (kind === 'date') {
|
|
13
|
+
const iso = new Date(v).toISOString();
|
|
14
|
+
if (iso.endsWith('T00:00:00.000Z')) return iso.slice(0, 10);
|
|
15
|
+
return iso.slice(0, iso.endsWith('.000Z') ? 19 : 23).replace('T', ' ');
|
|
16
|
+
}
|
|
17
|
+
if (Number.isInteger(v) && Math.abs(v) < 1e21) return v === 0 ? '0' : String(v);
|
|
18
|
+
return String(Number(v.toPrecision(15)));
|
|
19
|
+
}
|
|
20
|
+
export const isNull = (v, kind) => (kind === 'text' ? v === '' : v !== v);
|
|
21
|
+
export const textToNumber = (s) => (s.trim() === '' ? NaN : Number(s.trim()));
|
|
22
|
+
export const textToDate = (s) => (s.trim() === '' ? NaN : parseDateText(s.trim()));
|
|
23
|
+
|
|
24
|
+
const chars = (s) => Array.from(s);
|
|
25
|
+
function trimSet(s, set, start, end) {
|
|
26
|
+
const cs = chars(s), drop = new Set(chars(set));
|
|
27
|
+
let a = 0, b = cs.length;
|
|
28
|
+
if (start) while (a < b && drop.has(cs[a])) a++;
|
|
29
|
+
if (end) while (b > a && drop.has(cs[b - 1])) b--;
|
|
30
|
+
return cs.slice(a, b).join('');
|
|
31
|
+
}
|
|
32
|
+
// DuckDB's substring: start 1 is the first character, 0 the place before
|
|
33
|
+
// it, negative counts from the end; a negative length takes the characters before
|
|
34
|
+
function substring(s, start, len) {
|
|
35
|
+
const cs = chars(s), n = cs.length;
|
|
36
|
+
const at = start > 0 ? start - 1 : start === 0 ? -1 : n + start;
|
|
37
|
+
let a = at, b = len === undefined ? Infinity : at + len;
|
|
38
|
+
if (len !== undefined && len < 0) { a = at + len; b = at; }
|
|
39
|
+
return cs.slice(Math.max(0, a), Math.max(0, Math.min(n, b))).join('');
|
|
40
|
+
}
|
|
41
|
+
const regexes = new Map();
|
|
42
|
+
function regex(pattern, flags = '') {
|
|
43
|
+
const key = `${flags}/${pattern}`;
|
|
44
|
+
let re = regexes.get(key);
|
|
45
|
+
if (!re) {
|
|
46
|
+
if (regexes.size > 1000) regexes.clear();
|
|
47
|
+
try { re = new RegExp(pattern, flags.replace(/[^gimsu]/g, '') + 'u'); } catch (err) { throw new SyntaxError(`vectro SQL: bad regular expression /${pattern}/: ${err.message}`); }
|
|
48
|
+
regexes.set(key, re);
|
|
49
|
+
}
|
|
50
|
+
re.lastIndex = 0;
|
|
51
|
+
return re;
|
|
52
|
+
}
|
|
53
|
+
const DAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
54
|
+
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
|
55
|
+
function strftime(t, fmt) {
|
|
56
|
+
const d = new Date(t), p2 = (x) => String(x).padStart(2, '0');
|
|
57
|
+
const Y = d.getUTCFullYear(), M = d.getUTCMonth(), D = d.getUTCDate(), h = d.getUTCHours(), w = d.getUTCDay();
|
|
58
|
+
const yday = Math.floor((Date.UTC(Y, M, D) - Date.UTC(Y, 0, 1)) / 86_400_000) + 1;
|
|
59
|
+
return fmt.replace(/%([a-zA-Z%])/g, (m, c) => {
|
|
60
|
+
switch (c) {
|
|
61
|
+
case 'Y': return String(Y);
|
|
62
|
+
case 'y': return p2(Y % 100);
|
|
63
|
+
case 'm': return p2(M + 1);
|
|
64
|
+
case 'd': return p2(D);
|
|
65
|
+
case 'e': return String(D);
|
|
66
|
+
case 'H': return p2(h);
|
|
67
|
+
case 'I': return p2(h % 12 || 12);
|
|
68
|
+
case 'p': return h < 12 ? 'AM' : 'PM';
|
|
69
|
+
case 'M': return p2(d.getUTCMinutes());
|
|
70
|
+
case 'S': return p2(d.getUTCSeconds());
|
|
71
|
+
case 'f': return String(d.getUTCMilliseconds() * 1000).padStart(6, '0');
|
|
72
|
+
case 'j': return String(yday).padStart(3, '0');
|
|
73
|
+
case 'a': return DAYS[w].slice(0, 3);
|
|
74
|
+
case 'A': return DAYS[w];
|
|
75
|
+
case 'w': return String(w);
|
|
76
|
+
case 'u': return String(w || 7);
|
|
77
|
+
case 'b': return MONTHS[M].slice(0, 3);
|
|
78
|
+
case 'B': return MONTHS[M];
|
|
79
|
+
case 'F': return `${Y}-${p2(M + 1)}-${p2(D)}`;
|
|
80
|
+
case 'T': return `${p2(h)}:${p2(d.getUTCMinutes())}:${p2(d.getUTCSeconds())}`;
|
|
81
|
+
case '%': return '%';
|
|
82
|
+
default: return m;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// name -> { args: types ('s' text, 'n' number, 't' time), min (args
|
|
88
|
+
// needed), rest (type of any further ones), out: 'text' | 'number', skip
|
|
89
|
+
// (missing values are left out instead of making the answer missing), f }
|
|
90
|
+
export const TEXT_FUNCTIONS = {
|
|
91
|
+
UPPER: { args: 's', out: 'text', f: (s) => s.toUpperCase() },
|
|
92
|
+
LOWER: { args: 's', out: 'text', f: (s) => s.toLowerCase() },
|
|
93
|
+
LENGTH: { args: 's', out: 'number', f: (s) => chars(s).length },
|
|
94
|
+
OCTET_LENGTH: { args: 's', out: 'number', f: (s) => new TextEncoder().encode(s).length },
|
|
95
|
+
TRIM: { args: 'ss', min: 1, out: 'text', f: (s, set = ' ') => trimSet(s, set, true, true) },
|
|
96
|
+
LTRIM: { args: 'ss', min: 1, out: 'text', f: (s, set = ' ') => trimSet(s, set, true, false) },
|
|
97
|
+
RTRIM: { args: 'ss', min: 1, out: 'text', f: (s, set = ' ') => trimSet(s, set, false, true) },
|
|
98
|
+
SUBSTRING: { args: 'snn', min: 2, out: 'text', f: substring },
|
|
99
|
+
LEFT: { args: 'sn', out: 'text', f: (s, k) => { const cs = chars(s); return cs.slice(0, k < 0 ? Math.max(0, cs.length + k) : k).join(''); } },
|
|
100
|
+
RIGHT: { args: 'sn', out: 'text', f: (s, k) => { const cs = chars(s); return k < 0 ? cs.slice(Math.min(cs.length, -k)).join('') : k === 0 ? '' : cs.slice(-k).join(''); } },
|
|
101
|
+
REPLACE: { args: 'sss', out: 'text', f: (s, a, b) => (a === '' ? s : s.split(a).join(b)) },
|
|
102
|
+
REVERSE: { args: 's', out: 'text', f: (s) => chars(s).reverse().join('') },
|
|
103
|
+
REPEAT: { args: 'sn', out: 'text', f: (s, k) => (k > 0 ? s.repeat(Math.floor(k)) : '') },
|
|
104
|
+
LPAD: { args: 'sns', min: 2, out: 'text', f: (s, k, fill = ' ') => { const cs = chars(s); if (cs.length >= k) return cs.slice(0, Math.max(0, k)).join(''); const f = chars(fill); if (!f.length) return s; const pad = []; while (pad.length < k - cs.length) pad.push(f[pad.length % f.length]); return pad.join('') + s; } },
|
|
105
|
+
RPAD: { args: 'sns', min: 2, out: 'text', f: (s, k, fill = ' ') => { const cs = chars(s); if (cs.length >= k) return cs.slice(0, Math.max(0, k)).join(''); const f = chars(fill); if (!f.length) return s; const pad = []; while (pad.length < k - cs.length) pad.push(f[pad.length % f.length]); return s + pad.join(''); } },
|
|
106
|
+
SPLIT_PART: { args: 'ssn', out: 'text', f: (s, d, k) => { const parts = d === '' ? [s] : s.split(d); const i = k > 0 ? k - 1 : k < 0 ? parts.length + k : -1; return i >= 0 && i < parts.length ? parts[i] : ''; } },
|
|
107
|
+
STRPOS: { args: 'ss', out: 'number', f: (s, sub) => { const i = s.indexOf(sub); return i < 0 ? 0 : chars(s.slice(0, i)).length + 1; } },
|
|
108
|
+
STARTS_WITH: { args: 'ss', out: 'number', f: (s, p) => (s.startsWith(p) ? 1 : 0) },
|
|
109
|
+
ENDS_WITH: { args: 'ss', out: 'number', f: (s, p) => (s.endsWith(p) ? 1 : 0) },
|
|
110
|
+
CONTAINS: { args: 'ss', out: 'number', f: (s, p) => (s.includes(p) ? 1 : 0) },
|
|
111
|
+
ASCII: { args: 's', out: 'number', f: (s) => s.codePointAt(0) ?? NaN },
|
|
112
|
+
CHR: { args: 'n', out: 'text', f: (k) => (k >= 0 && k <= 0x10ffff ? String.fromCodePoint(Math.floor(k)) : '') },
|
|
113
|
+
REGEXP_MATCHES: { args: 'sss', min: 2, out: 'number', f: (s, p, fl) => (regex(p, fl).test(s) ? 1 : 0) },
|
|
114
|
+
REGEXP_REPLACE: { args: 'ssss', min: 3, out: 'text', f: (s, p, rep, fl = '') => s.replace(regex(p, fl), rep.replace(/\$/g, '$$$$').replace(/\\(\d)/g, '$$$1')) },
|
|
115
|
+
REGEXP_EXTRACT: { args: 'ssn', min: 2, out: 'text', f: (s, p, g = 0) => regex(p).exec(s)?.[g] ?? '' },
|
|
116
|
+
STRFTIME: { args: 'ts', out: 'text', f: strftime },
|
|
117
|
+
CONCAT: { args: '', rest: 's', out: 'text', skip: true, f: (...xs) => xs.filter((x) => x !== null).join('') },
|
|
118
|
+
CONCAT_WS: { args: 's', rest: 's', out: 'text', skip: true, f: (sep, ...xs) => (sep === null ? '' : xs.filter((x) => x !== null).join(sep)) },
|
|
119
|
+
'||': { args: '', rest: 's', out: 'text', f: (...xs) => xs.join('') },
|
|
120
|
+
FORMAT: { args: 's', rest: 's', out: 'text', skip: true, f: (fmt, ...xs) => { // PostgreSQL's format(): %s, %I (a name, quoted when needed), %L (a literal)
|
|
121
|
+
if (fmt === null) return '';
|
|
122
|
+
let k = 0;
|
|
123
|
+
return fmt.replace(/%([%sIL])/g, (m, c) => {
|
|
124
|
+
if (c === '%') return '%';
|
|
125
|
+
const x = xs[k++];
|
|
126
|
+
if (c === 's') return x ?? '';
|
|
127
|
+
if (c === 'I') return /^[a-z_][a-z0-9_$]*$/.test(x ?? '') ? x : `"${String(x ?? '').replaceAll('"', '""')}"`;
|
|
128
|
+
return x === null || x === undefined ? 'NULL' : `'${String(x).replaceAll("'", "''")}'`;
|
|
129
|
+
});
|
|
130
|
+
} },
|
|
131
|
+
};
|
|
132
|
+
for (const [alias, name] of Object.entries({ UCASE: 'UPPER', LCASE: 'LOWER', LEN: 'LENGTH', CHAR_LENGTH: 'LENGTH', CHARACTER_LENGTH: 'LENGTH', STRLEN: 'OCTET_LENGTH', SUBSTR: 'SUBSTRING', INSTR: 'STRPOS', POSITION: 'STRPOS', PREFIX: 'STARTS_WITH', SUFFIX: 'ENDS_WITH', REGEXP_LIKE: 'REGEXP_MATCHES', STR_SPLIT_PART: 'SPLIT_PART' })) TEXT_FUNCTIONS[alias] = TEXT_FUNCTIONS[name];
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
// The engine's fixed WebAssembly module: the built-in kernels plus the
|
|
2
|
+
// building blocks of the vectorized interpreter (elementwise math,
|
|
3
|
+
// comparisons, select, widening sums, group-by scatter, compaction).
|
|
4
|
+
//
|
|
5
|
+
// It never changes, so it can be built ahead of time (scripts/build-wasm.mjs)
|
|
6
|
+
// for places that forbid compiling WebAssembly at runtime, like Cloudflare
|
|
7
|
+
// Workers. With it, nothing else needs code generation.
|
|
8
|
+
|
|
9
|
+
import { I32, F64, V128, TY, Fn, loops, buildModule, kernelFns, get, set, i32c, simd, vload, vstore, block, loop, end, br, brIf, i32add, i32geu } from './wasm.mjs';
|
|
10
|
+
import { VEC, SCA } from './fuse.mjs';
|
|
11
|
+
|
|
12
|
+
const V128_AND = 0x4e, BITSELECT = 0x52, F64X2_PROMOTE_LOW = 0x5f, F64X2_ADD = 0xf0, SHUFFLE = 0x0d;
|
|
13
|
+
const SELECT = [0x1b], I32_SUB = [0x6b], I32_SHR_U = [0x76], I32_SHL = [0x74], I32_MUL = [0x6c];
|
|
14
|
+
const F64_LOAD = [0x2b, 3, 0], F64_STORE = [0x39, 3, 0], F64_PROMOTE_F32 = [0xbb];
|
|
15
|
+
const F64_OP = { add: [0xa0], min: [0xa4], max: [0xa5] };
|
|
16
|
+
const TRUNC_SAT_U = { f64: [0xfc, 0x03], f32: [0xfc, 0x01] };
|
|
17
|
+
const HIGH_HALF = [8, 9, 10, 11, 12, 13, 14, 15, 0, 1, 2, 3, 4, 5, 6, 7];
|
|
18
|
+
const U = 2;
|
|
19
|
+
|
|
20
|
+
export const BINARY_OPS = ['add', 'sub', 'mul', 'div', 'min', 'max'];
|
|
21
|
+
export const COMPARE_OPS = ['eq', 'ne', 'lt', 'gt', 'le', 'ge'];
|
|
22
|
+
export const UNARY_OPS = ['neg', 'abs', 'sqrt', 'floor', 'ceil'];
|
|
23
|
+
|
|
24
|
+
// dst = a OP b; comparisons give 1.0 or 0.0. (dst, a, b, n)
|
|
25
|
+
function vbinary(t, op) {
|
|
26
|
+
const ty = TY[t], cmp = COMPARE_OPS.includes(op);
|
|
27
|
+
const f = new Fn(`v${op}_${t}`, [I32, I32, I32, I32], []);
|
|
28
|
+
const [dst, a, b, n] = [0, 1, 2, 3];
|
|
29
|
+
const one = f.local(V128);
|
|
30
|
+
f.emit(ty.cnst(1), ty.vsplat, set(one));
|
|
31
|
+
loops(f, ty, {
|
|
32
|
+
ptrs: [a, b, dst], n, U,
|
|
33
|
+
body: () => {
|
|
34
|
+
for (let k = 0; k < U; k++) {
|
|
35
|
+
f.emit(get(dst), get(a), vload(16 * k), get(b), vload(16 * k), simd(VEC[t][op]));
|
|
36
|
+
if (cmp) f.emit(get(one), simd(V128_AND));
|
|
37
|
+
f.emit(vstore(16 * k));
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
tail: () => {
|
|
41
|
+
f.emit(get(dst), get(a), ty.load, get(b), ty.load, [SCA[t][op]]);
|
|
42
|
+
if (cmp) f.emit([SCA[t].fromBool]);
|
|
43
|
+
f.emit(ty.store);
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
return f;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// dst = OP a. (dst, a, n)
|
|
50
|
+
function vunary(t, op) {
|
|
51
|
+
const ty = TY[t];
|
|
52
|
+
const f = new Fn(`v${op}_${t}`, [I32, I32, I32], []);
|
|
53
|
+
const [dst, a, n] = [0, 1, 2];
|
|
54
|
+
loops(f, ty, {
|
|
55
|
+
ptrs: [a, dst], n, U,
|
|
56
|
+
body: () => { for (let k = 0; k < U; k++) f.emit(get(dst), get(a), vload(16 * k), simd(VEC[t][op]), vstore(16 * k)); },
|
|
57
|
+
tail: () => f.emit(get(dst), get(a), ty.load, [SCA[t][op]], ty.store),
|
|
58
|
+
});
|
|
59
|
+
return f;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// dst = m != 0 ? a : b. (dst, m, a, b, n)
|
|
63
|
+
function vselect(t) {
|
|
64
|
+
const ty = TY[t];
|
|
65
|
+
const f = new Fn(`vsel_${t}`, [I32, I32, I32, I32, I32], []);
|
|
66
|
+
const [dst, m, a, b, n] = [0, 1, 2, 3, 4];
|
|
67
|
+
const zero = f.local(V128);
|
|
68
|
+
f.emit(ty.cnst(0), ty.vsplat, set(zero));
|
|
69
|
+
loops(f, ty, {
|
|
70
|
+
ptrs: [m, a, b, dst], n, U,
|
|
71
|
+
body: () => {
|
|
72
|
+
for (let k = 0; k < U; k++) {
|
|
73
|
+
f.emit(get(dst), get(a), vload(16 * k), get(b), vload(16 * k));
|
|
74
|
+
f.emit(get(m), vload(16 * k), get(zero), simd(VEC[t].ne), simd(BITSELECT), vstore(16 * k));
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
tail: () => f.emit(get(dst), get(a), ty.load, get(b), ty.load, get(m), ty.load, ty.cnst(0), [SCA[t].ne], SELECT, ty.store),
|
|
78
|
+
});
|
|
79
|
+
return f;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Sum of float32 values, added up in float64 so millions of values keep their precision.
|
|
83
|
+
function sumWide() {
|
|
84
|
+
const ty = TY.f32;
|
|
85
|
+
const f = new Fn('vsumw_f32', [I32, I32], [F64]);
|
|
86
|
+
const [p, n] = [0, 1];
|
|
87
|
+
const accs = Array.from({ length: 2 * U }, () => f.local(V128));
|
|
88
|
+
const tmp = f.local(V128), s = f.local(F64);
|
|
89
|
+
for (const a of accs) f.emit(TY.f64.cnst(0), TY.f64.vsplat, set(a));
|
|
90
|
+
loops(f, ty, {
|
|
91
|
+
ptrs: [p], n, U,
|
|
92
|
+
body: () => {
|
|
93
|
+
for (let k = 0; k < U; k++) {
|
|
94
|
+
const [lo, hi] = [accs[2 * k], accs[2 * k + 1]];
|
|
95
|
+
f.emit(get(p), vload(16 * k), set(tmp));
|
|
96
|
+
f.emit(get(lo), get(tmp), simd(F64X2_PROMOTE_LOW), simd(F64X2_ADD), set(lo));
|
|
97
|
+
f.emit(get(hi), get(tmp), get(tmp), simd(SHUFFLE, ...HIGH_HALF), simd(F64X2_PROMOTE_LOW), simd(F64X2_ADD), set(hi));
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
between: () => {
|
|
101
|
+
f.emit(get(accs[0]));
|
|
102
|
+
for (let j = 1; j < accs.length; j++) f.emit(get(accs[j]), simd(F64X2_ADD));
|
|
103
|
+
f.emit(set(accs[0]), get(accs[0]), simd(0x21, 0), get(accs[0]), simd(0x21, 1), F64_OP.add, set(s));
|
|
104
|
+
},
|
|
105
|
+
tail: () => f.emit(get(s), get(p), ty.load, F64_PROMOTE_F32, F64_OP.add, set(s)),
|
|
106
|
+
});
|
|
107
|
+
f.emit(get(s));
|
|
108
|
+
return f;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// acc[key] = acc[key] OP val for each row (rows whose mask is 0 are skipped).
|
|
112
|
+
// (key, val, [mask,] acc, n); acc is float64.
|
|
113
|
+
function groupScatter(t, op, masked) {
|
|
114
|
+
const ty = TY[t];
|
|
115
|
+
const params = masked ? [I32, I32, I32, I32, I32] : [I32, I32, I32, I32];
|
|
116
|
+
const f = new Fn(`g${op}${masked ? 'm' : ''}_${t}`, params, []);
|
|
117
|
+
const [key, val] = [0, 1];
|
|
118
|
+
const mask = masked ? 2 : -1, acc = masked ? 3 : 2, n = masked ? 4 : 3;
|
|
119
|
+
const stop = f.local(I32), addr = f.local(I32);
|
|
120
|
+
f.emit(get(n), i32c(ty.bytes), I32_MUL, get(key), i32add, set(stop));
|
|
121
|
+
f.emit(block, loop, get(key), get(stop), i32geu, brIf(1));
|
|
122
|
+
f.emit(block);
|
|
123
|
+
if (masked) f.emit(get(mask), ty.load, ty.cnst(0), [SCA[t].eq], brIf(0));
|
|
124
|
+
f.emit(get(key), ty.load, TRUNC_SAT_U[t], i32c(3), I32_SHL, get(acc), i32add, set(addr));
|
|
125
|
+
f.emit(get(addr), get(addr), F64_LOAD, get(val), ty.load, t === 'f32' ? F64_PROMOTE_F32 : [], F64_OP[op], F64_STORE);
|
|
126
|
+
f.emit(end);
|
|
127
|
+
for (const p of masked ? [key, val, mask] : [key, val]) f.emit(get(p), i32c(ty.bytes), i32add, set(p));
|
|
128
|
+
f.emit(br(0), end, end);
|
|
129
|
+
return f;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Copy src rows whose mask is non-zero to dst; returns how many were copied.
|
|
133
|
+
// (mask, src, dst, n)
|
|
134
|
+
function compact(t) {
|
|
135
|
+
const ty = TY[t];
|
|
136
|
+
const f = new Fn(`compact_${t}`, [I32, I32, I32, I32], [I32]);
|
|
137
|
+
const [mask, src, dst, n] = [0, 1, 2, 3];
|
|
138
|
+
const stop = f.local(I32), start = f.local(I32);
|
|
139
|
+
f.emit(get(dst), set(start));
|
|
140
|
+
f.emit(get(n), i32c(ty.bytes), I32_MUL, get(src), i32add, set(stop));
|
|
141
|
+
f.emit(block, loop, get(src), get(stop), i32geu, brIf(1));
|
|
142
|
+
f.emit(block, get(mask), ty.load, ty.cnst(0), [SCA[t].eq], brIf(0));
|
|
143
|
+
f.emit(get(dst), get(src), ty.load, ty.store, get(dst), i32c(ty.bytes), i32add, set(dst));
|
|
144
|
+
f.emit(end);
|
|
145
|
+
for (const p of [mask, src]) f.emit(get(p), i32c(ty.bytes), i32add, set(p));
|
|
146
|
+
f.emit(br(0), end, end);
|
|
147
|
+
f.emit(get(dst), get(start), I32_SUB, i32c(Math.log2(ty.bytes)), I32_SHR_U);
|
|
148
|
+
return f;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function interpreterFns() {
|
|
152
|
+
const fns = [sumWide()];
|
|
153
|
+
for (const t of ['f64', 'f32']) {
|
|
154
|
+
for (const op of [...BINARY_OPS, ...COMPARE_OPS]) fns.push(vbinary(t, op));
|
|
155
|
+
for (const op of UNARY_OPS) fns.push(vunary(t, op));
|
|
156
|
+
fns.push(vselect(t), compact(t));
|
|
157
|
+
for (const op of ['add', 'min', 'max']) fns.push(groupScatter(t, op, false), groupScatter(t, op, true));
|
|
158
|
+
}
|
|
159
|
+
return fns;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function buildStaticModule(shared) {
|
|
163
|
+
return buildModule([...kernelFns(), ...interpreterFns()], shared);
|
|
164
|
+
}
|