minor tweaks
This commit is contained in:
parent
fb652c39ae
commit
22ab973f0c
13 changed files with 223 additions and 144 deletions
35
src/abi.rs
35
src/abi.rs
|
|
@ -609,9 +609,12 @@ pub(crate) fn gp_slot(r: Register) -> Option<usize> {
|
|||
/// Registers a `call` destroys — every caller-saved GPR. A pointer that SURVIVES a call is in a
|
||||
/// callee-saved register, which is exactly how a real `this` is kept across one.
|
||||
///
|
||||
/// One list, three shapes: this array, [`caller_saved_mask`]'s bitmask, and the slot indices `concmd`
|
||||
/// clears after a call. They must agree — a register missing from one and present in another is a
|
||||
/// tracker that forgets a value the machine kept, or keeps one the machine destroyed.
|
||||
/// ONE list, and every shape of it is derived from this array: [`caller_saved_mask`]'s bitmask, the slot
|
||||
/// indices [`caller_saved_slots`] hands the `concmd` and `vscript` value trackers, and `pulse`'s two
|
||||
/// invalidation loops, which read it directly. Nothing transcribes it, because a register present in one
|
||||
/// copy and missing from another is a tracker that forgets a value the machine kept, or keeps one the
|
||||
/// machine destroyed — and a fork retargeting this (Windows/MSVC makes RSI and RDI callee-saved) has to
|
||||
/// change exactly one place.
|
||||
pub(crate) const CALLER_SAVED: [Register; 9] = [
|
||||
Register::RAX,
|
||||
Register::RCX,
|
||||
|
|
@ -631,6 +634,16 @@ fn caller_saved_mask() -> u32 {
|
|||
.fold(0u32, |m, s| m | (1 << s))
|
||||
}
|
||||
|
||||
/// [`CALLER_SAVED`] as the `[_; 16]` slot indices the instruction readers clear after a call — the shape
|
||||
/// `concmd` and `vscript` need, derived once here instead of transcribed into each.
|
||||
pub(crate) fn caller_saved_slots() -> [usize; 9] {
|
||||
let mut out = [0usize; 9];
|
||||
for (i, &r) in CALLER_SAVED.iter().enumerate() {
|
||||
out[i] = gp_slot(r).expect("every caller-saved register is a GPR");
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The largest displacement the function reaches through the pointer it was handed in RDI — for a
|
||||
/// member function, how far into `this` it touches.
|
||||
///
|
||||
|
|
@ -766,6 +779,22 @@ pub fn this_reach(img: &CodeImage, entry: u64) -> Option<u64> {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn every_shape_of_the_caller_saved_list_agrees_with_the_array() {
|
||||
// The invariant `CALLER_SAVED` documents, checked rather than asserted. Both derived shapes are
|
||||
// computed from the array here, so this can only fail if someone reintroduces a hand-written
|
||||
// copy — which is exactly the drift that put a raw index list in `vscript` and a second register
|
||||
// array in `pulse`.
|
||||
let mask = caller_saved_mask();
|
||||
let slots = caller_saved_slots();
|
||||
assert_eq!(mask.count_ones() as usize, CALLER_SAVED.len());
|
||||
assert_eq!(slots.len(), CALLER_SAVED.len());
|
||||
for (&r, &s) in CALLER_SAVED.iter().zip(slots.iter()) {
|
||||
assert_eq!(gp_slot(r), Some(s), "{r:?} lost its slot index");
|
||||
assert_ne!(mask & (1 << s), 0, "{r:?} is missing from the bitmask");
|
||||
}
|
||||
}
|
||||
|
||||
// Decode a tiny hand-assembled straight-line function and recover its shape through the REAL
|
||||
// per-instruction helper (`insn_effect`) + the real liveness formula — so a test can't pass while
|
||||
// the production path is wrong. (A single-successor chain; the fixpoint isn't exercised here.)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue