-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbench.ts
More file actions
executable file
·1673 lines (1442 loc) · 47.5 KB
/
bench.ts
File metadata and controls
executable file
·1673 lines (1442 loc) · 47.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env bun
// bench.ts
// ========
// Unified benchmark runner for Bend/HVM benchmark suites.
//
// Sources:
// - `bench/*/main.bend` for Bend pipelines
// - `bench/*/main.hvm` for HVM pipelines
// - `bench/*/main.ts` for native TS pipelines
//
// Sampling model:
// - warmup calls
// - timed calls until either min_runs or min_secs is satisfied
// - report mean seconds per call
import { spawn } from "node:child_process";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import * as url from "node:url";
// Config
// ------
var MODE_MIN_WID = 11;
var NAME_MAX_WID = 28;
var MAX_BUFFER = 64 * 1024 * 1024;
var CMD_TIMEOUT_MS = 20 * 60 * 1000;
var CLEAR = "\x1b[2J\x1b[H";
var DEF_WARMUP = 1;
var DEF_MIN_RUNS = 1;
var DEF_MAX_RUNS = 1;
var DEF_MIN_SECS = 0.0;
var SECS_TAG = "BENCH_SECS";
var TIMEOUT_PREFIX = "timed out after ";
var BEND_MAIN_JS_EXPORT = "n" + Buffer.from("main", "utf8").toString("hex");
var ROOT_DIR = path.dirname(url.fileURLToPath(import.meta.url));
var CASE_DIR = path.join(ROOT_DIR, "bench");
var TMP_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "bench-ts."));
var BEND_CMD = process.env.BEND_CMD ?? "bend";
var NEW_BEND_CMD = process.env.NEW_BEND_CMD ?? "newbend";
var BEND_NODE_ENTRY = process.env.BEND_NODE_ENTRY ?? null;
var NEW_BEND_NODE_ENTRY = process.env.NEW_BEND_NODE_ENTRY ?? null;
var HVM_CMD = process.env.HVM_CMD ?? "hvm";
var BUN_CMD = process.env.BUN_CMD ?? "bun";
var NODE_CMD = process.env.NODE_CMD ?? "node";
// Types
// -----
type CellState = "pending" | "running" | "done" | "error" | "timeout" | "na";
type Cell = {
state: CellState;
secs: number | null;
err: string | null;
};
type Row = {
name: string;
bend_file: string | null;
hvm_file: string | null;
ts_file: string | null;
cells: Record<string, Cell>;
};
type SampleCfg = {
warmup: number;
min_runs: number;
max_runs: number;
min_secs: number;
};
type ModeInput = "bend" | "hvm" | "ts";
type BendCmdSrc = "none" | "bend" | "newbend";
type ModeDef = {
flag: string;
label: string;
input: ModeInput;
bend_cmd_src: BendCmdSrc;
needs_bend: boolean;
needs_hvm: boolean;
needs_bun: boolean;
needs_node: boolean;
needs_node_ts: boolean;
hvm_threads: boolean;
run: (row: Row, cfg: SampleCfg, threads: number, bend_cmd: string | null) => Promise<number>;
};
type Mode = {
key: string;
flag: string;
label: string;
input: ModeInput;
bend_cmd: string | null;
needs_bend: boolean;
needs_hvm: boolean;
needs_bun: boolean;
needs_node: boolean;
needs_node_ts: boolean;
run: (row: Row, cfg: SampleCfg) => Promise<number>;
};
type CliCfg = {
show_help: boolean;
timeout_ms: number;
mode_tokens: string[];
};
// Cells
// -----
// Creates one fresh table cell.
function cell_new(state: CellState = "pending"): Cell {
return { state, secs: null, err: null };
}
// Process Management
// ------------------
var ACTIVE_CHILDREN = new Set<number>();
var SHUTTING_DOWN = false;
// Kills one process group, or single pid fallback.
function kill_pg(pid: number): void {
try {
process.kill(-pid, "SIGKILL");
return;
} catch {}
try {
process.kill(pid, "SIGKILL");
} catch {}
}
// Kills all active child processes.
function cleanup_children(): void {
for (var pid of ACTIVE_CHILDREN) {
kill_pg(pid);
}
ACTIVE_CHILDREN.clear();
}
// Deletes temporary runner files.
function cleanup_tmp(): void {
try {
fs.rmSync(TMP_DIR, { recursive: true, force: true });
} catch {}
}
// Runs all cleanup actions.
function cleanup_all(): void {
cleanup_children();
cleanup_tmp();
}
// Handles process signals.
function on_signal(sig: string): void {
if (SHUTTING_DOWN) {
return;
}
SHUTTING_DOWN = true;
cleanup_all();
process.exit(sig === "SIGINT" ? 130 : 143);
}
process.on("SIGINT", () => on_signal("SIGINT"));
process.on("SIGTERM", () => on_signal("SIGTERM"));
process.on("exit", () => cleanup_all());
// Timer
// -----
// Returns current monotonic timestamp in nanoseconds.
function now_ns(): bigint {
return process.hrtime.bigint();
}
// Returns elapsed seconds since a start timestamp.
function elapsed_secs(start: bigint): number {
return Number(process.hrtime.bigint() - start) / 1e9;
}
// Env Parsing
// -----------
// Reads a positive integer env var.
function env_pos_int(name: string, fallback: number): number {
var raw = process.env[name];
if (raw === undefined || raw === "") {
return fallback;
}
var val = Number(raw);
if (!Number.isFinite(val)) {
throw new Error("invalid env " + name + ": " + JSON.stringify(raw));
}
var val = Math.floor(val);
if (val <= 0) {
throw new Error("invalid env " + name + " (must be > 0): " + JSON.stringify(raw));
}
return val;
}
// Reads a non-negative integer env var.
function env_nonneg_int(name: string, fallback: number): number {
var raw = process.env[name];
if (raw === undefined || raw === "") {
return fallback;
}
var val = Number(raw);
if (!Number.isFinite(val)) {
throw new Error("invalid env " + name + ": " + JSON.stringify(raw));
}
var val = Math.floor(val);
if (val < 0) {
throw new Error("invalid env " + name + " (must be >= 0): " + JSON.stringify(raw));
}
return val;
}
// Reads a non-negative number env var.
function env_nonneg_num(name: string, fallback: number): number {
var raw = process.env[name];
if (raw === undefined || raw === "") {
return fallback;
}
var val = Number(raw);
if (!Number.isFinite(val) || val < 0) {
throw new Error("invalid env " + name + " (must be >= 0): " + JSON.stringify(raw));
}
return val;
}
// Returns benchmark sampling config.
function sample_cfg_get(): SampleCfg {
return {
warmup: DEF_WARMUP,
min_runs: DEF_MIN_RUNS,
max_runs: DEF_MAX_RUNS,
min_secs: DEF_MIN_SECS,
};
}
// Formats sampling config.
function sample_cfg_show(cfg: SampleCfg): string {
return [
"sampling:",
"warmup=" + cfg.warmup,
"min_runs=" + cfg.min_runs,
"max_runs=" + cfg.max_runs,
"min_secs=" + cfg.min_secs.toFixed(3) + "s",
].join(" ");
}
// Paths
// -----
// Asserts one path exists.
function assert_exists(file: string, desc: string): void {
if (!fs.existsSync(file)) {
throw new Error("missing " + desc + ": " + file);
}
}
// Returns true if a path is executable.
function is_exec(file: string): boolean {
try {
var st = fs.statSync(file);
if (!st.isFile()) {
return false;
}
return (st.mode & 0o111) !== 0;
} catch {
return false;
}
}
// Resolves one command to an absolute executable path.
function resolve_cmd_path(cmd: string): string {
if (cmd.includes("/")) {
var abs = path.resolve(cmd);
if (!is_exec(abs)) {
throw new Error("command is not executable: " + abs);
}
return fs.realpathSync(abs);
}
var raw_path = process.env.PATH ?? "";
var dirs = raw_path.split(path.delimiter).filter(p => p.length > 0);
for (var dir of dirs) {
var cand = path.join(dir, cmd);
if (!is_exec(cand)) {
continue;
}
return fs.realpathSync(cand);
}
throw new Error("command not found in $PATH: " + cmd);
}
// Converts text into a filesystem-safe token.
function tmp_tok(txt: string): string {
var txt = txt.toLowerCase();
var txt = txt.replace(/[^a-z0-9]+/g, "-");
var txt = txt.replace(/^-+/, "");
var txt = txt.replace(/-+$/, "");
if (txt.length === 0) {
return "tmp";
}
return txt;
}
// Creates one deterministic temp path under this run directory.
function tmp_path(parts: string[], ext: string): string {
var nam = parts.map(tmp_tok).join("-");
if (nam.length === 0) {
nam = "tmp";
}
return path.join(TMP_DIR, nam + ext);
}
// Returns benchmark tag from benchmark file path.
function bench_tag(file: string): string {
return path.basename(path.dirname(file));
}
// Commands
// --------
// Formats spawn errors for readable messages.
function spawn_err_msg(cmd: string, err: unknown): string {
if (typeof err === "object" && err !== null && "code" in err) {
if ((err as any).code === "ENOENT") {
return "not found in $PATH: " + JSON.stringify(cmd);
}
}
if (err instanceof Error && err.message.length > 0) {
return err.message;
}
return String(err);
}
// Returns whether one error message corresponds to a timeout.
function err_is_timeout(msg: string): boolean {
return msg.startsWith(TIMEOUT_PREFIX);
}
// Runs one command and returns stdout, or throws on failure.
async function run_cmd(cmd: string, args: string[], cwd: string = ROOT_DIR): Promise<string> {
return await new Promise((resolve, reject) => {
var done = false;
var out_len = 0;
var err_len = 0;
var out_bufs: Buffer[] = [];
var err_bufs: Buffer[] = [];
var cmd_txt = [cmd].concat(args).join(" ");
var child = spawn(cmd, args, {
cwd,
detached: true,
stdio: ["ignore", "pipe", "pipe"],
});
var pid = child.pid ?? null;
if (pid !== null) {
ACTIVE_CHILDREN.add(pid);
}
function fin_ok(out: string): void {
if (done) {
return;
}
done = true;
clearTimeout(timer);
if (pid !== null) {
ACTIVE_CHILDREN.delete(pid);
}
resolve(out);
}
function fin_err(msg: string): void {
if (done) {
return;
}
done = true;
clearTimeout(timer);
if (pid !== null) {
ACTIVE_CHILDREN.delete(pid);
}
reject(new Error(msg));
}
function on_chunk(bufs: Buffer[], chunk: Buffer, add_len: (n: number) => void): void {
bufs.push(chunk);
add_len(chunk.length);
if (out_len + err_len > MAX_BUFFER) {
if (pid !== null) {
kill_pg(pid);
} else {
try {
child.kill("SIGKILL");
} catch {}
}
fin_err("output exceeded max buffer: " + cmd_txt);
}
}
child.stdout.on("data", (chunk: Buffer | string) => {
var buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
on_chunk(out_bufs, buf, n => {
out_len += n;
});
});
child.stderr.on("data", (chunk: Buffer | string) => {
var buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(String(chunk));
on_chunk(err_bufs, buf, n => {
err_len += n;
});
});
child.on("error", err => {
fin_err(spawn_err_msg(cmd, err));
});
child.on("close", code => {
var out = Buffer.concat(out_bufs).toString("utf8");
var err = Buffer.concat(err_bufs).toString("utf8");
if (code === 0) {
fin_ok(out);
return;
}
var msg = err.trim();
if (msg.length === 0) {
msg = out.trim();
}
if (msg.length === 0) {
msg = "command failed: " + cmd_txt;
}
fin_err(msg);
});
var timer = setTimeout(() => {
if (pid !== null) {
kill_pg(pid);
} else {
try {
child.kill("SIGKILL");
} catch {}
}
fin_err(TIMEOUT_PREFIX + CMD_TIMEOUT_MS + "ms: " + cmd_txt);
}, CMD_TIMEOUT_MS);
});
}
// Sampling
// --------
// Returns whether sampling needs one more timed run.
function sample_needs_more(cnt: number, sum: number, cfg: SampleCfg): boolean {
if (cnt === 0) {
return true;
}
if (cnt >= cfg.max_runs) {
return false;
}
if (cnt < cfg.min_runs && sum < cfg.min_secs) {
return true;
}
return false;
}
// Runs one async callback with warmup and sampled averaging.
async function run_sampled(run_once: () => Promise<void>, cfg: SampleCfg): Promise<number> {
for (var i = 0; i < cfg.warmup; ++i) {
await run_once();
}
var sum = 0;
var cnt = 0;
while (sample_needs_more(cnt, sum, cfg)) {
var start = now_ns();
await run_once();
var secs = elapsed_secs(start);
sum += secs;
cnt += 1;
}
if (cnt === 0) {
return NaN;
}
return sum / cnt;
}
// Runs one sync callback with warmup and sampled averaging.
function run_hot_sync(run: () => unknown, cfg: SampleCfg): number {
for (var i = 0; i < cfg.warmup; ++i) {
run();
}
var sum = 0;
var cnt = 0;
while (sample_needs_more(cnt, sum, cfg)) {
var start = now_ns();
run();
var secs = elapsed_secs(start);
sum += secs;
cnt += 1;
}
if (cnt === 0) {
return NaN;
}
return sum / cnt;
}
// JS Helpers
// ----------
// Builds a cache-busting module URL.
function js_mod_url(file: string): string {
var base = url.pathToFileURL(file).href;
return base + "?v=" + now_ns().toString();
}
// Finds the callable benchmark main function in one module.
function js_main_get(mod: Record<string, unknown>): () => unknown {
var main = mod.$main;
if (typeof main === "function") {
return main as () => unknown;
}
var main = mod.main;
if (typeof main === "function") {
return main as () => unknown;
}
var main = mod[BEND_MAIN_JS_EXPORT];
if (typeof main === "function") {
return main as () => unknown;
}
throw new Error("compiled JS module is missing `main`/`$main`");
}
// Removes `run_main` trailer from `bend --to-js` output.
function bend_js_strip_run_main(js: string): string {
var src = js.replace(/\r/g, "");
var src = src.replace(/\s*$/s, "");
var pats = [
/\nvar __main_result = [^\n;]+;\nconsole\.log\(\s*__bend_show_result\(\s*__main_result\s*\)\s*\)\s*;?\s*$/s,
/\nvar __main_result = [^\n;]+;\nconsole\.log\(\s*typeof __main_result === 'bigint' \? __main_result\.toString\(\) : JSON\.stringify\(__main_result\)\s*\)\s*;?\s*$/s,
/\nconsole\.log\(\s*JSON\.stringify\(\s*[A-Za-z_$][A-Za-z0-9_$]*\(\)\s*\)\s*\)\s*;?\s*$/s,
/\nconsole\.log\(\s*JSON\.stringify\(\s*main\(\)\s*\)\s*\)\s*;?\s*$/s,
/\nconsole\.log\(\s*JSON\.stringify\(\s*\$main\(\)\s*\)\s*\)\s*;?\s*$/s,
/\nconsole\.log\(\s*JSON\.stringify\(\s*null\s*\)\s*\)\s*;?\s*$/s,
/\nrun_main\(\)\s*;?\s*$/s,
];
for (var pat of pats) {
var out = src.replace(pat, "\n");
if (out !== src) {
return out;
}
}
var lns = src.split("\n");
if (lns.length > 0) {
var lst = lns[lns.length - 1].trim();
if (
lst.startsWith("console.log(") &&
(
lst.includes("JSON.stringify(main())") ||
lst.includes("JSON.stringify($main())") ||
/JSON\.stringify\([A-Za-z_$][A-Za-z0-9_$]*\(\)\)/.test(lst) ||
lst.includes("JSON.stringify(null)")
)
) {
lns.pop();
return lns.join("\n") + "\n";
}
}
return src + "\n";
}
// Parses one BENCH_SECS line from helper output.
function secs_parse(out: string): number {
var tag = SECS_TAG + " ";
var lns = out.replace(/\r/g, "").split("\n");
for (var i = lns.length - 1; i >= 0; --i) {
var lin = lns[i].trim();
if (!lin.startsWith(tag)) {
continue;
}
var txt = lin.slice(tag.length).trim();
var val = Number(txt);
if (!Number.isFinite(val) || val < 0) {
throw new Error("invalid " + SECS_TAG + " value: " + JSON.stringify(txt));
}
return val;
}
throw new Error("missing " + SECS_TAG + " in output");
}
// Builds source for the Node JS hot-run helper.
function js_node_runner_src(): string {
return [
"import * as url from \"node:url\";",
"",
"function now_ns() {",
" return process.hrtime.bigint();",
"}",
"",
"function elapsed_secs(start) {",
" return Number(process.hrtime.bigint() - start) / 1e9;",
"}",
"",
"function parse_num(txt, nam) {",
" var val = Number(txt);",
" if (!Number.isFinite(val) || val < 0) {",
" throw new Error(\"invalid numeric arg: \" + nam + \"=\" + JSON.stringify(txt));",
" }",
" return val;",
"}",
"",
"function parse_nonneg_int(txt, nam) {",
" var val = Math.floor(parse_num(txt, nam));",
" if (val < 0) {",
" throw new Error(\"invalid non-negative int arg: \" + nam + \"=\" + JSON.stringify(txt));",
" }",
" return val;",
"}",
"",
"function parse_pos_int(txt, nam) {",
" var val = Math.floor(parse_num(txt, nam));",
" if (val <= 0) {",
" throw new Error(\"invalid positive int arg: \" + nam + \"=\" + JSON.stringify(txt));",
" }",
" return val;",
"}",
"",
"function sample_needs_more(cnt, sum, min_runs, max_runs, min_secs) {",
" if (cnt === 0) {",
" return true;",
" }",
" if (cnt >= max_runs) {",
" return false;",
" }",
" if (cnt < min_runs && sum < min_secs) {",
" return true;",
" }",
" return false;",
"}",
"",
"function js_main_get(mod) {",
" var main = mod.$main;",
" if (typeof main === \"function\") {",
" return main;",
" }",
" var main = mod.main;",
" if (typeof main === \"function\") {",
" return main;",
" }",
" var main = mod[" + JSON.stringify(BEND_MAIN_JS_EXPORT) + "];",
" if (typeof main === \"function\") {",
" return main;",
" }",
" throw new Error(\"compiled JS module is missing `main`/`$main`\");",
"}",
"",
"async function main() {",
" var args = process.argv.slice(2);",
" if (args.length !== 5) {",
" throw new Error(\"usage: node runner.mjs <mod> <warmup> <min_runs> <max_runs> <min_secs>\");",
" }",
"",
" var mod_fil = args[0];",
" var warmup = parse_nonneg_int(args[1], \"warmup\");",
" var min_runs = parse_pos_int(args[2], \"min_runs\");",
" var max_runs = parse_pos_int(args[3], \"max_runs\");",
" var min_secs = parse_num(args[4], \"min_secs\");",
"",
" var mod = await import(url.pathToFileURL(mod_fil).href + \"?v=\" + String(now_ns()));",
" var run = js_main_get(mod);",
"",
" for (var i = 0; i < warmup; ++i) {",
" run();",
" }",
"",
" var sum = 0;",
" var cnt = 0;",
" while (sample_needs_more(cnt, sum, min_runs, max_runs, min_secs)) {",
" var start = now_ns();",
" run();",
" var secs = elapsed_secs(start);",
" sum += secs;",
" cnt += 1;",
" }",
"",
" if (cnt === 0) {",
" throw new Error(\"no timed runs were executed\");",
" }",
"",
" console.log(\"" + SECS_TAG + " \" + String(sum / cnt));",
"}",
"",
"main().catch(err => {",
" var msg = err instanceof Error ? err.message : String(err);",
" console.error(msg);",
" process.exit(1);",
"});",
"",
].join("\n");
}
var JS_NODE_RUNNER: string | null = null;
// Returns path to the shared Node JS hot-run helper.
function js_node_runner_path(): string {
if (JS_NODE_RUNNER !== null) {
return JS_NODE_RUNNER;
}
var file = tmp_path(["js", "node", "runner"], ".mjs");
fs.writeFileSync(file, js_node_runner_src(), "utf8");
JS_NODE_RUNNER = file;
return file;
}
// Repo Discovery
// --------------
// Discovers benchmarks under `bench/*`.
function discover_rows(): Row[] {
if (!fs.existsSync(CASE_DIR)) {
return [];
}
var rows: Row[] = [];
var entries = fs.readdirSync(CASE_DIR);
entries.sort();
for (var name of entries) {
var dir = path.join(CASE_DIR, name);
var st = fs.statSync(dir);
if (!st.isDirectory()) {
continue;
}
var bend_file = path.join(dir, "main.bend");
var hvm_file = path.join(dir, "main.hvm");
var ts_file = path.join(dir, "main.ts");
var has_bend = fs.existsSync(bend_file);
var has_hvm = fs.existsSync(hvm_file);
var has_ts = fs.existsSync(ts_file);
if (!has_bend && !has_hvm && !has_ts) {
continue;
}
rows.push({
name,
bend_file: has_bend ? bend_file : null,
hvm_file: has_hvm ? hvm_file : null,
ts_file: has_ts ? ts_file : null,
cells: {},
});
}
return rows;
}
// Caches
// ------
var BEND_JS_CACHE = new Map<string, string>();
var BEND_HVM_CACHE = new Map<string, string>();
var HVM_BIN_CACHE = new Map<string, string>();
var TS_NODE_CACHE = new Map<string, string>();
// HVM Helpers
// -----------
// Returns extra HVM args for known benchmark families.
function hvm_extra_args(name: string): string[] {
if (name.startsWith("gen_")) {
return ["-C1"];
}
if (name.startsWith("collapse_")) {
return ["-C"];
}
return [];
}
// Runs one `.hvm` file once via interpreted mode.
async function hvm_run_file_once(file: string, name: string, threads: number): Promise<void> {
var args = [file, "-S", "-T" + String(threads)].concat(hvm_extra_args(name));
await run_cmd(HVM_CMD, args, ROOT_DIR);
}
// Runs one compiled benchmark executable once.
async function hvm_run_bin_once(bin: string): Promise<void> {
await run_cmd(bin, [], ROOT_DIR);
}
// Compiles one `.hvm` file to binary and caches output path.
async function hvm_compile_bin(file: string, name: string, flow: string, threads: number): Promise<string> {
var key = [flow, String(threads), file, hvm_extra_args(name).join(" ")].join("|");
var old = HVM_BIN_CACHE.get(key);
if (old !== undefined) {
return old;
}
var out = tmp_path([flow, String(threads), bench_tag(file)], ".bin");
var args = [file, "-S", "-T" + String(threads)].concat(hvm_extra_args(name), ["-o", out]);
await run_cmd(HVM_CMD, args, ROOT_DIR);
HVM_BIN_CACHE.set(key, out);
return out;
}
// Bend Helpers
// ------------
var BEND_ENTRY_CACHE = new Map<string, string>();
var BEND_NODE_BUNDLE_CACHE = new Map<string, string>();
// Returns node-entry override for one Bend command, if configured.
function bend_node_entry_override_get(bend_cmd: string): string | null {
if (bend_cmd === BEND_CMD) {
return BEND_NODE_ENTRY;
}
if (bend_cmd === NEW_BEND_CMD) {
return NEW_BEND_NODE_ENTRY;
}
return null;
}
// Resolves script path behind one Bend command for node strip-types mode.
function bend_entry_get(bend_cmd: string): string {
var old = BEND_ENTRY_CACHE.get(bend_cmd);
if (old !== undefined) {
return old;
}
var exe = resolve_cmd_path(bend_cmd);
var ext = path.extname(exe).toLowerCase();
if (ext !== ".ts" && ext !== ".js" && ext !== ".mjs" && ext !== ".cjs") {
throw new Error("could not derive script entry from bend command: " + exe + " (set *_BEND_NODE_ENTRY)");
}
var entry = bend_node_entry_override_get(bend_cmd) ?? exe;
BEND_ENTRY_CACHE.set(bend_cmd, entry);
return entry;
}
// Builds (or returns) a Node-runnable bundled Bend CLI entry.
async function bend_node_bundle_get(bend_cmd: string): Promise<string> {
var old = BEND_NODE_BUNDLE_CACHE.get(bend_cmd);
if (old !== undefined) {
return old;
}
var entry = bend_entry_get(bend_cmd);
var out = tmp_path(["bend", "node", "bundle", bend_cmd], ".mjs");
await js_node_bundle_make(entry, out);
BEND_NODE_BUNDLE_CACHE.set(bend_cmd, out);
return out;
}
// Bundles one TS/JS entry into one Node-runnable ESM file.
async function js_node_bundle_make(entry: string, out: string): Promise<void> {
await run_cmd(BUN_CMD, [
"build",
entry,
"--target=node",
"--format=esm",
"--outfile",
out,
], ROOT_DIR);
}
// Compiles one `.bend` file to `.hvm` through Bend CLI and caches output.
async function bend_compile_hvm(
file: string,
cli_flag: "--to-hvm" | "--to-chk",
bend_cmd: string = BEND_CMD,
): Promise<string> {
var key = bend_cmd + "|" + cli_flag + "|" + file;
var old = BEND_HVM_CACHE.get(key);
if (old !== undefined) {
return old;
}
var out = await run_cmd(bend_cmd, [file, cli_flag], ROOT_DIR);
var fil = tmp_path(["bend", cli_flag, bend_cmd, bench_tag(file)], ".hvm");
fs.writeFileSync(fil, out, "utf8");
BEND_HVM_CACHE.set(key, fil);
return fil;
}
// Compiles one `.bend` file to JS library (main exported, no auto-run).
async function bend_compile_js_lib(file: string, bend_cmd: string = BEND_CMD): Promise<string> {
var key = bend_cmd + "|" + file;
var old = BEND_JS_CACHE.get(key);
if (old !== undefined) {
return old;
}
var js = await run_cmd(bend_cmd, [file, "--to-js"], ROOT_DIR);
var js = bend_js_strip_run_main(js);
var out = tmp_path(["bend", "js", bend_cmd, bench_tag(file)], ".mjs");
fs.writeFileSync(out, js, "utf8");
BEND_JS_CACHE.set(key, out);
return out;
}
// Runs one Bend benchmark once via Bun runtime.
async function bend_run_once_bun(file: string, bend_cmd: string = BEND_CMD): Promise<void> {
var entry = bend_entry_get(bend_cmd);
await run_cmd(BUN_CMD, ["run", entry, file], ROOT_DIR);
}
// Runs one Bend benchmark once via Node strip-types runtime.
async function bend_run_once_node(file: string, bend_cmd: string = BEND_CMD): Promise<void> {
var bundle = await bend_node_bundle_get(bend_cmd);
await run_cmd(NODE_CMD, [bundle, file], ROOT_DIR);
}
// Builds one TS benchmark into one Node-runnable module and caches it.
async function ts_node_bundle_get(file: string): Promise<string> {
var old = TS_NODE_CACHE.get(file);
if (old !== undefined) {
return old;
}
var out = tmp_path(["ts", "node", bench_tag(file)], ".mjs");
await js_node_bundle_make(file, out);
TS_NODE_CACHE.set(file, out);
return out;
}
// Runs one JS/TS module hot in Bun.
async function js_run_hot_bun(mod_file: string, cfg: SampleCfg): Promise<number> {
var mod = await import(js_mod_url(mod_file));
var run = js_main_get(mod as Record<string, unknown>);
return run_hot_sync(run, cfg);
}
// Runs one JS/TS module hot in Node.
async function js_run_hot_node(mod_file: string, cfg: SampleCfg): Promise<number> {
var runner = js_node_runner_path();
var out = await run_cmd(NODE_CMD, [
runner,
mod_file,
String(cfg.warmup),
String(cfg.min_runs),
String(cfg.max_runs),
String(cfg.min_secs),
], ROOT_DIR);
return secs_parse(out);
}
// Modes
// -----
// Runs native Bend via Bun (compile once, hot-call main in-process).
async function mode_bend_bun(row: Row, cfg: SampleCfg, _threads: number, bend_cmd: string | null): Promise<number> {
var file = row.bend_file;
if (file === null) {
throw new Error("internal: missing bend file");
}
if (bend_cmd === null) {