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
// Regression: cranelift JIT runtime errors must carry the same source span
// as tree / VM so the diagnostic renderer can underline the offending site.
//
// Before this fix, the JIT runtime-error TLS cell stored only `VmError`, so
// every cranelift error surfaced with `span: None` and the diagnostic JSON
// emitted an empty `labels: []`. The fix packs the call-site span into a
// u64 immediate at compile time and threads it through the helper signature
// for `jit_hd`, `jit_at`, `jit_tl`. These tests assert byte-for-byte parity
// of the `labels[0]` field across tree, VM, and cranelift for the v1 helpers.
use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
/// Run `ilo` in JSON-diagnostic mode and return stderr.
fn run_err(engine: &str, src: &str) -> String {
let out = ilo()
.args([src, engine, "f"])
.output()
.expect("failed to run ilo");
assert!(
!out.status.success(),
"engine={engine}: expected runtime error for `{src}`, stdout={}",
String::from_utf8_lossy(&out.stdout)
);
String::from_utf8_lossy(&out.stderr).into_owned()
}
/// Extract `"start":NUM,"end":NUM` from a diagnostic JSON label. Returns
/// `None` if the line has no label (i.e. `labels: []`).
fn extract_span(stderr: &str) -> Option<(u32, u32)> {
// Diagnostic JSON looks like:
// "labels":[{"col":7,"end":11,"line":1,"message":"here","primary":true,"start":6}]
// We want start/end. Bail if labels is empty.
if stderr.contains("\"labels\":[]") {
return None;
}
let start_key = "\"start\":";
let end_key = "\"end\":";
let s_pos = stderr.find(start_key)?;
let after_start = &stderr[s_pos + start_key.len()..];
let start: u32 = after_start
.split(|c: char| !c.is_ascii_digit())
.next()?
.parse()
.ok()?;
let e_pos = stderr.find(end_key)?;
let after_end = &stderr[e_pos + end_key.len()..];
let end: u32 = after_end
.split(|c: char| !c.is_ascii_digit())
.next()?
.parse()
.ok()?;
Some((start, end))
}
/// Asserts that cranelift produces the same `(start, end)` span as VM for the
/// same source program. Tree is checked too as a sanity baseline.
#[cfg(feature = "cranelift")]
fn assert_span_parity(src: &str) {
let vm_err = run_err("--vm", src);
let cl_err = run_err("--jit", src);
let vm_span = extract_span(&vm_err)
.unwrap_or_else(|| panic!("VM stderr had no labels for `{src}`: {vm_err}"));
let cl_span = extract_span(&cl_err).unwrap_or_else(|| {
panic!(
"cranelift stderr had no labels for `{src}` (span did not survive JIT helper): {cl_err}"
)
});
assert_eq!(
cl_span, vm_span,
"engine span divergence for `{src}`: cranelift={cl_span:?} vs vm={vm_span:?}\nvm stderr={vm_err}\ncl stderr={cl_err}"
);
}
// ── jit_hd ────────────────────────────────────────────────────────────
#[test]
#[cfg(feature = "cranelift")]
fn hd_empty_list_span_matches_vm() {
assert_span_parity("f>n;hd []");
}
#[test]
#[cfg(feature = "cranelift")]
fn hd_empty_text_span_matches_vm() {
assert_span_parity("f>t;hd \"\"");
}
// ── jit_tl ────────────────────────────────────────────────────────────
#[test]
#[cfg(feature = "cranelift")]
fn tl_empty_list_span_matches_vm() {
assert_span_parity("f>L n;tl []");
}
#[test]
#[cfg(feature = "cranelift")]
fn tl_empty_text_span_matches_vm() {
assert_span_parity("f>t;tl \"\"");
}
// ── jit_at ────────────────────────────────────────────────────────────
#[test]
#[cfg(feature = "cranelift")]
fn at_oob_positive_list_span_matches_vm() {
assert_span_parity("f>n;at [1,2,3] 99");
}
#[test]
#[cfg(feature = "cranelift")]
fn at_oob_negative_list_span_matches_vm() {
assert_span_parity("f>n;at [1,2,3] -99");
}
#[test]
#[cfg(feature = "cranelift")]
fn at_oob_text_span_matches_vm() {
assert_span_parity("f>t;at \"hi\" 99");
}
// Fractional indices used to error here; auto-floor turned that into a
// successful element fetch (see examples/at-float-index.ilo). The jit_at
// runtime error path is still covered above by `at_oob_*_span_matches_vm`.
// ── Sanity: a span must actually be present (not None) ────────────────
//
// Belt-and-braces guard against a future regression that silently drops to
// `span: None` without breaking the cross-engine equality above (which would
// fail because vm has a span and cl doesn't, but better to assert presence
// explicitly).
#[test]
#[cfg(feature = "cranelift")]
fn cranelift_hd_error_carries_some_span() {
let stderr = run_err("--jit", "f>n;hd []");
assert!(
!stderr.contains("\"labels\":[]"),
"cranelift hd error must carry a source span, got: {stderr}"
);
let (start, end) = extract_span(&stderr).expect("expected start/end in stderr");
assert!(
end > start,
"span end must be > start, got start={start} end={end}"
);
}