1use std::sync::Arc;
29
30use vyre::ir::{BufferAccess, BufferDecl, DataType, Expr, Ident, Node, Program};
31use vyre_primitives::bitset::bitset_words;
32
33pub(crate) const OP_ID: &str = "weir::escape";
34
35#[must_use]
40pub fn escape_analyze(points_to_in: &str, callgraph_in: &str, escape_out: &str) -> Program {
41 escape_analyze_with_count(points_to_in, callgraph_in, escape_out, 64)
42}
43
44#[must_use]
46pub fn escape_analyze_with_count(
47 points_to_in: &str,
48 callgraph_in: &str,
49 escape_out: &str,
50 node_count: u32,
51) -> Program {
52 let words = bitset_words(node_count).max(1);
53 let w = Expr::InvocationId { axis: 0 };
54
55 let body = vec![
56 Node::let_bind("pts", Expr::load(points_to_in, w.clone())),
57 Node::let_bind("cg", Expr::load(callgraph_in, w.clone())),
58 Node::let_bind("prev", Expr::load(escape_out, w.clone())),
59 Node::store(
60 escape_out,
61 w.clone(),
62 Expr::bitor(
63 Expr::bitor(Expr::var("pts"), Expr::var("cg")),
64 Expr::var("prev"),
65 ),
66 ),
67 ];
68
69 let buffers = vec![
70 BufferDecl::storage(points_to_in, 0, BufferAccess::ReadOnly, DataType::U32)
71 .with_count(words),
72 BufferDecl::storage(callgraph_in, 1, BufferAccess::ReadOnly, DataType::U32)
73 .with_count(words),
74 BufferDecl::storage(escape_out, 2, BufferAccess::ReadWrite, DataType::U32)
75 .with_count(words),
76 ];
77
78 Program::wrapped(
79 buffers,
80 [256, 1, 1],
81 vec![Node::Region {
82 generator: Ident::from(OP_ID),
83 source_region: None,
84 body: Arc::new(vec![Node::if_then(
85 Expr::lt(w.clone(), Expr::u32(words)),
86 body,
87 )]),
88 }],
89 )
90}
91
92#[derive(Clone, Copy, Debug, PartialEq, Eq)]
94pub struct Escape;
95
96impl super::soundness::SoundnessTagged for Escape {
97 fn soundness(&self) -> super::soundness::Soundness {
98 super::soundness::Soundness::MayOver
99 }
100}
101
102inventory::submit! {
103 vyre_harness::OpEntry::new(
104 OP_ID,
105 || escape_analyze_with_count("pts", "cg", "out", 64),
106 Some(|| {
107 let u32s = crate::dispatch_decode::pack_u32;
108 vec![vec![
109 u32s(&[0b0011, 0]),
110 u32s(&[0b0100, 0b1000]),
111 u32s(&[0b1000, 0b0001]),
112 ]]
113 }),
114 Some(|| {
115 let u32s = crate::dispatch_decode::pack_u32;
116 vec![vec![u32s(&[0b1111, 0b1001])]]
117 }),
118 )
119}
120
121#[cfg(test)]
122#[allow(deprecated)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn escape_analyze_emits_program_with_op_id() {
128 let p = escape_analyze_with_count("pts", "cg", "out", 64);
129 let has_region = p.entry().iter().any(|n| {
130 matches!(
131 n,
132 Node::Region { generator, .. } if generator.as_str() == OP_ID
133 )
134 });
135 assert!(
136 has_region,
137 "weir::escape: Program body must contain a Region tagged `{OP_ID}`"
138 );
139 }
140
141 #[test]
142 fn escape_analyze_declares_three_buffers() {
143 let p = escape_analyze_with_count("pts", "cg", "out", 64);
144 assert_eq!(
145 p.buffers().len(),
146 3,
147 "weir::escape: Program must declare exactly 3 buffers (pts, cg, out)"
148 );
149 }
150
151 #[test]
152 fn escape_analyze_buffer_widths_match_node_count() {
153 let p = escape_analyze_with_count("pts", "cg", "out", 64);
155 for buf in p.buffers() {
156 assert_eq!(
157 buf.count(),
158 2,
159 "weir::escape: buffer `{}` must have count=2 for 64 lanes",
160 buf.name()
161 );
162 }
163 }
164
165 #[test]
166 fn escape_soundness_is_mayover() {
167 use super::super::soundness::SoundnessTagged;
168 assert_eq!(
169 Escape.soundness(),
170 super::super::soundness::Soundness::MayOver,
171 "weir::escape: closure-over-pointers is over-approximate (MayOver)"
172 );
173 }
174
175 #[test]
176 fn escape_analyze_handles_minimum_node_count() {
177 let p = escape_analyze_with_count("pts", "cg", "out", 0);
179 assert_eq!(
180 p.buffers().len(),
181 3,
182 "weir::escape: zero-node case must still emit a valid 3-buffer Program"
183 );
184 }
185}