use std::sync::Arc;
use vyre::ir::{BufferAccess, BufferDecl, DataType, Expr, Ident, Node, Program};
use vyre_primitives::bitset::bitset_words;
pub(crate) const OP_ID: &str = "weir::escape";
#[must_use]
pub fn escape_analyze(points_to_in: &str, callgraph_in: &str, escape_out: &str) -> Program {
escape_analyze_with_count(points_to_in, callgraph_in, escape_out, 64)
}
#[must_use]
pub fn escape_analyze_with_count(
points_to_in: &str,
callgraph_in: &str,
escape_out: &str,
node_count: u32,
) -> Program {
let words = bitset_words(node_count).max(1);
let w = Expr::InvocationId { axis: 0 };
let body = vec![
Node::let_bind("pts", Expr::load(points_to_in, w.clone())),
Node::let_bind("cg", Expr::load(callgraph_in, w.clone())),
Node::let_bind("prev", Expr::load(escape_out, w.clone())),
Node::store(
escape_out,
w.clone(),
Expr::bitor(
Expr::bitor(Expr::var("pts"), Expr::var("cg")),
Expr::var("prev"),
),
),
];
let buffers = vec![
BufferDecl::storage(points_to_in, 0, BufferAccess::ReadOnly, DataType::U32)
.with_count(words),
BufferDecl::storage(callgraph_in, 1, BufferAccess::ReadOnly, DataType::U32)
.with_count(words),
BufferDecl::storage(escape_out, 2, BufferAccess::ReadWrite, DataType::U32)
.with_count(words),
];
Program::wrapped(
buffers,
[256, 1, 1],
vec![Node::Region {
generator: Ident::from(OP_ID),
source_region: None,
body: Arc::new(vec![Node::if_then(
Expr::lt(w.clone(), Expr::u32(words)),
body,
)]),
}],
)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Escape;
impl super::soundness::SoundnessTagged for Escape {
fn soundness(&self) -> super::soundness::Soundness {
super::soundness::Soundness::MayOver
}
}
inventory::submit! {
vyre_harness::OpEntry::new(
OP_ID,
|| escape_analyze_with_count("pts", "cg", "out", 64),
Some(|| {
let u32s = crate::dispatch_decode::pack_u32;
vec![vec![
u32s(&[0b0011, 0]),
u32s(&[0b0100, 0b1000]),
u32s(&[0b1000, 0b0001]),
]]
}),
Some(|| {
let u32s = crate::dispatch_decode::pack_u32;
vec![vec![u32s(&[0b1111, 0b1001])]]
}),
)
}
#[cfg(test)]
#[allow(deprecated)]
mod tests {
use super::*;
#[test]
fn escape_analyze_emits_program_with_op_id() {
let p = escape_analyze_with_count("pts", "cg", "out", 64);
let has_region = p.entry().iter().any(|n| {
matches!(
n,
Node::Region { generator, .. } if generator.as_str() == OP_ID
)
});
assert!(
has_region,
"weir::escape: Program body must contain a Region tagged `{OP_ID}`"
);
}
#[test]
fn escape_analyze_declares_three_buffers() {
let p = escape_analyze_with_count("pts", "cg", "out", 64);
assert_eq!(
p.buffers().len(),
3,
"weir::escape: Program must declare exactly 3 buffers (pts, cg, out)"
);
}
#[test]
fn escape_analyze_buffer_widths_match_node_count() {
let p = escape_analyze_with_count("pts", "cg", "out", 64);
for buf in p.buffers() {
assert_eq!(
buf.count(),
2,
"weir::escape: buffer `{}` must have count=2 for 64 lanes",
buf.name()
);
}
}
#[test]
fn escape_soundness_is_mayover() {
use super::super::soundness::SoundnessTagged;
assert_eq!(
Escape.soundness(),
super::super::soundness::Soundness::MayOver,
"weir::escape: closure-over-pointers is over-approximate (MayOver)"
);
}
#[test]
fn escape_analyze_handles_minimum_node_count() {
let p = escape_analyze_with_count("pts", "cg", "out", 0);
assert_eq!(
p.buffers().len(),
3,
"weir::escape: zero-node case must still emit a valid 3-buffer Program"
);
}
}