1use std::sync::Arc;
10
11use smallvec::SmallVec;
12use vyre_foundation::ir::{Expr, Ident, Node, Program};
13
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
16pub struct BarrierElisionReport {
17 pub removed: usize,
19}
20
21#[must_use]
35pub fn elide_value_flow_barriers(program: Program) -> (Program, BarrierElisionReport) {
36 let mut report = BarrierElisionReport::default();
37 if !nodes_have_barrier(program.entry()) {
38 return (program, report);
39 }
40 let entry = rewrite_nodes(program.entry().to_vec(), &mut report);
41 let rewritten = if report.removed == 0 {
42 program
43 } else {
44 program.with_rewritten_entry(entry)
45 };
46 (rewritten, report)
47}
48
49fn nodes_have_barrier(nodes: &[Node]) -> bool {
50 nodes.iter().any(node_has_barrier)
51}
52
53fn node_has_barrier(node: &Node) -> bool {
54 match node {
55 Node::Barrier { .. } => true,
56 Node::If {
57 then, otherwise, ..
58 } => nodes_have_barrier(then) || nodes_have_barrier(otherwise),
59 Node::Loop { body, .. } | Node::Block(body) => nodes_have_barrier(body),
60 Node::Region { body, .. } => nodes_have_barrier(body),
61 _ => false,
62 }
63}
64
65fn rewrite_nodes(nodes: Vec<Node>, report: &mut BarrierElisionReport) -> Vec<Node> {
66 if !nodes_have_barrier(&nodes) {
67 return nodes;
68 }
69 let mut rewritten = Vec::with_capacity(nodes.len());
70 for node in nodes {
71 rewritten.push(rewrite_node(node, report));
72 }
73 elide_barrier_siblings(rewritten, report)
74}
75
76fn rewrite_node(node: Node, report: &mut BarrierElisionReport) -> Node {
77 match node {
78 Node::If {
79 cond,
80 then,
81 otherwise,
82 } => Node::If {
83 cond,
84 then: rewrite_nodes(then, report),
85 otherwise: rewrite_nodes(otherwise, report),
86 },
87 Node::Loop {
88 var,
89 from,
90 to,
91 body,
92 } => Node::Loop {
93 var,
94 from,
95 to,
96 body: rewrite_nodes(body, report),
97 },
98 Node::Block(body) => Node::Block(rewrite_nodes(body, report)),
99 Node::Region {
100 generator,
101 source_region,
102 body,
103 } => {
104 if !nodes_have_barrier(&body) {
105 Node::Region {
106 generator,
107 source_region,
108 body,
109 }
110 } else {
111 Node::Region {
112 generator,
113 source_region,
114 body: Arc::new(rewrite_nodes(arc_vec_into_vec(body), report)),
115 }
116 }
117 }
118 other => other,
119 }
120}
121
122fn elide_barrier_siblings(nodes: Vec<Node>, report: &mut BarrierElisionReport) -> Vec<Node> {
123 let mut out = Vec::with_capacity(nodes.len());
124 let mut iter = nodes.into_iter().peekable();
125 while let Some(node) = iter.next() {
126 if matches!(&node, Node::Barrier { .. }) {
127 if let (Some(left), Some(right)) = (out.last(), iter.peek()) {
128 if is_runtime_arm(left)
129 && is_runtime_arm(right)
130 && arms_are_independent(left, right)
131 {
132 report.removed += 1;
133 continue;
134 }
135 }
136 }
137 out.push(node);
138 }
139 out
140}
141
142fn arc_vec_into_vec<T: Clone>(body: Arc<Vec<T>>) -> Vec<T> {
145 match Arc::try_unwrap(body) {
146 Ok(nodes) => nodes,
147 Err(shared) => shared.as_ref().clone(),
148 }
149}
150
151fn is_runtime_arm(node: &Node) -> bool {
152 matches!(node, Node::Block(_) | Node::Region { .. })
153}
154
155fn arms_are_independent(left: &Node, right: &Node) -> bool {
156 let mut left_access = AccessSet::default();
157 let mut right_access = AccessSet::default();
158 collect_node_access(left, &mut left_access);
159 collect_node_access(right, &mut right_access);
160 !left_access.unknown && !right_access.unknown && !left_access.conflicts_with(&right_access)
161}
162
163#[derive(Debug, Default)]
164struct AccessSet<'a> {
165 reads: SmallVec<[&'a Ident; 8]>,
166 writes: SmallVec<[&'a Ident; 8]>,
167 unknown: bool,
168}
169
170impl<'a> AccessSet<'a> {
171 fn read(&mut self, buffer: &'a Ident) {
172 push_unique(&mut self.reads, buffer);
173 }
174
175 fn write(&mut self, buffer: &'a Ident) {
176 push_unique(&mut self.writes, buffer);
177 }
178
179 fn read_write(&mut self, buffer: &'a Ident) {
180 self.read(buffer);
181 self.write(buffer);
182 }
183
184 fn conflicts_with(&self, other: &Self) -> bool {
185 intersects(&self.writes, &other.reads)
186 || intersects(&self.reads, &other.writes)
187 || intersects(&self.writes, &other.writes)
188 }
189}
190
191fn push_unique<'a>(set: &mut SmallVec<[&'a Ident; 8]>, value: &'a Ident) {
192 if !set.iter().any(|existing| *existing == value) {
193 set.push(value);
194 }
195}
196
197fn intersects(left: &[&Ident], right: &[&Ident]) -> bool {
198 if left.len() <= right.len() {
199 left.iter()
200 .any(|value| right.iter().any(|other| other == value))
201 } else {
202 right
203 .iter()
204 .any(|value| left.iter().any(|other| other == value))
205 }
206}
207
208fn collect_node_access<'a>(node: &'a Node, out: &mut AccessSet<'a>) {
209 match node {
210 Node::Let { value, .. } | Node::Assign { value, .. } => collect_expr_access(value, out),
211 Node::Store {
212 buffer,
213 index,
214 value,
215 } => {
216 out.write(buffer);
217 collect_expr_access(index, out);
218 collect_expr_access(value, out);
219 }
220 Node::If {
221 cond,
222 then,
223 otherwise,
224 } => {
225 collect_expr_access(cond, out);
226 collect_nodes_access(then, out);
227 collect_nodes_access(otherwise, out);
228 }
229 Node::Loop { from, to, body, .. } => {
230 collect_expr_access(from, out);
231 collect_expr_access(to, out);
232 collect_nodes_access(body, out);
233 }
234 Node::IndirectDispatch { count_buffer, .. } => out.read(count_buffer),
235 Node::AsyncLoad {
236 source,
237 destination,
238 offset,
239 size,
240 ..
241 } => {
242 out.read(source);
243 out.write(destination);
244 collect_expr_access(offset, out);
245 collect_expr_access(size, out);
246 }
247 Node::AsyncStore {
248 source,
249 destination,
250 offset,
251 size,
252 ..
253 } => {
254 out.read(source);
255 out.write(destination);
256 collect_expr_access(offset, out);
257 collect_expr_access(size, out);
258 }
259 Node::AsyncWait { .. } | Node::Return | Node::Barrier { .. } | Node::Resume { .. } => {}
260 Node::Trap { address, .. } => {
261 collect_expr_access(address, out);
262 out.unknown = true;
263 }
264 Node::Block(body) => collect_nodes_access(body, out),
265 Node::Region { body, .. } => collect_nodes_access(body, out),
266 Node::Opaque(_) => out.unknown = true,
267 _ => out.unknown = true,
268 }
269}
270
271fn collect_nodes_access<'a>(nodes: &'a [Node], out: &mut AccessSet<'a>) {
272 for node in nodes {
273 collect_node_access(node, out);
274 }
275}
276
277fn collect_expr_access<'a>(expr: &'a Expr, out: &mut AccessSet<'a>) {
278 match expr {
279 Expr::Load { buffer, index } => {
280 out.read(buffer);
281 collect_expr_access(index, out);
282 }
283 Expr::BufLen { buffer } => out.read(buffer),
284 Expr::BinOp { left, right, .. } => {
285 collect_expr_access(left, out);
286 collect_expr_access(right, out);
287 }
288 Expr::UnOp { operand, .. } => collect_expr_access(operand, out),
289 Expr::Call { args, .. } => {
290 for arg in args {
291 collect_expr_access(arg, out);
292 }
293 }
294 Expr::Select {
295 cond,
296 true_val,
297 false_val,
298 } => {
299 collect_expr_access(cond, out);
300 collect_expr_access(true_val, out);
301 collect_expr_access(false_val, out);
302 }
303 Expr::Cast { value, .. } => collect_expr_access(value, out),
304 Expr::Fma { a, b, c } => {
305 collect_expr_access(a, out);
306 collect_expr_access(b, out);
307 collect_expr_access(c, out);
308 }
309 Expr::SubgroupBallot { cond } => collect_expr_access(cond, out),
310 Expr::SubgroupShuffle { value, lane } => {
311 collect_expr_access(value, out);
312 collect_expr_access(lane, out);
313 }
314 Expr::SubgroupReduce { value, .. } => collect_expr_access(value, out),
315 Expr::Atomic {
316 buffer,
317 index,
318 expected,
319 value,
320 ..
321 } => {
322 out.read_write(buffer);
323 collect_expr_access(index, out);
324 if let Some(expected) = expected {
325 collect_expr_access(expected, out);
326 }
327 collect_expr_access(value, out);
328 }
329 Expr::Opaque(_) => out.unknown = true,
330 Expr::LitU32(_)
331 | Expr::LitI32(_)
332 | Expr::LitF32(_)
333 | Expr::LitBool(_)
334 | Expr::Var(_)
335 | Expr::InvocationId { .. }
336 | Expr::WorkgroupId { .. }
337 | Expr::LocalId { .. }
338 | Expr::SubgroupLocalId
339 | Expr::SubgroupSize => {}
340 _ => out.unknown = true,
341 }
342}
343
344#[cfg(test)]
345mod tests {
346 use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType};
347
348 use super::*;
349
350 fn buffer(name: &str, binding: u32) -> BufferDecl {
351 BufferDecl::storage(name, binding, BufferAccess::ReadWrite, DataType::U32)
352 }
353
354 fn barrier_count(nodes: &[Node]) -> usize {
355 nodes
356 .iter()
357 .map(|node| match node {
358 Node::Barrier { .. } => 1,
359 Node::If {
360 then, otherwise, ..
361 } => barrier_count(then) + barrier_count(otherwise),
362 Node::Loop { body, .. } | Node::Block(body) => barrier_count(body),
363 Node::Region { body, .. } => barrier_count(body),
364 _ => 0,
365 })
366 .sum()
367 }
368
369 fn store_count(nodes: &[Node]) -> usize {
370 nodes
371 .iter()
372 .map(|node| match node {
373 Node::Store { .. } => 1,
374 Node::If {
375 then, otherwise, ..
376 } => store_count(then) + store_count(otherwise),
377 Node::Loop { body, .. } | Node::Block(body) => store_count(body),
378 Node::Region { body, .. } => store_count(body),
379 _ => 0,
380 })
381 .sum()
382 }
383
384 #[test]
385 fn removes_barrier_between_disjoint_runtime_arms() {
386 let program = Program::wrapped(
387 vec![buffer("a", 0), buffer("b", 1)],
388 [64, 1, 1],
389 vec![
390 Node::Block(vec![Node::store("a", Expr::u32(0), Expr::u32(1))]),
391 Node::barrier(),
392 Node::Block(vec![Node::store("b", Expr::u32(0), Expr::u32(2))]),
393 ],
394 );
395
396 let (rewritten, report) = elide_value_flow_barriers(program);
397
398 assert_eq!(report.removed, 1);
399 assert_eq!(barrier_count(rewritten.entry()), 0);
400 }
401
402 #[test]
410 fn elides_every_barrier_across_many_disjoint_arms_in_one_pass() {
411 let program = Program::wrapped(
412 vec![
413 buffer("a", 0),
414 buffer("b", 1),
415 buffer("c", 2),
416 buffer("d", 3),
417 ],
418 [64, 1, 1],
419 vec![
420 Node::Block(vec![Node::store("a", Expr::u32(0), Expr::u32(1))]),
421 Node::barrier(),
422 Node::Block(vec![Node::store("b", Expr::u32(0), Expr::u32(2))]),
423 Node::barrier(),
424 Node::Block(vec![Node::store("c", Expr::u32(0), Expr::u32(3))]),
425 Node::barrier(),
426 Node::Block(vec![Node::store("d", Expr::u32(0), Expr::u32(4))]),
427 ],
428 );
429
430 let (rewritten, report) = elide_value_flow_barriers(program);
431
432 assert_eq!(
433 report.removed, 3,
434 "all three disjoint-arm barriers must be elided"
435 );
436 assert_eq!(barrier_count(rewritten.entry()), 0);
437 assert_eq!(
440 store_count(rewritten.entry()),
441 4,
442 "all four independent store arms must survive the rewrite"
443 );
444 }
445
446 #[test]
447 fn no_barrier_program_is_returned_without_rewrite() {
448 let program = Program::wrapped(
449 vec![buffer("a", 0)],
450 [64, 1, 1],
451 vec![Node::Block(vec![Node::store(
452 "a",
453 Expr::u32(0),
454 Expr::u32(1),
455 )])],
456 );
457 let expected = program.clone();
458
459 let (rewritten, report) = elide_value_flow_barriers(program);
460
461 assert_eq!(report.removed, 0);
462 assert_eq!(
463 rewritten.fingerprint(),
464 expected.fingerprint(),
465 "Fix: barrier-free megakernel plans must avoid structural rewrites."
466 );
467 }
468
469 #[test]
470 fn keeps_barrier_when_next_arm_reads_previous_write() {
471 let program = Program::wrapped(
472 vec![buffer("a", 0)],
473 [64, 1, 1],
474 vec![
475 Node::Block(vec![Node::store("a", Expr::u32(0), Expr::u32(1))]),
476 Node::barrier(),
477 Node::Block(vec![Node::let_bind("x", Expr::load("a", Expr::u32(0)))]),
478 ],
479 );
480
481 let (rewritten, report) = elide_value_flow_barriers(program);
482
483 assert_eq!(report.removed, 0);
484 assert_eq!(barrier_count(rewritten.entry()), 1);
485 }
486
487 #[test]
488 fn keeps_barrier_around_unknown_opaque_arm() {
489 let program = Program::wrapped(
490 vec![buffer("a", 0), buffer("b", 1)],
491 [64, 1, 1],
492 vec![
493 Node::Block(vec![Node::Opaque(Arc::new(TestOpaqueNode))]),
494 Node::barrier(),
495 Node::Block(vec![Node::store("b", Expr::u32(0), Expr::u32(2))]),
496 ],
497 );
498
499 let (rewritten, report) = elide_value_flow_barriers(program);
500
501 assert_eq!(report.removed, 0);
502 assert_eq!(barrier_count(rewritten.entry()), 1);
503 }
504
505 #[derive(Debug)]
506 struct TestOpaqueNode;
507
508 impl vyre_foundation::ir::NodeExtension for TestOpaqueNode {
509 fn extension_kind(&self) -> &'static str {
510 "test.opaque"
511 }
512
513 fn debug_identity(&self) -> &str {
514 "test.opaque"
515 }
516
517 fn stable_fingerprint(&self) -> [u8; 32] {
518 [7; 32]
519 }
520
521 fn validate_extension(&self) -> Result<(), String> {
522 Ok(())
523 }
524
525 fn as_any(&self) -> &dyn std::any::Any {
526 self
527 }
528 }
529}