Skip to main content

patch_prolog_shared/
cell.rs

1//! Tagged 64-bit term words — the single source of truth for the word/cell
2//! ABI shared by the runtime (which builds and reads these on the heap) and
3//! the compiler (which emits the same encoding into `.rodata` fact tables and
4//! blobs). Keeping the tag values, the `<< 3` / `& 7` split, and the
5//! functor-packing layout in ONE place means codegen and runtime can never
6//! drift one-sidedly.
7//!
8//! A word's low 3 bits are the tag; the payload sits in the high 61.
9//! Pointer-like tags (REF/STR/LST/FLT/BIG) carry **heap cell indices**, not
10//! raw pointers — the heap is a growable `Vec<u64>` and indices stay valid
11//! across reallocation (and across backtracking truncation).
12
13pub type Word = u64;
14
15pub const TAG_REF: u64 = 0; // payload = heap index of a variable cell
16pub const TAG_ATOM: u64 = 1; // payload = AtomId
17pub const TAG_INT: u64 = 2; // payload = signed 61-bit immediate
18pub const TAG_STR: u64 = 3; // payload = heap index of [functor|arity][args...]
19pub const TAG_LST: u64 = 4; // payload = heap index of [head][tail]
20pub const TAG_FLT: u64 = 5; // payload = heap index of an f64-bits cell
21pub const TAG_BIG: u64 = 6; // payload = heap index of a raw i64 cell
22
23/// Largest magnitude representable as an immediate integer; values
24/// outside box to a TAG_BIG heap cell (full i64 range, v1 parity).
25pub const INT_MAX: i64 = (1 << 60) - 1;
26pub const INT_MIN: i64 = -(1 << 60);
27
28#[inline]
29pub fn make(tag: u64, payload: u64) -> Word {
30    (payload << 3) | tag
31}
32
33#[inline]
34pub fn tag_of(w: Word) -> u64 {
35    w & 7
36}
37
38#[inline]
39pub fn payload(w: Word) -> u64 {
40    w >> 3
41}
42
43#[inline]
44pub fn make_atom(id: u32) -> Word {
45    make(TAG_ATOM, id as u64)
46}
47
48#[inline]
49pub fn make_int(n: i64) -> Word {
50    debug_assert!((INT_MIN..=INT_MAX).contains(&n));
51    ((n as u64) << 3) | TAG_INT
52}
53
54/// Signed payload extraction (arithmetic shift preserves the sign).
55#[inline]
56pub fn int_value(w: Word) -> i64 {
57    (w as i64) >> 3
58}
59
60#[inline]
61pub fn make_ref(idx: usize) -> Word {
62    make(TAG_REF, idx as u64)
63}
64
65#[inline]
66pub fn atom_id(w: Word) -> u32 {
67    payload(w) as u32
68}
69
70/// Pack a STR header cell: functor id in the high half, arity in the low.
71#[inline]
72pub fn pack_functor(functor: u32, arity: u32) -> u64 {
73    ((functor as u64) << 32) | arity as u64
74}
75
76#[inline]
77pub fn unpack_functor(cell: u64) -> (u32, u32) {
78    ((cell >> 32) as u32, cell as u32)
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn int_roundtrip_preserves_sign() {
87        for n in [0i64, 1, -1, 42, -42, INT_MAX, INT_MIN] {
88            assert_eq!(int_value(make_int(n)), n, "roundtrip {n}");
89            assert_eq!(tag_of(make_int(n)), TAG_INT);
90        }
91    }
92
93    #[test]
94    fn functor_packing() {
95        let cell = pack_functor(7, 3);
96        assert_eq!(unpack_functor(cell), (7, 3));
97    }
98
99    #[test]
100    fn tags_distinct() {
101        assert_eq!(tag_of(make_atom(5)), TAG_ATOM);
102        assert_eq!(tag_of(make_ref(9)), TAG_REF);
103        assert_eq!(atom_id(make_atom(5)), 5);
104    }
105}