Skip to main content

DebugSlotKind

Enum DebugSlotKind 

Source
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

Source

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:

kindbiasthe one payload that reads <uninit>
Reference0none — NonNull has no zero
Bool1none: two payloads, biased to 1 and 2
Char1none: 0..=0x10FFFF biased clear of zero
Byte1none: 0..=255 biased clear of zero
Float1one quiet NaN (0xFFFF_FFFF_FFFF_FFFF)
Inti64::MINi64::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

Source§

fn clone(&self) -> DebugSlotKind

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Copy for DebugSlotKind

Source§

impl Debug for DebugSlotKind

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for DebugSlotKind

Source§

impl Hash for DebugSlotKind

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl PartialEq for DebugSlotKind

Source§

fn eq(&self, other: &DebugSlotKind) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for DebugSlotKind

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.