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
//! `interpreter_hostless` must FORK the prebuilt substrate, never rebuild it.
//!
//! Guards a measured 48× regression risk, and it is a cost gate rather than a
//! correctness gate for a specific reason: rebuilding the stdlib per run is
//! perfectly *correct*. Every behavioural test stays green while it happens.
//! Only a timing assertion can see it, which is why this file exists next to
//! the ones that check what the interpreter computes.
//!
//! ## What was measured (2026-08-01)
//!
//! Profiling a trivial blue program found one dominant cost:
//!
//! ```text
//! parse 9.6 µs
//! check 1.6 µs
//! interpreter_hostless() 5 820 µs <- 98.4% of the run
//! ```
//!
//! `Interpreter::new()` is 24 µs of that; the rest is `install_full_stdlib_with`
//! rebuilding blue's whole surface (`map`, `filter`, string ops) from scratch on
//! every single run. `fork` already existed upstream for exactly this — its own
//! `fork_cost.rs` opens with *"cheapness is the entire reason it exists"* — and
//! blue was not using it.
//!
//! | | per call |
//! |---|---|
//! | rebuild | 9.03 ms |
//! | fork | 0.102 ms |
//! | config-shaped `run()` before | 5.92 ms |
//! | config-shaped `run()` after | 0.12 ms |
//!
//! ## Why the threshold is loose
//!
//! A wall-clock assert on shared CI is a flake generator, so the bound is set
//! at 20× the observed fork cost — far above timing noise, far below the
//! rebuild it guards against. It is a TRIPWIRE for "someone reintroduced the
//! rebuild", not a benchmark. Tightening it to look impressive would convert a
//! reliable gate into an intermittent one, which trains people to ignore it.
use Instant;
/// Forking must be dramatically cheaper than rebuilding — the property the
/// optimisation rests on.
///
/// Compares the two directly in-process, so a machine being slow moves BOTH
/// arms and the ratio survives. An absolute threshold would not.
/// The forked interpreter must actually work — cheapness is worthless if the
/// substrate is not there.
///
/// Probes BOTH substrate layers a fork could lose, which is why there are two
/// cases rather than one:
///
/// - `1 + 2` exercises tatara-lisp's own installed primitives.
/// - `"ab".length` exercises **blue's layer-3 stdlib** (`install_blue_stdlib`),
/// which the interpreter builder documents as absent from tatara-lisp
/// entirely. A fork that carried the base and dropped blue's own layer would
/// pass a single-case test while breaking every string operation.
///
/// The first draft of this test used `[1,2,3].map { |x| x * 2 }` and failed —
/// with a **Parse** error, not an eval one. Blue has no Ruby block syntax yet,
/// so the probe never reached the interpreter and proved nothing about the
/// fork. Recorded because the failure mode is instructive: a substrate test
/// written in syntax the language does not have reports a runtime defect that
/// is really a test bug, and it fails in the direction that looks alarming.
/// Repeated runs stay cheap, which is the case that actually matters.
///
/// shikumi loading a `.b` config and an LSP re-evaluating on keystroke both do
/// exactly this. Before the fork each of those paid a full stdlib build.