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
#![allow(clippy::single_element_loop)] // see soft-deprecate-tree: arrays shrank from 2-3 engines to 1
// Regression: Cranelift JIT silently produced NaN / inf where tree and VM
// raised ILO-R003 division by zero. Discovered in the ml-engineer rerun8
// Adult-dataset workload where `zsc` over a constant column produced
// `bias=NaN w1=NaN w2=NaN` but a plausible 0.76 majority-class accuracy on
// Cranelift; tree + VM correctly errored.
//
// Two bugs were rolled together:
// 1. The Cranelift fast-path inline-fdiv emit (OP_DIV_NN, OP_DIVK_N, and the
// both_always_num / num_block branches of OP_DIV) skipped the zero check
// that `jit_div` performed, producing silent NaN / inf.
// 2. The AOT `compile_cranelift` path also inlined `mod` as fdiv/trunc/fmul
// /fsub with no zero guard.
//
// Fix:
// * Added a tiny `jit_raise_divzero(span_bits)` extern in `src/vm/mod.rs`
// that sets `VmError::DivisionByZero` on the per-thread cell.
// * At every inline-fdiv site in `jit_cranelift.rs` / `compile_cranelift.rs`
// emit an `fcmp == 0.0 -> brif` guard that calls the helper on the zero
// edge. Constant divisor sites (OP_DIVK_N) resolve at compile time.
// * Rejected `OP_DIV_NN` and zero-const `OP_DIVK_N` from the leaf-inline
// emitter so chunks fall back to the guarded main path.
// * AOT `OP_MOD` now routes through the existing `jit_mod` helper.
//
// These tests assert cross-engine parity: every engine surfaces ILO-R003
// "division by zero" for `/`. For `mod`-by-zero we only assert that no engine
// produces silent NaN; the existing tree-vs-VM R003/R004 mismatch is tracked
// separately in ilo_assessment_feedback.md.
use std::process::Command;
fn ilo() -> Command {
Command::new(env!("CARGO_BIN_EXE_ilo"))
}
/// Run `ilo` with inline source + engine flag + entry args, returning
/// (stdout, stderr, exit-code). Mirrors `regression_cranelift_error_span`.
fn run_engine(engine: &str, src: &str, args: &[&str]) -> (String, String, i32) {
let mut cmd = ilo();
cmd.arg(src).arg(engine).arg("f");
for a in args {
cmd.arg(a);
}
let out = cmd.output().expect("failed to run ilo");
(
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
out.status.code().unwrap_or(-1),
)
}
fn assert_engine_errs(engine: &str, src: &str, args: &[&str], code: &str, msg: &str) {
let (stdout, stderr, ec) = run_engine(engine, src, args);
assert_ne!(
ec, 0,
"engine={engine} src={src:?} args={args:?} expected error exit, got 0\nstdout={stdout}\nstderr={stderr}"
);
assert!(
stderr.contains(code),
"engine={engine} src={src:?} expected `{code}` in stderr, got {stderr}"
);
assert!(
stderr.contains(msg),
"engine={engine} src={src:?} expected `{msg}` in stderr, got {stderr}"
);
}
fn assert_all_engines_err(src: &str, args: &[&str], code: &str, msg: &str) {
for engine in ["--vm"] {
assert_engine_errs(engine, src, args, code, msg);
}
#[cfg(feature = "cranelift")]
{
assert_engine_errs("--jit", src, args, code, msg);
}
}
fn assert_engine_fails(engine: &str, src: &str, args: &[&str], msg: &str) {
let (stdout, stderr, ec) = run_engine(engine, src, args);
assert_ne!(
ec, 0,
"engine={engine} src={src:?} args={args:?} expected error exit, got 0\nstdout={stdout}\nstderr={stderr}"
);
assert!(
stderr.contains(msg),
"engine={engine} src={src:?} expected `{msg}` in stderr, got {stderr}"
);
}
/// Like `assert_all_engines_err` but only asserts every engine errors (without
/// locking in the code). Used for `mod`-by-zero where tree returns R003 and
/// VM/Cranelift return R004 — pre-existing parity gap.
fn assert_all_engines_fail(src: &str, args: &[&str], msg: &str) {
for engine in ["--vm"] {
assert_engine_fails(engine, src, args, msg);
}
#[cfg(feature = "cranelift")]
{
assert_engine_fails("--jit", src, args, msg);
}
}
// ── `/` zero divisor: every engine must raise ILO-R003 ──────────────────
#[test]
fn div_by_literal_zero_cross_engine() {
// OP_DIVK_N path: literal zero constant divisor. Compile-time check fires.
assert_all_engines_err("f a:n>n;/a 0", &["10"], "ILO-R003", "division by zero");
}
#[test]
fn div_by_runtime_zero_cross_engine() {
// OP_DIV_NN / OP_DIV path: divisor computed at runtime so the inline
// fdiv-fast-path emit must check it dynamically. Previously NaN'd silently.
assert_all_engines_err(
"f a:n b:n>n;/a b",
&["10", "0"],
"ILO-R003",
"division by zero",
);
}
#[test]
fn div_zero_over_zero_cross_engine() {
// 0 / 0 specifically produced NaN on Cranelift (vs +inf for 10/0). Cover
// both NaN and inf branches of the previous silent-failure space.
assert_all_engines_err(
"f a:n b:n>n;/a b",
&["0", "0"],
"ILO-R003",
"division by zero",
);
}
#[test]
fn div_by_zero_runtime_computed_cross_engine() {
// The originating ml-engineer bug had the divisor computed at runtime via
// a `sqrt(variance)` that hit zero. Mirror that without the lambda parser
// surface area: compute the zero divisor from two equal runtime numbers.
assert_all_engines_err(
"f a:n b:n>n;d=- b a\n /a d",
&["5", "5"],
"ILO-R003",
"division by zero",
);
}
// ── `mod` zero divisor: every engine must fail (no silent NaN) ──────────
#[test]
fn mod_by_zero_cross_engine_fails() {
// Tree returns R003, VM/Cranelift return R004 (pre-existing parity gap).
// The point of this test is that Cranelift no longer silently NaN's
// through the AOT-inlined fdiv/trunc/fmul/fsub expansion.
assert_all_engines_fail("f a:n b:n>n;mod a b", &["10", "0"], "modulo by zero");
}
// ── Sanity: safe-divisor fast paths still produce correct results ───────
#[test]
fn div_safe_runtime_divisor_cross_engine_agrees() {
for engine in ["--vm"] {
let (stdout, _, ec) = run_engine(engine, "f a:n b:n>n;/a b", &["10", "4"]);
assert_eq!(ec, 0, "engine={engine} unexpected error");
assert_eq!(stdout.trim(), "2.5", "engine={engine}");
}
#[cfg(feature = "cranelift")]
{
let (stdout, _, ec) = run_engine("--jit", "f a:n b:n>n;/a b", &["10", "4"]);
assert_eq!(ec, 0, "engine=cranelift unexpected error");
assert_eq!(stdout.trim(), "2.5");
}
}
#[test]
fn div_const_nonzero_fast_path_cross_engine_agrees() {
// OP_DIVK_N with non-zero literal divisor: ensures the compile-time
// "k != 0 → no guard" branch still emits a working fdiv.
for engine in ["--vm"] {
let (stdout, _, ec) = run_engine(engine, "f a:n>n;/a 4", &["10"]);
assert_eq!(ec, 0, "engine={engine} unexpected error");
assert_eq!(stdout.trim(), "2.5", "engine={engine}");
}
#[cfg(feature = "cranelift")]
{
let (stdout, _, ec) = run_engine("--jit", "f a:n>n;/a 4", &["10"]);
assert_eq!(ec, 0, "engine=cranelift unexpected error");
assert_eq!(stdout.trim(), "2.5");
}
}