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. |
| 7 | import fs from 'node:fs'; |
| 8 | import os from 'node:os'; |
| 9 | import path from 'node:path'; |
| 10 | import { fileURLToPath, pathToFileURL } from 'node:url'; |
| 11 | |
| 12 | const DIAG_FULL = 3; // typst.ts diagnostics_format: full (matches www/js/typst.js) |
| 13 | |
| 14 | const 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], ...] }`). |
| 24 | export 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`. |
| 32 | export 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. |
| 60 | export 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 | |
| 114 | export 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. |
| 121 | export 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 | |
| 128 | export function loadavg1() { |
| 129 | // os.loadavg()[0] mirrors /proc/loadavg field 1. |
| 130 | return os.loadavg()[0]; |
| 131 | } |
| 132 | |
| 133 | export 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 | |
| 144 | export const __dirnameOf = (importMetaUrl) => path.dirname(fileURLToPath(importMetaUrl)); |