Skip to main content

vyre_libs/
range_ordering.rs

1//! Domain-neutral byte-range ordering predicates.
2//!
3//! These helpers build IR for relations between tagged byte-range streams.
4//! They use the scanner output contract's `counts`, `offsets`, and `lengths`
5//! buffer names.
6
7use vyre_foundation::ir::{Expr, Node};
8
9/// Maximum number of cached positions per tagged range. Matches the
10/// source-query dialect scanner-side cap.
11pub const MAX_CACHED_POSITIONS: u32 = 256;
12
13/// Maximum logical "depth" used by the same scanner-side convention.
14pub const MAX_DEPTH: u32 = 12;
15
16/// Helper to read the element of a packed 2D array laid out as
17/// `buffer[id * MAX_CACHED_POSITIONS + index]`.
18fn packed_load(buffer: &str, id: Expr, index: Expr) -> Expr {
19    Expr::load(
20        buffer,
21        Expr::add(Expr::mul(id, Expr::u32(MAX_CACHED_POSITIONS)), index),
22    )
23}
24
25/// Generate a loop block deciding whether any range tagged `left_id`
26/// ends at or before some range tagged `right_id` begins.
27///
28/// Returns `(Vec<Node>, Expr)` where the expression is the boolean
29/// result bound to an internal `let` variable named `<res_name>_found`.
30///
31/// The emitted block assumes the enclosing IR provides three storage
32/// buffers: `counts[tag]` (how many ranges carry that tag), and
33/// `offsets[tag * MAX_CACHED_POSITIONS + i]` + `lengths[tag * MAX_CACHED_POSITIONS + i]`.
34///
35/// AUDIT: PHASE5_ASTWALK match_order quadratic  -  replaced nested O(N²)
36/// loops with a sweep-line O(N) pass.  The hit positions for each tag
37/// are guaranteed to be sorted by ascending offset by the host scanner
38/// (see `downstream analyzer::scan::collector::select_hits_for_dispatch`).  Because
39/// the inputs are sorted, the predicate `∃ a ∈ A, ∃ b ∈ B : a_end <=
40/// b_start` is equivalent to `min_a_end <= max_b_start`.  We compute
41/// `min_a_end` with a single linear scan over A and read `max_b_start`
42/// directly from the last element of B (the largest offset).  Inner
43/// work is O(N) with N ≤ MAX_CACHED_POSITIONS = 256, versus the prior
44/// 65 536 iterations per workgroup lane.
45#[must_use]
46pub fn match_order(left_id: Expr, right_id: Expr, res_name: &str) -> (Vec<Node>, Expr) {
47    let mut block = Vec::new();
48
49    let limit_a = Expr::load("counts", left_id.clone());
50    let clamped_limit_a = Expr::select(
51        Expr::gt(limit_a.clone(), Expr::u32(MAX_CACHED_POSITIONS)),
52        Expr::u32(MAX_CACHED_POSITIONS),
53        limit_a,
54    );
55
56    let limit_b = Expr::load("counts", right_id.clone());
57    let clamped_limit_b = Expr::select(
58        Expr::gt(limit_b.clone(), Expr::u32(MAX_CACHED_POSITIONS)),
59        Expr::u32(MAX_CACHED_POSITIONS),
60        limit_b,
61    );
62
63    block.push(Node::let_bind(format!("{res_name}_len_a"), clamped_limit_a));
64    block.push(Node::let_bind(format!("{res_name}_len_b"), clamped_limit_b));
65
66    // Compute min_a_end across all valid A positions.
67    block.push(Node::let_bind(
68        format!("{res_name}_min_a_end"),
69        Expr::u32(u32::MAX),
70    ));
71
72    let scan_a_loop = Node::loop_for(
73        "i",
74        Expr::u32(0),
75        Expr::var(format!("{res_name}_len_a").as_str()),
76        vec![
77            Node::let_bind(
78                "a_start",
79                packed_load("offsets", left_id.clone(), Expr::var("i")),
80            ),
81            Node::let_bind(
82                "a_len",
83                packed_load("lengths", left_id.clone(), Expr::var("i")),
84            ),
85            Node::let_bind("a_end", Expr::add(Expr::var("a_start"), Expr::var("a_len"))),
86            Node::assign(
87                format!("{res_name}_min_a_end"),
88                Expr::select(
89                    Expr::lt(
90                        Expr::var("a_end"),
91                        Expr::var(format!("{res_name}_min_a_end")),
92                    ),
93                    Expr::var("a_end"),
94                    Expr::var(format!("{res_name}_min_a_end")),
95                ),
96            ),
97        ],
98    );
99    block.push(scan_a_loop);
100
101    // B is sorted by offset, so max_b_start is the last valid element.
102    let max_b_start = Expr::select(
103        Expr::gt(
104            Expr::var(format!("{res_name}_len_b").as_str()),
105            Expr::u32(0),
106        ),
107        packed_load(
108            "offsets",
109            right_id.clone(),
110            Expr::sub(
111                Expr::var(format!("{res_name}_len_b").as_str()),
112                Expr::u32(1),
113            ),
114        ),
115        Expr::u32(0),
116    );
117    block.push(Node::let_bind(
118        format!("{res_name}_max_b_start"),
119        max_b_start,
120    ));
121
122    // Found iff both sides are non-empty and the earliest-ending A
123    // ends at or before the latest-starting B begins.
124    let both_non_empty = Expr::and(
125        Expr::gt(
126            Expr::var(format!("{res_name}_len_a").as_str()),
127            Expr::u32(0),
128        ),
129        Expr::gt(
130            Expr::var(format!("{res_name}_len_b").as_str()),
131            Expr::u32(0),
132        ),
133    );
134    block.push(Node::let_bind(
135        format!("{res_name}_found"),
136        Expr::select(
137            both_non_empty,
138            Expr::select(
139                Expr::le(
140                    Expr::var(format!("{res_name}_min_a_end").as_str()),
141                    Expr::var(format!("{res_name}_max_b_start").as_str()),
142                ),
143                Expr::u32(1),
144                Expr::u32(0),
145            ),
146            Expr::u32(0),
147        ),
148    ));
149
150    (block, Expr::var(format!("{res_name}_found")))
151}