Skip to main content

vyre_foundation/ir_inner/model/
arena.rs

1//! Arena-backed expression storage for opt-in IR construction.
2#![allow(unsafe_code)]
3
4use crate::ir_inner::model::expr::Expr;
5use crate::ir_inner::model::program::BufferDecl;
6use bumpalo::Bump;
7use rustc_hash::FxHashMap;
8use std::cell::{Cell, UnsafeCell};
9use std::sync::Arc;
10
11/// Stable handle to an expression allocated in an [`ExprArena`].
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct ExprRef {
14    index: usize,
15}
16
17impl ExprRef {
18    /// Zero-based expression index within the arena.
19    #[must_use]
20    #[inline]
21    pub fn index(self) -> usize {
22        self.index
23    }
24}
25
26/// Bump-allocated expression arena.
27///
28/// This is an opt-in migration path for builders that create many temporary
29/// expression nodes. Existing callers can continue to use boxed [`Expr`] trees
30/// through `Program::new`.
31#[derive(Default)]
32pub struct ExprArena {
33    bump: Bump,
34    exprs: UnsafeCell<Vec<*const Expr>>,
35    len: Cell<usize>,
36}
37
38impl ExprArena {
39    /// Create an empty expression arena.
40    #[must_use]
41    #[inline]
42    pub fn new() -> Self {
43        Self::default()
44    }
45
46    /// Allocate an expression and return its stable arena handle.
47    #[must_use]
48    pub fn alloc(&self, expr: Expr) -> ExprRef {
49        let index = self.len.get();
50        let ptr = std::ptr::from_ref::<Expr>(self.bump.alloc(expr));
51        // SAFETY: ExprArena is a single-writer builder and never shared across writer threads.
52        unsafe {
53            (*self.exprs.get()).push(ptr);
54        }
55        self.len.set(index + 1);
56        ExprRef { index }
57    }
58
59    /// Borrow an allocated expression by handle.
60    #[must_use]
61    pub fn get(&self, expr_ref: ExprRef) -> Option<&Expr> {
62        // SAFETY: pointers are produced only by `self.bump.alloc` and remain
63        // stable until `reset(&mut self)`, which requires exclusive access.
64        unsafe {
65            let vec: &Vec<*const Expr> = &*self.exprs.get();
66            vec.get(expr_ref.index).and_then(|ptr| ptr.as_ref())
67        }
68    }
69
70    /// Clear allocated expressions.
71    pub fn reset(&mut self) {
72        // SAFETY: we have exclusive mutable access to the arena, so it is safe to
73        // drop all allocated expressions in place.
74        unsafe {
75            let vec = self.exprs.get_mut();
76            for &ptr in vec.iter() {
77                std::ptr::drop_in_place(ptr as *mut Expr);
78            }
79            vec.clear();
80        }
81        self.len.set(0);
82        self.bump.reset();
83    }
84
85    /// Number of expressions allocated in this arena.
86    #[must_use]
87    #[inline]
88    pub fn len(&self) -> usize {
89        self.len.get()
90    }
91
92    /// Return true if no expressions have been allocated.
93    #[must_use]
94    #[inline]
95    pub fn is_empty(&self) -> bool {
96        self.len() == 0
97    }
98}
99
100impl Drop for ExprArena {
101    fn drop(&mut self) {
102        // SAFETY: during destruction, we have exclusive access to the arena, so it is safe
103        // to drop all allocated expressions in place.
104        unsafe {
105            let vec = self.exprs.get_mut();
106            for &ptr in vec.iter() {
107                std::ptr::drop_in_place(ptr as *mut Expr);
108            }
109        }
110    }
111}
112
113/// Lightweight program scaffold for arena-backed expression builders.
114pub struct ArenaProgram<'a> {
115    arena: &'a ExprArena,
116    buffers: Vec<BufferDecl>,
117    buffer_index: FxHashMap<Arc<str>, usize>,
118    workgroup_size: [u32; 3],
119    entry: Vec<ExprRef>,
120}
121
122impl<'a> ArenaProgram<'a> {
123    pub(crate) fn new(
124        arena: &'a ExprArena,
125        buffers: Vec<BufferDecl>,
126        workgroup_size: [u32; 3],
127    ) -> Self {
128        let mut buffer_index = FxHashMap::default();
129        buffer_index.reserve(buffers.len());
130        for (index, buffer) in buffers.iter().enumerate() {
131            buffer_index
132                .entry(Arc::clone(&buffer.name))
133                .or_insert(index);
134        }
135        Self {
136            arena,
137            buffers,
138            buffer_index,
139            workgroup_size,
140            entry: Vec::new(),
141        }
142    }
143
144    /// Allocate `expr` in the backing arena and append it to the entry list.
145    #[must_use]
146    pub fn push_expr(&mut self, expr: Expr) -> ExprRef {
147        let expr_ref = self.arena.alloc(expr);
148        self.entry.push(expr_ref);
149        expr_ref
150    }
151
152    /// Return an expression previously appended to this arena program.
153    #[must_use]
154    pub fn expr(&self, expr_ref: ExprRef) -> Option<&Expr> {
155        self.arena.get(expr_ref)
156    }
157
158    /// Declared buffers.
159    #[must_use]
160    pub fn buffers(&self) -> &[BufferDecl] {
161        &self.buffers
162    }
163
164    /// Look up a declared buffer by name.
165    #[must_use]
166    pub fn buffer(&self, name: &str) -> Option<&BufferDecl> {
167        self.buffer_index
168            .get(name)
169            .and_then(|&index| self.buffers.get(index))
170    }
171
172    /// Workgroup dimensions.
173    #[must_use]
174    pub fn workgroup_size(&self) -> [u32; 3] {
175        self.workgroup_size
176    }
177
178    /// Entry expression handles in append order.
179    #[must_use]
180    pub fn entry(&self) -> &[ExprRef] {
181        &self.entry
182    }
183}
184
185#[cfg(test)]
186mod tests {
187    use super::{ArenaProgram, ExprArena};
188    use crate::ir_inner::model::expr::Expr;
189    use crate::ir_inner::model::program::BufferDecl;
190    use crate::ir_inner::model::types::DataType;
191
192    #[test]
193    fn arena_allocates_stable_expression_refs() {
194        let arena = ExprArena::new();
195        let first = arena.alloc(Expr::u32(7));
196        let second = arena.alloc(Expr::var("x"));
197        assert_eq!(first.index(), 0);
198        assert_eq!(second.index(), 1);
199        assert_eq!(arena.get(first), Some(&Expr::u32(7)));
200        assert_eq!(arena.get(second), Some(&Expr::var("x")));
201    }
202
203    #[test]
204    fn arena_program_keeps_buffers_and_expression_handles() {
205        let arena = ExprArena::new();
206        let mut program = ArenaProgram::new(
207            &arena,
208            vec![BufferDecl::read("input", 0, DataType::U32)],
209            [64, 1, 1],
210        );
211        let expr_ref = program.push_expr(Expr::load("input", Expr::u32(0)));
212        assert_eq!(program.entry(), &[expr_ref]);
213        assert_eq!(program.buffer("input").map(BufferDecl::binding), Some(0));
214        assert_eq!(
215            program.expr(expr_ref),
216            Some(&Expr::load("input", Expr::u32(0)))
217        );
218    }
219}