plotnik_lib/ir/
transition.rs

1//! Transition struct - the fundamental unit of the query IR.
2//!
3//! Each transition is 64 bytes and cache-line aligned to ensure no transition
4//! straddles cache lines. Transitions carry all semantics: matching, effects,
5//! and successors. States are implicit junction points.
6
7use super::{EffectOp, Matcher, Nav, RefTransition, Slice, TransitionId};
8
9/// Maximum number of inline successors before spilling to external segment.
10pub const MAX_INLINE_SUCCESSORS: usize = 8;
11
12/// A single transition in the query graph.
13///
14/// Transitions use SSO (small-size optimization) for successors:
15/// - 0-8 successors: stored inline in `successor_data`
16/// - 9+ successors: `successor_data[0]` is index into successors segment
17///
18/// Layout (64 bytes total, 64-byte aligned):
19/// ```text
20/// offset 0:  matcher (16 bytes)
21/// offset 16: ref_marker (4 bytes)
22/// offset 20: nav (2 bytes)
23/// offset 22: effects_len (2 bytes)
24/// offset 24: successor_count (4 bytes)
25/// offset 28: effects_start (4 bytes)
26/// offset 32: successor_data (32 bytes)
27/// ```
28#[repr(C, align(64))]
29#[derive(Clone, Copy)]
30pub struct Transition {
31    // --- 32 bytes metadata ---
32    /// What this transition matches (node kind, wildcard, epsilon).
33    pub matcher: Matcher, // 16 bytes, offset 0
34
35    /// Reference call/return marker for recursive definitions.
36    pub ref_marker: RefTransition, // 4 bytes, offset 16
37
38    /// Navigation instruction (descend/ascend/sibling traversal).
39    pub nav: Nav, // 2 bytes, offset 20
40
41    /// Number of effect operations (inlined from Slice for alignment).
42    effects_len: u16, // 2 bytes, offset 22
43
44    /// Number of successor transitions.
45    pub successor_count: u32, // 4 bytes, offset 24
46
47    /// Start index into effects segment (inlined from Slice for alignment).
48    effects_start: u32, // 4 bytes, offset 28
49
50    // --- 32 bytes control flow ---
51    /// Successor storage (inline or spilled index).
52    ///
53    /// - If `successor_count <= 8`: contains `TransitionId` values directly
54    /// - If `successor_count > 8`: `successor_data[0]` is index into successors segment
55    pub successor_data: [u32; MAX_INLINE_SUCCESSORS], // 32 bytes, offset 32
56}
57
58impl Transition {
59    /// Creates a new transition with all fields.
60    #[inline]
61    pub fn new(
62        matcher: Matcher,
63        ref_marker: RefTransition,
64        nav: Nav,
65        effects: Slice<EffectOp>,
66        successor_count: u32,
67        successor_data: [u32; MAX_INLINE_SUCCESSORS],
68    ) -> Self {
69        Self {
70            matcher,
71            ref_marker,
72            nav,
73            effects_len: effects.len(),
74            successor_count,
75            effects_start: effects.start_index(),
76            successor_data,
77        }
78    }
79
80    /// Returns the effects slice.
81    #[inline]
82    pub fn effects(&self) -> Slice<EffectOp> {
83        Slice::new(self.effects_start, self.effects_len)
84    }
85
86    /// Sets the effects slice.
87    #[inline]
88    pub fn set_effects(&mut self, effects: Slice<EffectOp>) {
89        self.effects_start = effects.start_index();
90        self.effects_len = effects.len();
91    }
92
93    /// Returns `true` if successors are stored inline.
94    #[inline]
95    pub fn has_inline_successors(&self) -> bool {
96        self.successor_count as usize <= MAX_INLINE_SUCCESSORS
97    }
98
99    /// Returns inline successors if they fit, `None` if spilled.
100    #[inline]
101    pub fn inline_successors(&self) -> Option<&[TransitionId]> {
102        if self.has_inline_successors() {
103            Some(&self.successor_data[..self.successor_count as usize])
104        } else {
105            None
106        }
107    }
108
109    /// Returns the spilled successor segment index and count.
110    /// Panics if successors are inline.
111    #[inline]
112    pub fn spilled_successors_index(&self) -> u32 {
113        debug_assert!(
114            !self.has_inline_successors(),
115            "successors are inline, not spilled"
116        );
117        self.successor_data[0]
118    }
119}
120
121// Compile-time size/alignment verification
122const _: () = {
123    assert!(core::mem::size_of::<Transition>() == 64);
124    assert!(core::mem::align_of::<Transition>() == 64);
125};