pub enum DebugSlotKind {
Reference,
Int,
Bool,
Float,
Char,
Byte,
}Expand description
What a debug value slot’s word is — and the only thing in the process that can say so (ADR-120 part 2).
The word alone cannot. A slot holding the Int payload 4 and a slot
holding a GcRef to address 4 are the same sixty-four bits, and there is
no bit left over to tag them apart: DebugValueStack is one machine word
per local by ADR-104’s construction and the shadow stack’s, which is what
makes a definition’s debug store a single str.
So the discrimination lives in the static metadata beside the slot, where
it costs nothing per call and cannot be corrupted by a program: this field
is written once per function by build_function_debug_meta at compile time,
interned in the JIT generation arena, and never written again.
This is the type that makes ADR-120 part 2 sound, and it is a type
rather than a bool for a reason. The failure mode the scalar slot creates
is the collector dereferencing an f64 bit pattern as a [GcHeader]
(crate::GcHeader), and ADR-106 makes the debug frames the collector’s one
weak arm — every claimed slot is scanned after every sweep. A bool field
would be a condition each scan has to remember to test. An enum whose
non-Reference variants are the input to
DebugLocalMeta::read, which answers a
DebugValue::Scalar that contains no reference, is a condition no scan
can fail to test: there is no path from a scalar slot to a GcRef.
Every praxis_mir::ir::ScalarKind
has a variant here, including Byte, which is unwired and which ADR-120’s
forwarding therefore cannot reach. The map is total on purpose: a partial map
would have to answer something for a kind it did not cover, and the only
available answer is Reference — which is precisely the unsound one.
Variants§
Reference
The slot holds an Option<GcRef>: a reference into the heap, or the
all-zero None. Every local whose box survives compilation has this.
Int
i64 — an Int payload whose box ADR-120’s forwarding deleted.
Bool
u8 widened — a Bool payload.
Float
f64::to_bits() — a Float payload. The bit pattern the scalar channel
carries (ScalarKind::Float’s doc), not an f64 register value.
Char
u32 widened — a Char payload.
Byte
u8 widened — a Byte payload.
Implementations§
Source§impl DebugSlotKind
impl DebugSlotKind
Sourcepub const fn store_bias(self) -> i64
pub const fn store_bias(self) -> i64
What generated code adds to a payload before storing it into a debug
slot, and what DebugLocalMeta::read subtracts on the way out
(ADR-121 decision 2).
§The problem this solves
A slot is one word and a claim zeroes its run, so the all-zero word means
“nothing written here yet”. For a Reference slot
that is exact — a GcRef is NonNull. For a scalar slot it cannot be:
there are 2^64 payloads and 2^64 words, so some payload must collide
with “unwritten”, and no encoding avoids it. The only question is which.
Storing the payload raw would make the collision 0 — and therefore
false, and 0.0. ADR-121 promotes bindings into scalar slots, so
that collision reaches var i = 0, which is close to the most common
line a Praxis program has.
§What each kind gives up instead
The bias is chosen per kind so the collision lands on a value the language does not hold, or barely does:
| kind | bias | the one payload that reads <uninit> |
|---|---|---|
Reference | 0 | none — NonNull has no zero |
Bool | 1 | none: two payloads, biased to 1 and 2 |
Char | 1 | none: 0..=0x10FFFF biased clear of zero |
Byte | 1 | none: 0..=255 biased clear of zero |
Float | 1 | one quiet NaN (0xFFFF_FFFF_FFFF_FFFF) |
Int | i64::MIN | i64::MIN |
Three of the six lose nothing at all, because their payloads do not fill
the word. Float loses one NaN bit pattern out of the 2^52 that are NaN,
and every one of them prints NaN anyway. Int genuinely loses a value
a program could compute — and i64::MIN against 0 is the whole trade,
made in the direction where the losing case is a number nothing reaches
by accident. small_int’s range starts at -256, and the sentinel
idiom that module names is -1; neither is anywhere near this.
§Why not a written-marker, which would lose nothing
A parallel byte per slot, zeroed by the claim and set by each store, is
exact. It is also a second store per definition in generated code, on
the path ADR-120 part 2 already measures at 2.4% of the suite. This is one
iadd_imm against a register that is about to be stored anyway — an ALU
operation with no memory traffic, which the same measurement cannot see.
Exactness here is worth an instruction, not a store.
Trait Implementations§
Source§impl Clone for DebugSlotKind
impl Clone for DebugSlotKind
Source§fn clone(&self) -> DebugSlotKind
fn clone(&self) -> DebugSlotKind
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read more