#![cfg(feature = "cuda-oxide-backend")]
use std::collections::HashMap;
use cranelift_codegen::ir as cl;
use crate::lowered_ir::{LoweredBlockId, LoweredValueId};
pub struct LoweringBuilder {
value_map: HashMap<cl::Value, LoweredValueId>,
block_map: HashMap<cl::Block, LoweredBlockId>,
stack_slot_map: HashMap<cl::StackSlot, LoweredValueId>,
next_value_id: LoweredValueId,
next_block_id: LoweredBlockId,
}
impl LoweringBuilder {
pub fn new() -> Self {
Self {
value_map: HashMap::new(),
block_map: HashMap::new(),
stack_slot_map: HashMap::new(),
next_value_id: 0,
next_block_id: 0,
}
}
pub fn alloc_value(&mut self) -> LoweredValueId {
let id = self.next_value_id;
self.next_value_id = self
.next_value_id
.checked_add(1)
.expect("LoweredValueId counter overflowed u32 — function too large");
id
}
pub fn alloc_block(&mut self) -> LoweredBlockId {
let id = self.next_block_id;
self.next_block_id = self
.next_block_id
.checked_add(1)
.expect("LoweredBlockId counter overflowed u32 — function too large");
id
}
pub fn get_or_alloc_value(&mut self, v: cl::Value) -> LoweredValueId {
if let Some(&id) = self.value_map.get(&v) {
return id;
}
let id = self.alloc_value();
self.value_map.insert(v, id);
id
}
pub fn get_or_alloc_block(&mut self, b: cl::Block) -> LoweredBlockId {
if let Some(&id) = self.block_map.get(&b) {
return id;
}
let id = self.alloc_block();
self.block_map.insert(b, id);
id
}
pub fn get_or_alloc_stack_slot(&mut self, ss: cl::StackSlot) -> LoweredValueId {
if let Some(&id) = self.stack_slot_map.get(&ss) {
return id;
}
let id = self.alloc_value();
self.stack_slot_map.insert(ss, id);
id
}
pub fn lookup_value(&self, v: cl::Value) -> Option<LoweredValueId> {
self.value_map.get(&v).copied()
}
pub fn lookup_block(&self, b: cl::Block) -> Option<LoweredBlockId> {
self.block_map.get(&b).copied()
}
pub fn lookup_stack_slot(&self, ss: cl::StackSlot) -> Option<LoweredValueId> {
self.stack_slot_map.get(&ss).copied()
}
pub fn bind_value(&mut self, v: cl::Value, id: LoweredValueId) -> Option<LoweredValueId> {
self.value_map.insert(v, id)
}
pub fn bind_stack_slot(
&mut self,
ss: cl::StackSlot,
id: LoweredValueId,
) -> Option<LoweredValueId> {
self.stack_slot_map.insert(ss, id)
}
pub fn value_map_mut(&mut self) -> &mut HashMap<cl::Value, LoweredValueId> {
&mut self.value_map
}
pub fn next_value_id_mut(&mut self) -> &mut LoweredValueId {
&mut self.next_value_id
}
pub fn value_map(&self) -> &HashMap<cl::Value, LoweredValueId> {
&self.value_map
}
pub fn block_map(&self) -> &HashMap<cl::Block, LoweredBlockId> {
&self.block_map
}
pub fn stack_slot_map(&self) -> &HashMap<cl::StackSlot, LoweredValueId> {
&self.stack_slot_map
}
#[allow(clippy::type_complexity)]
pub fn split_borrow_for_memory(
&mut self,
) -> (
&HashMap<cl::Value, LoweredValueId>,
&mut HashMap<cl::StackSlot, LoweredValueId>,
&mut LoweredValueId,
) {
(
&self.value_map,
&mut self.stack_slot_map,
&mut self.next_value_id,
)
}
}
impl Default for LoweringBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use cranelift_codegen::entity::EntityRef;
fn v(n: u32) -> cl::Value {
cl::Value::new(n as usize)
}
fn b(n: u32) -> cl::Block {
cl::Block::new(n as usize)
}
fn ss(n: u32) -> cl::StackSlot {
cl::StackSlot::new(n as usize)
}
#[test]
fn alloc_value_returns_sequential_ids_starting_at_zero() {
let mut b = LoweringBuilder::new();
assert_eq!(b.alloc_value(), 0);
assert_eq!(b.alloc_value(), 1);
assert_eq!(b.alloc_value(), 2);
assert_eq!(*b.next_value_id_mut(), 3);
}
#[test]
fn alloc_block_returns_sequential_ids_starting_at_zero_independent_of_values() {
let mut bld = LoweringBuilder::new();
bld.alloc_value();
bld.alloc_value();
assert_eq!(bld.alloc_block(), 0);
assert_eq!(bld.alloc_block(), 1);
assert_eq!(bld.alloc_block(), 2);
assert_eq!(*bld.next_value_id_mut(), 2);
}
#[test]
fn get_or_alloc_value_idempotent_and_fresh_on_new() {
let mut bld = LoweringBuilder::new();
let v0 = v(0);
let v1 = v(1);
let id_a = bld.get_or_alloc_value(v0);
let id_b = bld.get_or_alloc_value(v0); assert_eq!(id_a, id_b, "same Value must return same id");
let id_c = bld.get_or_alloc_value(v1);
assert_ne!(id_a, id_c, "different Value must return different id");
assert_eq!(*bld.next_value_id_mut(), 2);
}
#[test]
fn get_or_alloc_block_idempotent_and_fresh_on_new() {
let mut bld = LoweringBuilder::new();
let b0 = b(0);
let b1 = b(1);
let id_a = bld.get_or_alloc_block(b0);
let id_b = bld.get_or_alloc_block(b0);
assert_eq!(id_a, id_b);
let id_c = bld.get_or_alloc_block(b1);
assert_ne!(id_a, id_c);
let next_block_before = bld.alloc_block();
assert_eq!(next_block_before, 2);
}
#[test]
fn lookup_value_returns_none_for_unallocated_some_for_allocated() {
let mut bld = LoweringBuilder::new();
let v0 = v(7);
assert_eq!(bld.lookup_value(v0), None, "unallocated must be None");
let id = bld.get_or_alloc_value(v0);
assert_eq!(
bld.lookup_value(v0),
Some(id),
"after alloc, lookup must return the id"
);
let before = *bld.next_value_id_mut();
let _ = bld.lookup_value(v0);
assert_eq!(*bld.next_value_id_mut(), before);
}
#[test]
fn lookup_block_and_lookup_stack_slot_behave_like_lookup_value() {
let mut bld = LoweringBuilder::new();
let b0 = b(3);
let s0 = ss(5);
assert_eq!(bld.lookup_block(b0), None);
assert_eq!(bld.lookup_stack_slot(s0), None);
let bid = bld.get_or_alloc_block(b0);
let sid = bld.get_or_alloc_stack_slot(s0);
assert_eq!(bld.lookup_block(b0), Some(bid));
assert_eq!(bld.lookup_stack_slot(s0), Some(sid));
}
#[test]
fn bind_value_does_not_advance_counter() {
let mut bld = LoweringBuilder::new();
let v0 = v(0);
let prev = bld.bind_value(v0, 42);
assert_eq!(prev, None, "first bind returns no prior");
assert_eq!(*bld.next_value_id_mut(), 0);
assert_eq!(bld.lookup_value(v0), Some(42));
let prev2 = bld.bind_value(v0, 99);
assert_eq!(prev2, Some(42));
assert_eq!(bld.lookup_value(v0), Some(99));
assert_eq!(*bld.next_value_id_mut(), 0);
}
#[test]
fn value_map_mut_and_next_value_id_mut_expose_inner_state() {
let mut bld = LoweringBuilder::new();
let v0 = v(0);
let v1 = v(1);
{
let map = bld.value_map_mut();
map.insert(v0, 100);
map.insert(v1, 101);
}
{
let next = bld.next_value_id_mut();
*next = 102;
}
assert_eq!(bld.value_map().len(), 2);
assert_eq!(bld.value_map().get(&v0), Some(&100));
assert_eq!(bld.value_map().get(&v1), Some(&101));
assert_eq!(bld.alloc_value(), 102);
assert_eq!(bld.alloc_value(), 103);
}
#[test]
fn get_or_alloc_stack_slot_idempotent_and_uses_value_counter() {
let mut bld = LoweringBuilder::new();
let s0 = ss(0);
let s1 = ss(1);
let pre = bld.alloc_value();
assert_eq!(pre, 0);
let sid_a = bld.get_or_alloc_stack_slot(s0);
let sid_b = bld.get_or_alloc_stack_slot(s0); assert_eq!(sid_a, sid_b, "same StackSlot returns same id");
assert_eq!(sid_a, 1, "stack-slot ids come from the value counter");
let sid_c = bld.get_or_alloc_stack_slot(s1);
assert_ne!(sid_a, sid_c, "different StackSlot gets a new id");
assert_eq!(sid_c, 2);
assert_eq!(bld.lookup_value(v(0)), None);
assert_eq!(bld.lookup_stack_slot(s0), Some(1));
assert_eq!(bld.stack_slot_map().len(), 2);
}
#[test]
fn bind_stack_slot_is_id_stable_and_does_not_advance_counter() {
let mut bld = LoweringBuilder::new();
let s0 = ss(0);
assert_eq!(bld.alloc_value(), 0);
let prev = bld.bind_stack_slot(s0, 0);
assert_eq!(prev, None, "first bind has no previous value");
assert_eq!(bld.lookup_stack_slot(s0), Some(0));
assert_eq!(
bld.alloc_value(),
1,
"bind_stack_slot must not advance the value-id counter"
);
let prev2 = bld.bind_stack_slot(s0, 0);
assert_eq!(prev2, Some(0));
assert_eq!(bld.lookup_stack_slot(s0), Some(0));
assert_eq!(bld.stack_slot_map().len(), 1);
}
#[test]
fn split_borrow_for_memory_exposes_live_state() {
let mut bld = LoweringBuilder::new();
bld.bind_value(v(0), 7);
{
let (value_map, stack_slot_map, next_value_id) = bld.split_borrow_for_memory();
assert_eq!(value_map.get(&v(0)), Some(&7));
stack_slot_map.insert(ss(0), *next_value_id);
*next_value_id += 1;
}
assert_eq!(bld.lookup_stack_slot(ss(0)), Some(0));
assert_eq!(
bld.alloc_value(),
1,
"counter advanced through split borrow"
);
}
#[test]
fn default_matches_new() {
let bld_new = LoweringBuilder::new();
let bld_default = LoweringBuilder::default();
assert_eq!(bld_new.value_map().len(), bld_default.value_map().len());
assert_eq!(bld_new.block_map().len(), bld_default.block_map().len());
assert_eq!(
bld_new.stack_slot_map().len(),
bld_default.stack_slot_map().len()
);
}
#[test]
fn block_and_value_namespaces_are_disjoint() {
let mut bld = LoweringBuilder::new();
let vid = bld.get_or_alloc_value(v(0));
let bid = bld.get_or_alloc_block(b(0));
assert_eq!(vid, 0);
assert_eq!(bid, 0);
assert_eq!(bld.lookup_value(v(0)), Some(0));
assert_eq!(bld.lookup_block(b(0)), Some(0));
assert!(bld.lookup_stack_slot(ss(0)).is_none());
}
}