reg-cli 0.18.16 → 0.19.0-rc1

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/dist/index.mjs ADDED
@@ -0,0 +1,207 @@
1
+ import { a as processWorkerSpans, i as processRustTraceData, n as initTracing, o as shutdownTracing, p as resolveExtention, r as isTracingEnabled, s as startRootSpan, t as endRootSpan, u as isCJS } from "./tracing-Bo38b9aR.mjs";
2
+ import EventEmitter from "node:events";
3
+ import { Worker } from "node:worker_threads";
4
+ import { fileURLToPath } from "url";
5
+ import { dirname, join } from "node:path";
6
+ //#region src/index.ts
7
+ const dir = () => {
8
+ return isCJS ? __dirname : dirname(fileURLToPath(import.meta.url));
9
+ };
10
+ const mainSpans = [];
11
+ const recordMainSpan = (name, start_ms, attributes) => {
12
+ if (!isTracingEnabled()) return;
13
+ mainSpans.push({
14
+ name,
15
+ start_ms,
16
+ end_ms: Date.now(),
17
+ attributes,
18
+ worker_label: "main"
19
+ });
20
+ };
21
+ const run = (argv) => {
22
+ const emitter = new EventEmitter();
23
+ setImmediate(() => emitter.emit("start"));
24
+ runInternal(argv, emitter);
25
+ return emitter;
26
+ };
27
+ const runInternal = async (argv, emitter) => {
28
+ const run_start_ms = Date.now();
29
+ const t_init = Date.now();
30
+ if (isTracingEnabled()) {
31
+ await initTracing();
32
+ recordMainSpan("main.init_tracing", t_init);
33
+ }
34
+ const traceContext = isTracingEnabled() ? startRootSpan("reg-cli") : null;
35
+ const t_new_entry = Date.now();
36
+ const worker = new Worker(join(dir(), `./runner.${resolveExtention()}`), { workerData: {
37
+ argv,
38
+ mode: "entry"
39
+ } });
40
+ recordMainSpan("main.new_entry_worker", t_new_entry);
41
+ let nextTid = 1;
42
+ const workers = [worker];
43
+ const threadWorkerSpans = [];
44
+ const attachCommonHandlers = (w) => {
45
+ w.on("message", (msg) => {
46
+ switch (msg.cmd) {
47
+ case "compare-event":
48
+ if (msg.event) emitter.emit("compare", msg.event);
49
+ return;
50
+ case "thread-spawn":
51
+ spawn(msg.startArg, msg.threadId, msg.memory);
52
+ return;
53
+ case "worker-spans":
54
+ if (Array.isArray(msg.workerSpans)) threadWorkerSpans.push(...msg.workerSpans);
55
+ return;
56
+ case "loaded":
57
+ if (typeof w.unref === "function") w.unref();
58
+ return;
59
+ }
60
+ });
61
+ w.on("error", (e) => {
62
+ if (traceContext) endRootSpan(false);
63
+ workers.forEach((x) => x.terminate());
64
+ emitter.emit("error", e);
65
+ });
66
+ };
67
+ const spawn = (startArg, threadId, memory) => {
68
+ const t_new_worker = Date.now();
69
+ const w = new Worker(join(dir(), `./runner.${resolveExtention()}`), { workerData: {
70
+ argv,
71
+ mode: "thread"
72
+ } });
73
+ const tid = nextTid++;
74
+ recordMainSpan("main.new_thread_worker", t_new_worker, { tid });
75
+ workers.push(w);
76
+ attachCommonHandlers(w);
77
+ if (threadId) {
78
+ Atomics.store(threadId, 0, tid);
79
+ Atomics.notify(threadId, 0);
80
+ }
81
+ w.postMessage({
82
+ startArg,
83
+ tid,
84
+ memory
85
+ });
86
+ return tid;
87
+ };
88
+ attachCommonHandlers(worker);
89
+ worker.on("message", async (msg) => {
90
+ if (msg.cmd !== "complete") return;
91
+ const { data, traceData, workerSpans } = msg;
92
+ const t_post_complete = Date.now();
93
+ if (isTracingEnabled()) {
94
+ if (Array.isArray(workerSpans)) processWorkerSpans(workerSpans, "entry");
95
+ if (threadWorkerSpans.length) processWorkerSpans(threadWorkerSpans, "thread");
96
+ if (traceData) processRustTraceData(traceData);
97
+ recordMainSpan("main.process_trace_and_spans", t_post_complete);
98
+ mainSpans.push({
99
+ name: "main.run_total",
100
+ start_ms: run_start_ms,
101
+ end_ms: Date.now(),
102
+ worker_label: "main"
103
+ });
104
+ processWorkerSpans(mainSpans, "main");
105
+ endRootSpan(true);
106
+ await shutdownTracing();
107
+ }
108
+ workers.forEach((w) => w.terminate());
109
+ emitter.emit("complete", data);
110
+ });
111
+ };
112
+ /** Flags that `compare()` handles itself in JS; they are NOT forwarded to
113
+ * the Wasm binary. `extendedErrors` WAS here but now also feeds the
114
+ * Rust-side junit generator, so it's forwarded below via KEY_REMAP.
115
+ *
116
+ * `ignoreChange` and `enableCliAdditionalDetection` are stripped because
117
+ * reg-suit unconditionally passes them (see
118
+ * reg-viz/reg-suit `packages/reg-suit-core/src/processor.ts` `compare({…})`)
119
+ * and the Rust clap layer doesn't recognise them — forwarding would abort
120
+ * the binary with "unexpected argument". Semantically both are no-ops at
121
+ * the library-event layer:
122
+ * - `ignoreChange` only governs classic reg-cli's process exit code;
123
+ * the EventEmitter surface never needed it.
124
+ * - `enableCliAdditionalDetection` was classic's flag for running an
125
+ * extra CLI-side x-img-diff pass; the Wasm port's diff pipeline
126
+ * already produces the final pass/fail classification, so toggling
127
+ * it changes nothing. (The *client*-side variant is handled via
128
+ * `additionalDetection: 'client'` / the legacy
129
+ * `enableClientAdditionalDetection: true` alias further down.) */
130
+ const CLI_ONLY_KEYS = new Set([
131
+ "update",
132
+ "ignoreChange",
133
+ "enableCliAdditionalDetection"
134
+ ]);
135
+ /** Library option names that must be forwarded to the Wasm binary under a
136
+ * different flag name (Rust uses `--junit`, callers pass `junitReport`). */
137
+ const KEY_REMAP = { junitReport: "junit" };
138
+ const compare = (input) => {
139
+ const { actualDir, expectedDir, diffDir, threshold, update, ...rest } = input;
140
+ if (rest.diffFormat == null) rest.diffFormat = "png";
141
+ if (rest.json == null) rest.json = "./reg.json";
142
+ if (threshold != null && rest.thresholdRate == null) rest.thresholdRate = threshold;
143
+ const restAny = rest;
144
+ if (restAny.enableClientAdditionalDetection && restAny.additionalDetection == null) restAny.additionalDetection = "client";
145
+ delete restAny.enableClientAdditionalDetection;
146
+ for (const k of CLI_ONLY_KEYS) delete rest[k];
147
+ const inner = run([
148
+ "--",
149
+ actualDir,
150
+ expectedDir,
151
+ diffDir,
152
+ ...Object.entries(rest).flatMap(([k, v]) => {
153
+ if (v == null || v === "") return [];
154
+ return [`--${KEY_REMAP[k] ?? k}`, String(v)];
155
+ })
156
+ ]);
157
+ const outer = new EventEmitter();
158
+ inner.on("start", () => outer.emit("start"));
159
+ inner.on("compare", (p) => outer.emit("compare", p));
160
+ inner.on("error", (e) => outer.emit("error", e));
161
+ inner.on("complete", async (data) => {
162
+ try {
163
+ if (restAny.additionalDetection === "client" && typeof input.report === "string") {
164
+ const { writeXimgdiffAssets } = await import("./ximgdiff-D8GAJaxA.mjs").then((n) => n.n);
165
+ await writeXimgdiffAssets({
166
+ reportPath: input.report,
167
+ urlPrefix: typeof input.urlPrefix === "string" ? input.urlPrefix : "",
168
+ distDir: dir()
169
+ });
170
+ }
171
+ if (update) {
172
+ await updateExpected(actualDir, expectedDir, {
173
+ newItems: data.newItems ?? [],
174
+ failedItems: data.failedItems ?? [],
175
+ deletedItems: data.deletedItems ?? []
176
+ });
177
+ outer.emit("update");
178
+ }
179
+ } catch (e) {
180
+ outer.emit("error", e);
181
+ return;
182
+ }
183
+ outer.emit("complete", data);
184
+ });
185
+ return outer;
186
+ };
187
+ async function writeRegJson(path, data) {
188
+ const { writeFile, mkdir } = await import("node:fs/promises");
189
+ const { dirname } = await import("node:path");
190
+ await mkdir(dirname(path), { recursive: true });
191
+ await writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf8");
192
+ }
193
+ async function updateExpected(actualDir, expectedDir, items) {
194
+ const { mkdir, copyFile, rm } = await import("node:fs/promises");
195
+ const { dirname, join } = await import("node:path");
196
+ const toRemove = [...items.deletedItems, ...items.failedItems];
197
+ for (const img of toRemove) await rm(join(expectedDir, img), { force: true });
198
+ const toCopy = [...items.newItems, ...items.failedItems];
199
+ for (const img of toCopy) {
200
+ const src = join(actualDir, img);
201
+ const dst = join(expectedDir, img);
202
+ await mkdir(dirname(dst), { recursive: true });
203
+ await copyFile(src, dst);
204
+ }
205
+ }
206
+ //#endregion
207
+ export { compare, dir, run, writeRegJson };
package/dist/reg.wasm ADDED
Binary file
@@ -0,0 +1,228 @@
1
+ const require_tracing = require("./tracing-Cf9TNPXQ.cjs");
2
+ let node_worker_threads = require("node:worker_threads");
3
+ let node_process = require("node:process");
4
+ let node_fs = require("node:fs");
5
+ node_fs = require_tracing.__toESM(node_fs, 1);
6
+ let _tybys_wasm_util = require("@tybys/wasm-util");
7
+ //#region src/proxy.ts
8
+ const kIsProxy = Symbol("kIsProxy");
9
+ const createInstanceProxy = (instance, memory) => {
10
+ if (instance[kIsProxy]) return instance;
11
+ const originalExports = instance.exports;
12
+ const createHandler = function(target) {
13
+ const handlers = [
14
+ "apply",
15
+ "construct",
16
+ "defineProperty",
17
+ "deleteProperty",
18
+ "get",
19
+ "getOwnPropertyDescriptor",
20
+ "getPrototypeOf",
21
+ "has",
22
+ "isExtensible",
23
+ "ownKeys",
24
+ "preventExtensions",
25
+ "set",
26
+ "setPrototypeOf"
27
+ ];
28
+ const handler = {};
29
+ for (let i = 0; i < handlers.length; i++) {
30
+ const name = handlers[i];
31
+ handler[name] = function() {
32
+ const args = Array.prototype.slice.call(arguments, 1);
33
+ args.unshift(target);
34
+ return Reflect[name].apply(Reflect, args);
35
+ };
36
+ }
37
+ return handler;
38
+ };
39
+ const handler = createHandler(originalExports);
40
+ const _initialize = () => {};
41
+ const _start = () => 0;
42
+ handler.get = function(_target, p, receiver) {
43
+ if (p === "memory") return (typeof memory === "function" ? memory() : memory) ?? Reflect.get(originalExports, p, receiver);
44
+ if (p === "_initialize") return p in originalExports ? _initialize : void 0;
45
+ if (p === "_start") return p in originalExports ? _start : void 0;
46
+ return Reflect.get(originalExports, p, receiver);
47
+ };
48
+ handler.has = function(_target, p) {
49
+ if (p === "memory") return true;
50
+ return Reflect.has(originalExports, p);
51
+ };
52
+ const exportsProxy = new Proxy(Object.create(null), handler);
53
+ return new Proxy(instance, { get(target, p, receiver) {
54
+ if (p === "exports") return exportsProxy;
55
+ if (p === kIsProxy) return true;
56
+ return Reflect.get(target, p, receiver);
57
+ } });
58
+ };
59
+ //#endregion
60
+ //#region src/progress.ts
61
+ const MARKER = "__REG_CLI_EVT__ ";
62
+ /**
63
+ * Build a WASI `printErr` hook that forwards progress events to `onEvent`
64
+ * and everything else to `fallback` (defaults to `console.error`).
65
+ *
66
+ * `@tybys/wasm-util`'s `StandardOutput.write` already buffers until a
67
+ * newline and invokes the callback with one stripped line per call
68
+ * (see the `StandardOutput` class in
69
+ * `node_modules/@tybys/wasm-util/dist/wasm-util.esm-bundler.js`). So
70
+ * we can treat each invocation as a single complete line.
71
+ */
72
+ const createPrintErrHook = (onEvent, fallback = (s) => console.error(s)) => {
73
+ return (line) => {
74
+ if (line.startsWith(MARKER)) {
75
+ const json = line.slice(16);
76
+ try {
77
+ const ev = JSON.parse(json);
78
+ if (ev && typeof ev.type === "string" && typeof ev.path === "string") {
79
+ onEvent(ev);
80
+ return;
81
+ }
82
+ } catch {}
83
+ }
84
+ fallback(line);
85
+ };
86
+ };
87
+ //#endregion
88
+ //#region src/runner.ts
89
+ const mode = node_worker_threads.workerData.mode;
90
+ const isTracingEnabled = () => node_process.env.OTEL_ENABLED === "true" || node_process.env.JAEGER_ENABLED === "true";
91
+ const argv = require_tracing.normalizeWasiArgv(node_worker_threads.workerData.argv);
92
+ const sandbox = require_tracing.computeWasiSandbox(argv);
93
+ const printErr = createPrintErrHook((ev) => {
94
+ node_worker_threads.parentPort?.postMessage({
95
+ cmd: "compare-event",
96
+ event: ev
97
+ });
98
+ });
99
+ const wasi = new _tybys_wasm_util.WASI({
100
+ version: "preview1",
101
+ args: argv,
102
+ env: sandbox.env,
103
+ returnOnExit: true,
104
+ preopens: sandbox.preopens,
105
+ fs: node_fs.default,
106
+ printErr
107
+ });
108
+ const wasmFile = require_tracing.readWasm();
109
+ const readWasmString = (exports, memory, outputPtr) => {
110
+ const view = new DataView(memory.buffer, outputPtr);
111
+ const len = view.getUint32(0, true);
112
+ const bufPtr = view.getUint32(4, true);
113
+ const bytes = new Uint8Array(memory.buffer, bufPtr, len);
114
+ const s = new TextDecoder("utf-8").decode(bytes);
115
+ exports.free_wasm_output(outputPtr);
116
+ return s;
117
+ };
118
+ const makeSpanRecorder = (workerLabel) => {
119
+ const workerSpans = [];
120
+ const tSpan = async (name, fn, attributes) => {
121
+ if (!isTracingEnabled()) return fn();
122
+ const start_ms = Date.now();
123
+ const r = await fn();
124
+ workerSpans.push({
125
+ name,
126
+ start_ms,
127
+ end_ms: Date.now(),
128
+ attributes,
129
+ worker_label: workerLabel
130
+ });
131
+ return r;
132
+ };
133
+ return {
134
+ workerSpans,
135
+ tSpan
136
+ };
137
+ };
138
+ const makeThreadSpawnImport = (memory) => (startArg) => {
139
+ const buf = new SharedArrayBuffer(4);
140
+ const id = new Int32Array(buf);
141
+ Atomics.store(id, 0, -1);
142
+ node_worker_threads.parentPort?.postMessage({
143
+ cmd: "thread-spawn",
144
+ startArg,
145
+ threadId: id,
146
+ memory
147
+ });
148
+ Atomics.wait(id, 0, -1);
149
+ return Atomics.load(id, 0);
150
+ };
151
+ const compileAndInstantiate = async (memory, tSpan, prefix) => {
152
+ const wasm = await tSpan(`${prefix}.wasm_compile`, async () => WebAssembly.compile(await wasmFile));
153
+ const wasi_snapshot_preview1 = require_tracing.filterWasiImports(wasm, wasi.getImportObject().wasi_snapshot_preview1);
154
+ return tSpan(`${prefix}.wasm_instantiate`, async () => WebAssembly.instantiate(wasm, {
155
+ wasi_snapshot_preview1,
156
+ wasi: { "thread-spawn": makeThreadSpawnImport(memory) },
157
+ env: { memory }
158
+ }));
159
+ };
160
+ if (mode === "entry") (async () => {
161
+ const entry_start_ms = Date.now();
162
+ const { workerSpans, tSpan } = makeSpanRecorder("entry");
163
+ const memory = new WebAssembly.Memory({
164
+ initial: 256,
165
+ maximum: 16384,
166
+ shared: true
167
+ });
168
+ const instance = await compileAndInstantiate(memory, tSpan, "entry");
169
+ const exports = instance.exports;
170
+ await tSpan("entry.wasi_start", async () => {
171
+ wasi.start(instance);
172
+ });
173
+ if (isTracingEnabled() && typeof exports.init_tracing === "function") await tSpan("entry.init_tracing_rust", async () => {
174
+ exports.init_tracing();
175
+ });
176
+ const m = await tSpan("entry.wasm_main", async () => exports.wasm_main());
177
+ const reportString = await tSpan("entry.read_report_string", async () => readWasmString(exports, memory, m));
178
+ const report = JSON.parse(reportString);
179
+ let traceData = null;
180
+ if (isTracingEnabled() && typeof exports.get_trace_data === "function") await tSpan("entry.collect_rust_traces", async () => {
181
+ const traceJson = readWasmString(exports, memory, exports.get_trace_data());
182
+ try {
183
+ traceData = JSON.parse(traceJson);
184
+ } catch (e) {
185
+ console.error("[Tracing] Failed to parse trace data:", e);
186
+ }
187
+ if (typeof exports.clear_trace_data === "function") exports.clear_trace_data();
188
+ });
189
+ if (isTracingEnabled()) workerSpans.push({
190
+ name: "entry.worker_total",
191
+ start_ms: entry_start_ms,
192
+ end_ms: Date.now(),
193
+ worker_label: "entry"
194
+ });
195
+ node_worker_threads.parentPort?.postMessage({
196
+ cmd: "complete",
197
+ data: report,
198
+ traceData,
199
+ workerSpans
200
+ });
201
+ })();
202
+ else node_worker_threads.parentPort?.addListener("message", async ({ startArg, tid, memory }) => {
203
+ const workerLabel = `thread-${tid}`;
204
+ const handler_start_ms = Date.now();
205
+ const { workerSpans, tSpan } = makeSpanRecorder(workerLabel);
206
+ let instance = await compileAndInstantiate(memory, tSpan, "worker");
207
+ instance = createInstanceProxy(instance, memory);
208
+ await tSpan("worker.wasi_start", async () => {
209
+ wasi.start(instance);
210
+ });
211
+ await tSpan("worker.wasi_thread_start", async () => {
212
+ instance.exports.wasi_thread_start(tid, startArg);
213
+ }, { tid });
214
+ if (isTracingEnabled()) {
215
+ workerSpans.push({
216
+ name: "worker.thread_total",
217
+ start_ms: handler_start_ms,
218
+ end_ms: Date.now(),
219
+ worker_label: workerLabel,
220
+ attributes: { tid }
221
+ });
222
+ node_worker_threads.parentPort?.postMessage({
223
+ cmd: "worker-spans",
224
+ workerSpans
225
+ });
226
+ }
227
+ });
228
+ //#endregion
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,228 @@
1
+ import { c as computeWasiSandbox, d as normalizeWasiArgv, f as readWasm, l as filterWasiImports } from "./tracing-Bo38b9aR.mjs";
2
+ import { parentPort, workerData } from "node:worker_threads";
3
+ import { env } from "node:process";
4
+ import fs from "node:fs";
5
+ import { WASI } from "@tybys/wasm-util";
6
+ //#region src/proxy.ts
7
+ const kIsProxy = Symbol("kIsProxy");
8
+ const createInstanceProxy = (instance, memory) => {
9
+ if (instance[kIsProxy]) return instance;
10
+ const originalExports = instance.exports;
11
+ const createHandler = function(target) {
12
+ const handlers = [
13
+ "apply",
14
+ "construct",
15
+ "defineProperty",
16
+ "deleteProperty",
17
+ "get",
18
+ "getOwnPropertyDescriptor",
19
+ "getPrototypeOf",
20
+ "has",
21
+ "isExtensible",
22
+ "ownKeys",
23
+ "preventExtensions",
24
+ "set",
25
+ "setPrototypeOf"
26
+ ];
27
+ const handler = {};
28
+ for (let i = 0; i < handlers.length; i++) {
29
+ const name = handlers[i];
30
+ handler[name] = function() {
31
+ const args = Array.prototype.slice.call(arguments, 1);
32
+ args.unshift(target);
33
+ return Reflect[name].apply(Reflect, args);
34
+ };
35
+ }
36
+ return handler;
37
+ };
38
+ const handler = createHandler(originalExports);
39
+ const _initialize = () => {};
40
+ const _start = () => 0;
41
+ handler.get = function(_target, p, receiver) {
42
+ if (p === "memory") return (typeof memory === "function" ? memory() : memory) ?? Reflect.get(originalExports, p, receiver);
43
+ if (p === "_initialize") return p in originalExports ? _initialize : void 0;
44
+ if (p === "_start") return p in originalExports ? _start : void 0;
45
+ return Reflect.get(originalExports, p, receiver);
46
+ };
47
+ handler.has = function(_target, p) {
48
+ if (p === "memory") return true;
49
+ return Reflect.has(originalExports, p);
50
+ };
51
+ const exportsProxy = new Proxy(Object.create(null), handler);
52
+ return new Proxy(instance, { get(target, p, receiver) {
53
+ if (p === "exports") return exportsProxy;
54
+ if (p === kIsProxy) return true;
55
+ return Reflect.get(target, p, receiver);
56
+ } });
57
+ };
58
+ //#endregion
59
+ //#region src/progress.ts
60
+ const MARKER = "__REG_CLI_EVT__ ";
61
+ /**
62
+ * Build a WASI `printErr` hook that forwards progress events to `onEvent`
63
+ * and everything else to `fallback` (defaults to `console.error`).
64
+ *
65
+ * `@tybys/wasm-util`'s `StandardOutput.write` already buffers until a
66
+ * newline and invokes the callback with one stripped line per call
67
+ * (see the `StandardOutput` class in
68
+ * `node_modules/@tybys/wasm-util/dist/wasm-util.esm-bundler.js`). So
69
+ * we can treat each invocation as a single complete line.
70
+ */
71
+ const createPrintErrHook = (onEvent, fallback = (s) => console.error(s)) => {
72
+ return (line) => {
73
+ if (line.startsWith(MARKER)) {
74
+ const json = line.slice(16);
75
+ try {
76
+ const ev = JSON.parse(json);
77
+ if (ev && typeof ev.type === "string" && typeof ev.path === "string") {
78
+ onEvent(ev);
79
+ return;
80
+ }
81
+ } catch {}
82
+ }
83
+ fallback(line);
84
+ };
85
+ };
86
+ //#endregion
87
+ //#region src/runner.ts
88
+ const mode = workerData.mode;
89
+ const isTracingEnabled = () => env.OTEL_ENABLED === "true" || env.JAEGER_ENABLED === "true";
90
+ const argv = normalizeWasiArgv(workerData.argv);
91
+ const sandbox = computeWasiSandbox(argv);
92
+ const printErr = createPrintErrHook((ev) => {
93
+ parentPort?.postMessage({
94
+ cmd: "compare-event",
95
+ event: ev
96
+ });
97
+ });
98
+ const wasi = new WASI({
99
+ version: "preview1",
100
+ args: argv,
101
+ env: sandbox.env,
102
+ returnOnExit: true,
103
+ preopens: sandbox.preopens,
104
+ fs,
105
+ printErr
106
+ });
107
+ const wasmFile = readWasm();
108
+ const readWasmString = (exports, memory, outputPtr) => {
109
+ const view = new DataView(memory.buffer, outputPtr);
110
+ const len = view.getUint32(0, true);
111
+ const bufPtr = view.getUint32(4, true);
112
+ const bytes = new Uint8Array(memory.buffer, bufPtr, len);
113
+ const s = new TextDecoder("utf-8").decode(bytes);
114
+ exports.free_wasm_output(outputPtr);
115
+ return s;
116
+ };
117
+ const makeSpanRecorder = (workerLabel) => {
118
+ const workerSpans = [];
119
+ const tSpan = async (name, fn, attributes) => {
120
+ if (!isTracingEnabled()) return fn();
121
+ const start_ms = Date.now();
122
+ const r = await fn();
123
+ workerSpans.push({
124
+ name,
125
+ start_ms,
126
+ end_ms: Date.now(),
127
+ attributes,
128
+ worker_label: workerLabel
129
+ });
130
+ return r;
131
+ };
132
+ return {
133
+ workerSpans,
134
+ tSpan
135
+ };
136
+ };
137
+ const makeThreadSpawnImport = (memory) => (startArg) => {
138
+ const buf = new SharedArrayBuffer(4);
139
+ const id = new Int32Array(buf);
140
+ Atomics.store(id, 0, -1);
141
+ parentPort?.postMessage({
142
+ cmd: "thread-spawn",
143
+ startArg,
144
+ threadId: id,
145
+ memory
146
+ });
147
+ Atomics.wait(id, 0, -1);
148
+ return Atomics.load(id, 0);
149
+ };
150
+ const compileAndInstantiate = async (memory, tSpan, prefix) => {
151
+ const wasm = await tSpan(`${prefix}.wasm_compile`, async () => WebAssembly.compile(await wasmFile));
152
+ const wasi_snapshot_preview1 = filterWasiImports(wasm, wasi.getImportObject().wasi_snapshot_preview1);
153
+ return tSpan(`${prefix}.wasm_instantiate`, async () => WebAssembly.instantiate(wasm, {
154
+ wasi_snapshot_preview1,
155
+ wasi: { "thread-spawn": makeThreadSpawnImport(memory) },
156
+ env: { memory }
157
+ }));
158
+ };
159
+ if (mode === "entry") (async () => {
160
+ const entry_start_ms = Date.now();
161
+ const { workerSpans, tSpan } = makeSpanRecorder("entry");
162
+ const memory = new WebAssembly.Memory({
163
+ initial: 256,
164
+ maximum: 16384,
165
+ shared: true
166
+ });
167
+ const instance = await compileAndInstantiate(memory, tSpan, "entry");
168
+ const exports = instance.exports;
169
+ await tSpan("entry.wasi_start", async () => {
170
+ wasi.start(instance);
171
+ });
172
+ if (isTracingEnabled() && typeof exports.init_tracing === "function") await tSpan("entry.init_tracing_rust", async () => {
173
+ exports.init_tracing();
174
+ });
175
+ const m = await tSpan("entry.wasm_main", async () => exports.wasm_main());
176
+ const reportString = await tSpan("entry.read_report_string", async () => readWasmString(exports, memory, m));
177
+ const report = JSON.parse(reportString);
178
+ let traceData = null;
179
+ if (isTracingEnabled() && typeof exports.get_trace_data === "function") await tSpan("entry.collect_rust_traces", async () => {
180
+ const traceJson = readWasmString(exports, memory, exports.get_trace_data());
181
+ try {
182
+ traceData = JSON.parse(traceJson);
183
+ } catch (e) {
184
+ console.error("[Tracing] Failed to parse trace data:", e);
185
+ }
186
+ if (typeof exports.clear_trace_data === "function") exports.clear_trace_data();
187
+ });
188
+ if (isTracingEnabled()) workerSpans.push({
189
+ name: "entry.worker_total",
190
+ start_ms: entry_start_ms,
191
+ end_ms: Date.now(),
192
+ worker_label: "entry"
193
+ });
194
+ parentPort?.postMessage({
195
+ cmd: "complete",
196
+ data: report,
197
+ traceData,
198
+ workerSpans
199
+ });
200
+ })();
201
+ else parentPort?.addListener("message", async ({ startArg, tid, memory }) => {
202
+ const workerLabel = `thread-${tid}`;
203
+ const handler_start_ms = Date.now();
204
+ const { workerSpans, tSpan } = makeSpanRecorder(workerLabel);
205
+ let instance = await compileAndInstantiate(memory, tSpan, "worker");
206
+ instance = createInstanceProxy(instance, memory);
207
+ await tSpan("worker.wasi_start", async () => {
208
+ wasi.start(instance);
209
+ });
210
+ await tSpan("worker.wasi_thread_start", async () => {
211
+ instance.exports.wasi_thread_start(tid, startArg);
212
+ }, { tid });
213
+ if (isTracingEnabled()) {
214
+ workerSpans.push({
215
+ name: "worker.thread_total",
216
+ start_ms: handler_start_ms,
217
+ end_ms: Date.now(),
218
+ worker_label: workerLabel,
219
+ attributes: { tid }
220
+ });
221
+ parentPort?.postMessage({
222
+ cmd: "worker-spans",
223
+ workerSpans
224
+ });
225
+ }
226
+ });
227
+ //#endregion
228
+ export {};