Skip to content

Commit 76b3441

Browse files
committed
Add JZ WebAssembly kernel example
1 parent 0ff2433 commit 76b3441

5 files changed

Lines changed: 284 additions & 0 deletions

File tree

examples/jz-kernel/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# JZ kernel example
2+
3+
This example uses [`jz`](https://www.npmjs.com/package/jz) to compile a small JavaScript-subset numeric kernel to WebAssembly and run it inside EdgeJS.
4+
5+
The primary path is a pure scalar module: `compile()` returns WebAssembly bytes, `WebAssembly.Module.imports()` is empty, and EdgeJS runs the module through the standard WebAssembly API. `index.mjs` also includes a small explicit host-import sample that passes a `console`-like object through `imports`; this is useful for diagnostics without requiring implicit runtime imports.
6+
7+
JZ is useful for hot numeric, DSP, parser, and typed-array kernels. It is not a general JavaScript runtime or a Node.js compatibility layer; EdgeJS remains the runtime.
8+
9+
Install the example dependency:
10+
11+
```bash
12+
npm install
13+
```
14+
15+
Run the smoke example:
16+
17+
```bash
18+
edge index.mjs
19+
```
20+
21+
Run the checksum benchmark. The benchmark uses a small `Float64Array` 4x4 matrix kernel and calls one compiled export that owns the hot loop, so it measures generated WebAssembly work rather than repeated JS-to-WASM call overhead.
22+
23+
```bash
24+
edge bench.mjs
25+
```
26+
27+
The benchmark prints matching checksums, WebAssembly import count, generated byte size, and median timings. Tune the workload with `ITERATIONS`, `WARMUP`, and `RUNS`.
28+
29+
When working from a local EdgeJS checkout, replace `edge` with `../../build-edge/edge` after running `make build`.

examples/jz-kernel/bench.mjs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import assert from 'node:assert/strict';
2+
import { compile } from 'jz';
3+
4+
const kernelSource = `
5+
const init = (a, b) => {
6+
for (let i = 0; i < 16; i++) {
7+
a[i] = (i + 1) * 0.125
8+
b[i] = (16 - i) * 0.0625
9+
}
10+
}
11+
12+
const multiplyMany = (a, b, out, iters) => {
13+
for (let n = 0; n < iters; n++) {
14+
for (let r = 0; r < 4; r++) {
15+
for (let c = 0; c < 4; c++) {
16+
let s = 0
17+
for (let k = 0; k < 4; k++) s += a[r * 4 + k] * b[k * 4 + c]
18+
out[r * 4 + c] = s + n * 0.0000001
19+
}
20+
}
21+
const t = a[0]
22+
a[0] = out[15]
23+
a[5] = t + out[10] * 0.000001
24+
}
25+
}
26+
27+
export let run = (iters) => {
28+
const a = new Float64Array(16)
29+
const b = new Float64Array(16)
30+
const out = new Float64Array(16)
31+
init(a, b)
32+
multiplyMany(a, b, out, iters)
33+
let checksum = 0
34+
for (let i = 0; i < 16; i++) checksum = (checksum + out[i] * 1000000) | 0
35+
return checksum
36+
}
37+
`;
38+
39+
function init(a, b) {
40+
for (let i = 0; i < 16; i += 1) {
41+
a[i] = (i + 1) * 0.125;
42+
b[i] = (16 - i) * 0.0625;
43+
}
44+
}
45+
46+
function multiplyMany(a, b, out, iters) {
47+
for (let n = 0; n < iters; n += 1) {
48+
for (let r = 0; r < 4; r += 1) {
49+
for (let c = 0; c < 4; c += 1) {
50+
let s = 0;
51+
for (let k = 0; k < 4; k += 1) s += a[r * 4 + k] * b[k * 4 + c];
52+
out[r * 4 + c] = s + n * 0.0000001;
53+
}
54+
}
55+
const t = a[0];
56+
a[0] = out[15];
57+
a[5] = t + out[10] * 0.000001;
58+
}
59+
}
60+
61+
function runJs(iters) {
62+
const a = new Float64Array(16);
63+
const b = new Float64Array(16);
64+
const out = new Float64Array(16);
65+
init(a, b);
66+
multiplyMany(a, b, out, iters);
67+
let checksum = 0;
68+
for (let i = 0; i < 16; i += 1) checksum = (checksum + out[i] * 1000000) | 0;
69+
return checksum;
70+
}
71+
72+
const wasm = compile(kernelSource);
73+
const mod = new WebAssembly.Module(wasm);
74+
const imports = WebAssembly.Module.imports(mod);
75+
const { exports } = new WebAssembly.Instance(mod);
76+
77+
assert.deepEqual(imports, []);
78+
assert.equal(exports.run(128), runJs(128));
79+
80+
const iterations = Number(process.env.ITERATIONS || 200000);
81+
const warmup = Number(process.env.WARMUP || 5);
82+
const runs = Number(process.env.RUNS || 9);
83+
const now = globalThis.performance && typeof globalThis.performance.now === 'function'
84+
? () => globalThis.performance.now()
85+
: () => Date.now();
86+
87+
function median(values) {
88+
const sorted = values.slice().sort((a, b) => a - b);
89+
return sorted[sorted.length >> 1];
90+
}
91+
92+
function measure(fn) {
93+
for (let i = 0; i < warmup; i += 1) fn(iterations);
94+
const samples = [];
95+
const start = now();
96+
let checksum = 0;
97+
for (let i = 0; i < runs; i += 1) {
98+
const sampleStart = now();
99+
checksum = fn(iterations);
100+
samples.push(now() - sampleStart);
101+
}
102+
const totalMs = now() - start;
103+
return {
104+
checksum,
105+
medianMs: Number(median(samples).toFixed(3)),
106+
totalMs: Number(totalMs.toFixed(3)),
107+
};
108+
}
109+
110+
const js = measure(runJs);
111+
const jzWasm = measure(exports.run);
112+
113+
assert.equal(jzWasm.checksum, js.checksum);
114+
115+
console.log(JSON.stringify({
116+
iterations,
117+
warmup,
118+
runs,
119+
imports: imports.length,
120+
wasmBytes: wasm.length,
121+
js,
122+
jzWasm,
123+
}, null, 2));

examples/jz-kernel/index.mjs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import assert from 'node:assert/strict';
2+
import jz, { compile } from 'jz';
3+
4+
const kernelSource = `
5+
export let mac = (a, b, c) => {
6+
let x = c
7+
for (let i = 0; i < 8; i++) x = x + (a + i) * (b - i)
8+
return x
9+
}
10+
11+
export let mix = (x, y) => mac(x, y, 3) - mac(y, x, 1)
12+
`;
13+
14+
function macJs(a, b, c) {
15+
let x = c;
16+
for (let i = 0; i < 8; i += 1) x += (a + i) * (b - i);
17+
return x;
18+
}
19+
20+
function mixJs(x, y) {
21+
return macJs(x, y, 3) - macJs(y, x, 1);
22+
}
23+
24+
const wasm = compile(kernelSource);
25+
const mod = new WebAssembly.Module(wasm);
26+
const imports = WebAssembly.Module.imports(mod);
27+
28+
assert.deepEqual(imports, []);
29+
30+
const { exports } = new WebAssembly.Instance(mod);
31+
const vectors = [
32+
[2, 9, 1],
33+
[7, 4, 3],
34+
[11, -2, 5],
35+
];
36+
37+
let checksum = 0;
38+
for (const [a, b, c] of vectors) {
39+
const actual = exports.mac(a, b, c);
40+
const expected = macJs(a, b, c);
41+
assert.equal(actual, expected);
42+
checksum += actual;
43+
}
44+
45+
const mix = exports.mix(4, 7);
46+
assert.equal(mix, mixJs(4, 7));
47+
48+
const captured = [];
49+
const hostConsole = Object.assign(Object.create(console), {
50+
log(value) {
51+
captured.push(value);
52+
},
53+
});
54+
55+
const hostModule = jz(`
56+
import { log } from "console"
57+
export let report = (value) => {
58+
log(value)
59+
return value + 1
60+
}
61+
`, {
62+
imports: { console: hostConsole },
63+
});
64+
65+
assert.equal(hostModule.exports.report(41), 42);
66+
assert.deepEqual(captured, [41]);
67+
68+
console.log(JSON.stringify({
69+
kernel: {
70+
imports: imports.length,
71+
checksum,
72+
mix,
73+
},
74+
explicitHostImport: {
75+
module: 'console',
76+
captured,
77+
result: 42,
78+
},
79+
}, null, 2));

examples/jz-kernel/package-lock.json

Lines changed: 41 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

examples/jz-kernel/package.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "edgejs-jz-kernel-example",
3+
"private": true,
4+
"type": "module",
5+
"scripts": {
6+
"start": "edge index.mjs",
7+
"bench": "edge bench.mjs"
8+
},
9+
"dependencies": {
10+
"jz": "^0.1.1"
11+
}
12+
}

0 commit comments

Comments
 (0)