mod common;
use std::path::{Path, PathBuf};
use std::sync::OnceLock;
mod probes {
pub const ANY: &str = r#"
use ijson::{INumber, IString, IValue, ValueType};
// Constructing, from an input known at compile time.
#[no_mangle] pub fn probe_null() -> IValue { IValue::NULL }
#[no_mangle] pub fn probe_true() -> IValue { IValue::from(true) }
#[no_mangle] pub fn probe_false() -> IValue { IValue::from(false) }
#[no_mangle] pub fn probe_empty_string() -> IString { IString::from("") }
#[no_mangle] pub fn probe_inline_string() -> IString { IString::from("abc") }
#[no_mangle] pub fn probe_zero() -> INumber { INumber::from(0i64) }
#[no_mangle] pub fn probe_small_int() -> INumber { INumber::from(42i64) }
#[no_mangle] pub fn probe_negative_int() -> INumber { INumber::from(-1i64) }
#[no_mangle] pub fn probe_half() -> IValue { IValue::from(0.5f64) }
// Reading, from a value whose representation is not known at compile time.
#[no_mangle] pub fn probe_to_i64(n: &INumber) -> Option<i64> { n.to_i64() }
#[no_mangle] pub fn probe_to_u64(n: &INumber) -> Option<u64> { n.to_u64() }
#[no_mangle] pub fn probe_to_f64(n: &INumber) -> Option<f64> { n.to_f64() }
#[no_mangle] pub fn probe_to_f64_lossy(n: &INumber) -> f64 { n.to_f64_lossy() }
#[no_mangle] pub fn probe_has_decimal_point(n: &INumber) -> bool { n.has_decimal_point() }
#[no_mangle] pub fn probe_is_number(v: &IValue) -> bool { v.is_number() }
#[no_mangle] pub fn probe_type(v: &IValue) -> ValueType { v.type_() }
"#;
pub const FAST: &str = r#"
use ijson::codegen_probes::{assume_inline, assume_inline_integer};
use ijson::{INumber, IValue, ValueType};
#[no_mangle] pub fn probe_fast_is_number(n: &INumber) -> bool {
unsafe { assume_inline(n) };
AsRef::<IValue>::as_ref(n).is_number()
}
#[no_mangle] pub fn probe_fast_type(n: &INumber) -> ValueType {
unsafe { assume_inline(n) };
AsRef::<IValue>::as_ref(n).type_()
}
#[no_mangle] pub fn probe_fast_has_decimal_point(n: &INumber) -> bool {
unsafe { assume_inline(n) };
n.has_decimal_point()
}
#[no_mangle] pub fn probe_int_has_decimal_point(n: &INumber) -> bool {
unsafe { assume_inline_integer(n) };
n.has_decimal_point()
}
#[no_mangle] pub fn probe_int_to_i64(n: &INumber) -> Option<i64> {
unsafe { assume_inline_integer(n) };
n.to_i64()
}
#[no_mangle] pub fn probe_int_to_u64(n: &INumber) -> Option<u64> {
unsafe { assume_inline_integer(n) };
n.to_u64()
}
#[no_mangle] pub fn probe_int_to_f64_lossy(n: &INumber) -> f64 {
unsafe { assume_inline_integer(n) };
n.to_f64_lossy()
}
"#;
}
#[allow(clippy::identity_op)]
fn constants() -> Vec<(&'static str, i64, &'static str)> {
let half = if cfg!(feature = "arbitrary_precision") {
(5 << 8) | (6 << 4) | 8
} else {
(1 << 8) | (6 << 4) | 8
};
vec![
(
"probe_null",
1 << 5,
"Null: discriminant 1 in the payload bits",
),
("probe_true", 3 << 5, "True: discriminant 3"),
("probe_false", 2 << 5, "False: discriminant 2"),
(
"probe_empty_string",
1 << 4,
"IS_STRING alone: length 0, no bytes — and still non-zero, so the empty string \
is not the reserved niche",
),
(
"probe_inline_string",
(i64::from(b'c') << 24)
| (i64::from(b'b') << 16)
| (i64::from(b'a') << 8)
| (1 << 4)
| (3 << 5),
"\"abc\": control byte 0x70, then the three bytes",
),
(
"probe_zero",
(0 << 8) | (15 << 4) | 8,
"integer 0: a zero mantissa at the reserved exponent code — and *not* the \
all-zero niche, because IS_NUMBER is set",
),
(
"probe_small_int",
(42 << 8) | (15 << 4) | 8,
"integer 42: mantissa 42 at the reserved exponent code",
),
(
"probe_negative_int",
(-1 << 8) | (15 << 4) | 8,
"integer -1: the mantissa is signed, so the top bits are all ones",
),
("probe_half", half, "0.5, in the active inline number base"),
]
}
#[test]
#[cfg_attr(
any(miri, not(all(target_pointer_width = "64", target_endian = "little"))),
ignore = "needs a compiler to shell out to, and a 64-bit little-endian target to expect these words of"
)]
fn constructing_a_small_value_folds_to_a_constant() {
let ir = any_ir();
let mut failures = Vec::new();
for (name, word, meaning) in constants() {
let Some(body) = common::body_of(ir, name) else {
failures.push(format!("{}: not found in the emitted IR", name));
continue;
};
let want = format!("ret ptr inttoptr (i64 {} to ptr)", word);
if body != [want.as_str()] {
failures.push(format!(
"{} ({})\n expected: {}\n got: {}",
name,
meaning,
want,
body.join("\n ")
));
}
}
assert!(
failures.is_empty(),
"constructing a small value no longer folds to its constant:\n\n{}\n\n\
Each of these should compile to a single `ret` of the encoded inline word. A \
*different constant* most likely means the inline bit layout changed — decode it \
against the table in this file's module docs. Anything other than a lone `ret` \
means the constructor stopped folding, so building a small value now costs real \
work at run time.",
failures.join("\n\n")
);
}
struct FastPath {
name: &'static str,
allowed: &'static [&'static str],
because: &'static str,
}
fn fast_paths() -> Vec<FastPath> {
const UNWRAP: &str = "unwrap_failed";
const NUMERIC: &str = "numeric";
let dispatch_only = |name| FastPath {
name,
allowed: &[],
because: "asking a value's type is a switch on the tag and nothing more",
};
let converts = |name| FastPath {
name,
allowed: &[NUMERIC],
because: "a conversion may call into the numeric model, and nothing else",
};
vec![
dispatch_only("probe_is_number"),
dispatch_only("probe_type"),
dispatch_only("probe_has_decimal_point"),
converts("probe_to_i64"),
converts("probe_to_u64"),
converts("probe_to_f64"),
FastPath {
name: "probe_to_f64_lossy",
allowed: &[NUMERIC, UNWRAP],
because: "a conversion may call into the numeric model; and `INumber::to_f64_lossy` unwraps, asserting that an `INumber` really is a number",
},
]
}
fn cost_of(symbol: &str) -> &'static str {
if symbol.contains("panic") || symbol.contains("unwrap_failed") {
"panics"
} else if symbol.contains("__rust_alloc") || symbol.contains("__rust_realloc") {
"allocates"
} else {
"is not inlined — the work should be straight-line code here"
}
}
#[test]
#[cfg_attr(miri, ignore = "shells out to a compiler, which Miri cannot run")]
fn reading_a_value_stays_on_the_fast_path() {
let ir = any_ir();
let mut failures = Vec::new();
for probe in fast_paths() {
let Some(body) = common::body_of(ir, probe.name) else {
failures.push(format!("{}: not found in the emitted IR", probe.name));
continue;
};
assert!(!body.is_empty(), "{}: empty body", probe.name);
let called = common::called_symbols(&body);
if body.iter().any(|line| common::is_indirect_call(line)) {
failures.push(format!(
"{}: calls through a function pointer — the representation dispatch is no \
longer devirtualized",
probe.name
));
}
for symbol in called {
if probe.allowed.iter().any(|allowed| symbol.contains(allowed)) {
continue;
}
failures.push(format!(
"{}: {} — calls `{}`\n (all it may call: {})",
probe.name,
cost_of(symbol),
symbol,
probe.because
));
}
}
assert!(
failures.is_empty(),
"reading a value no longer stays on the fast path:\n\n {}\n\n\
These run in the innermost loop of anything that walks a document, and every cost \
above is invisible from behaviour alone — the results stay correct, they just stop \
being cheap.\n\n\
If an operation has started to panic, look for an `unreachable!()` on a state that \
cannot occur: it is indeed unreachable, but the compiler does not know that, so it \
emits the panic and its `Arguments` into every caller. State the invariant with a \
`debug_assert!` over a total fallback instead — checked where checks are \
affordable, and generating nothing where they are not.",
failures.join("\n "),
);
}
fn fast_paths_exactly() -> Vec<(&'static str, &'static [&'static str], &'static str)> {
vec![
(
"probe_fast_is_number",
&["ret"],
"an inline number is a number: nothing to compute",
),
(
"probe_fast_type",
&["ret"],
"and its type is a constant, for the same reason",
),
(
"probe_fast_has_decimal_point",
&["load", "ptrtoint", "and", "icmp", "ret"],
"read the word, and compare the exponent code against the one that means \
`integer`",
),
(
"probe_int_has_decimal_point",
&["ret"],
"an integer has no decimal point: once the exponent code is known, a constant",
),
(
"probe_int_to_i64",
&["load", "ptrtoint", "ashr", "insertvalue", "ret"],
"read the word, shift the mantissa down (an arithmetic shift, so it arrives \
sign-extended), and wrap it in `Some`",
),
(
"probe_int_to_u64",
&[
"load",
"ptrtoint",
"ashr",
"icmp",
"zext",
"insertvalue",
"insertvalue",
"ret",
],
"the same, and a sign test — a negative integer is not a `u64`",
),
(
"probe_int_to_f64_lossy",
&["load", "ptrtoint", "ashr", "sitofp", "ret"],
"the same shift, and one conversion instruction",
),
]
}
#[test]
#[cfg_attr(miri, ignore = "shells out to a compiler, which Miri cannot run")]
fn the_fast_path_is_exactly_this() {
let ir = fast_ir();
let mut failures = Vec::new();
for (name, expected, meaning) in fast_paths_exactly() {
let Some(body) = common::fast_path_of(ir, name) else {
failures.push(format!("{}: not found in the emitted IR", name));
continue;
};
let actual = common::opcodes(&body);
if actual != expected {
failures.push(format!(
"{} — {}\n expected: {}\n got: {}\n{}",
name,
meaning,
expected.join(", "),
actual.join(", "),
body.iter()
.map(|line| format!(" {}\n", line))
.collect::<String>(),
));
}
}
assert!(
failures.is_empty(),
"the fast path is no longer what it was:\n\n{}\n\
These are the operations a document walk spends its time in, on the values it \
spends its time on — a small integer, held inline. Each should be the pointer \
word, a shift, and nothing else. An instruction that has appeared is work every \
one of them now pays, and nothing about the behaviour will have changed to show \
it: look for a function that stopped being inlined (the numeric model's decode \
is the usual one — it is large, and only `#[inline]` keeps its hot branch \
folding into the caller), or for a check that is now being made twice.",
failures.join("\n\n")
);
}
fn any_ir() -> &'static str {
static IR: OnceLock<String> = OnceLock::new();
IR.get_or_init(|| emit_probe_ir("any", probes::ANY))
}
fn fast_ir() -> &'static str {
static IR: OnceLock<String> = OnceLock::new();
IR.get_or_init(|| emit_probe_ir("fast", probes::FAST))
}
fn emit_probe_ir(name: &str, source: &str) -> String {
let dir = PathBuf::from(env!("CARGO_TARGET_TMPDIR")).join(format!("codegen-probes-{}", name));
std::fs::create_dir_all(dir.join("src")).expect("create the probe crate");
let manifest = format!(
"[package]\nname = \"probes\"\nversion = \"0.0.0\"\nedition = \"2018\"\n\n\
[lib]\ncrate-type = [\"cdylib\"]\n\n\
[dependencies]\nijson = {{ path = '{}'{} }}\n\n\
[profile.release]\nlto = \"fat\"\ncodegen-units = 1\n",
env!("CARGO_MANIFEST_DIR"),
if cfg!(feature = "arbitrary_precision") {
", features = [\"arbitrary_precision\"]"
} else {
""
},
);
write_if_changed(&dir.join("Cargo.toml"), &manifest);
write_if_changed(&dir.join("src/lib.rs"), source);
let status = common::nested_cargo()
.env("RUSTFLAGS", "--cfg codegen_probes")
.current_dir(&dir)
.args([
"rustc",
"--release",
"--",
"--emit=llvm-ir",
"-Cdebuginfo=0",
])
.status()
.expect("failed to run `cargo rustc` on the probe crate");
assert!(status.success(), "building the probe crate failed");
let deps = dir.join("target/release/deps");
let ir_file = std::fs::read_dir(&deps)
.expect("read the probe crate's deps directory")
.filter_map(Result::ok)
.map(|entry| entry.path())
.filter(|path| {
path.extension().is_some_and(|ext| ext == "ll")
&& path
.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with("probes"))
})
.max_by_key(|path| path.metadata().and_then(|meta| meta.modified()).ok())
.unwrap_or_else(|| panic!("no `probes*.ll` was emitted in {:?}", deps));
let ir = std::fs::read_to_string(&ir_file).expect("read the probe crate's LLVM IR");
assert!(
!common::is_instrumented(&ir),
"the probe crate was built with coverage instrumentation, so its IR is not the \
library's. Something is still feeding flags into the nested build:\n\n{}",
common::flag_environment()
);
ir
}
fn write_if_changed(path: &Path, contents: &str) {
if std::fs::read_to_string(path).ok().as_deref() != Some(contents) {
std::fs::write(path, contents).unwrap_or_else(|e| panic!("write {:?}: {}", path, e));
}
}