Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_austenite/tools/bench/lib/wasm_common.mjs

5.1 KiB, 1 run

created by r1870400018:60946, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

1// Shared node-side helpers for the S0 wasm bench: loading the two vendored wasm
2// compilers exactly as Daimond's `www/js/typst.js` sequences them (dummy access
3// model, the same bundled font set, shadow sources), but reading everything with
4// `fs` instead of `fetch`, because Node's `fetch` does not resolve `file://` --
5// confirmed on this host, Node v20.20.2. This is the one deliberate deviation
6// from the browser driving code; see tools/bench/README.md.
7import fs from 'node:fs';
8import os from 'node:os';
9import path from 'node:path';
10import { fileURLToPath, pathToFileURL } from 'node:url';
11
12const DIAG_FULL = 3; // typst.ts diagnostics_format: full (matches www/js/typst.js)
13
14const FONTS = [
15 'LibertinusSerif-Regular.otf',
16 'LibertinusSerif-Bold.otf',
17 'LibertinusSerif-Italic.otf',
18 'LibertinusSerif-BoldItalic.otf',
19 'NewCMMath-Regular.otf',
20];
21
22/// Reads a `.typ` file as one project source at `/main.typ`, the shape both
23/// wasm compile entry points want (`{ main, sources: [[path, text], ...] }`).
24export function projectFromFile(typPath) {
25 const text = fs.readFileSync(typPath, 'utf8');
26 return { main: '/main.typ', sources: [['/main.typ', text]] };
27}
28
29/// Builds the Austenite wasm instance from a vendor directory containing
30/// `oxedyne_fe2o3_austenite.js` and `..._bg.wasm`, exactly as `getAustenite()`
31/// does in `www/js/typst.js`.
32export async function loadAustenite(vendorDir) {
33 const glue = pathToFileURL(path.join(vendorDir, 'oxedyne_fe2o3_austenite.js')).href;
34 const wasmPath = path.join(vendorDir, 'oxedyne_fe2o3_austenite_bg.wasm');
35 const mod = await import(glue);
36 await mod.default(fs.readFileSync(wasmPath));
37 const instance = new mod.DaimondTypst();
38 return {
39 engine: 'austenite',
40 compilePdf(project) {
41 return instance.compileProject(project);
42 },
43 compileVector(project) {
44 return instance.compileProjectVector(project);
45 },
46 compileDelta(project) {
47 return instance.compileProjectDelta(project);
48 },
49 heapMB() {
50 return instance.heapMB();
51 },
52 };
53}
54
55/// Builds the typst.ts wasm compiler from a vendor directory containing
56/// `typst_ts_web_compiler.mjs`/`..._bg.wasm` and a `fonts/` subdirectory with
57/// the bundled font set, exactly as `getCompiler()` does in `www/js/typst.js`:
58/// a dummy access model (sources are injected, nothing is read from disk) and
59/// the same five bundled fonts.
60export async function loadTypstTs(vendorDir) {
61 const glue = pathToFileURL(path.join(vendorDir, 'typst_ts_web_compiler.mjs')).href;
62 const wasmPath = path.join(vendorDir, 'typst_ts_web_compiler_bg.wasm');
63 const mod = await import(glue);
64 await mod.default(fs.readFileSync(wasmPath));
65 const builder = new mod.TypstCompilerBuilder();
66 builder.set_dummy_access_model();
67 for (const name of FONTS) {
68 const buf = fs.readFileSync(path.join(vendorDir, 'fonts', name));
69 await builder.add_raw_font(buf);
70 }
71 const compiler = await builder.build();
72 let lastMain = null;
73 function ensureSources(project) {
74 compiler.reset_shadow();
75 for (const [p, text] of project.sources) {
76 if (compiler.add_source(p, text) === false) {
77 throw new Error(`typst.ts refused source ${p}`);
78 }
79 }
80 lastMain = project.main;
81 }
82 function extract(ret, key) {
83 if (ret instanceof Uint8Array) return ret;
84 if (ret && typeof ret === 'object') {
85 const cand = ret.result ?? ret.artifact ?? ret[key];
86 if (cand instanceof Uint8Array) return cand;
87 if (cand && cand.buffer) return new Uint8Array(cand.buffer);
88 }
89 return null;
90 }
91 return {
92 engine: 'typstts',
93 compilePdf(project) {
94 ensureSources(project);
95 const ret = compiler.compile(project.main, undefined, 'pdf', DIAG_FULL);
96 const bytes = extract(ret, 'pdf');
97 return bytes ? { pdf: bytes } : { error: JSON.stringify(ret).slice(0, 500) };
98 },
99 compileVector(project) {
100 ensureSources(project);
101 const ret = compiler.compile(project.main, undefined, 'vector', DIAG_FULL);
102 const bytes = extract(ret, 'vector');
103 return bytes ? { vector: bytes } : { error: JSON.stringify(ret).slice(0, 500) };
104 },
105 // typst.ts has no changed-only delta door in Daimond's wiring (see
106 // tools/bench/README.md): its live-view path is a full re-compile to
107 // `vector` on every edit, which is what edit_latency.mjs times.
108 compileDelta(project) {
109 return this.compileVector(project);
110 },
111 };
112}
113
114export async function loadEngine(kind, vendorDir) {
115 if (kind === 'austenite') return loadAustenite(vendorDir);
116 if (kind === 'typstts') return loadTypstTs(vendorDir);
117 throw new Error(`unknown engine ${kind}`);
118}
119
120/// One wall-clock timing of a synchronous compile call, in seconds.
121export function timeOnce(fn) {
122 const t0 = process.hrtime.bigint();
123 const ret = fn();
124 const t1 = process.hrtime.bigint();
125 return { wall_s: Number(t1 - t0) / 1e9, ret };
126}
127
128export function loadavg1() {
129 // os.loadavg()[0] mirrors /proc/loadavg field 1.
130 return os.loadavg()[0];
131}
132
133export function psiAvg10(resource) {
134 try {
135 const raw = fs.readFileSync(`/proc/pressure/${resource}`, 'utf8');
136 const line = raw.split('\n').find((l) => l.startsWith('some'));
137 const m = line && line.match(/avg10=([0-9.]+)/);
138 return m ? Number(m[1]) : 0;
139 } catch {
140 return 0;
141 }
142}
143
144export const __dirnameOf = (importMetaUrl) => path.dirname(fileURLToPath(importMetaUrl));