use std::collections::HashMap;
use rucc_base::Interner;
use rucc_ir::{Meta, MetaNode, Module, PlaneNode};
const UNTYPED: u32 = 0;
const CHARACTER: u32 = 1;
const POINTER_FIRST: u32 = 2;
const FIRST_INTERNED: u32 = POINTER_FIRST + 8;
const LAST_INTERNED: u32 = u32::MAX >> 1;
#[derive(Debug)]
pub struct Plane {
untyped: Meta,
character: Meta,
types: HashMap<Meta, Meta>,
}
impl Plane {
pub fn build(module: &mut Module) -> Self {
let character = module.add_meta(MetaNode::Plane(PlaneNode::Character));
let untyped = module.add_meta(MetaNode::Plane(PlaneNode::NoType));
let named: Vec<Meta> = module
.metadata()
.filter(|&meta| module[meta].tbaa().is_some_and(|node| node.parent.is_some()))
.collect();
let types = named
.into_iter()
.map(|node| (node, module.add_meta(MetaNode::Plane(PlaneNode::Type(node)))))
.collect();
Self { untyped, character, types }
}
#[must_use]
pub fn entry(&self, tbaa: Option<Meta>) -> Meta {
match tbaa.and_then(|node| self.types.get(&node)) {
Some(&entry) => entry,
None if tbaa.is_some() => self.character,
None => self.untyped,
}
}
}
pub(crate) fn numbers(module: &Module, names: &Interner) -> HashMap<Meta, u32> {
module
.metadata()
.filter_map(|meta| Some((meta, number(module, names, module[meta].plane()?))))
.collect()
}
fn number(module: &Module, names: &Interner, node: PlaneNode) -> u32 {
match node {
PlaneNode::NoType => UNTYPED,
PlaneNode::Character => CHARACTER,
PlaneNode::PointerSlot(k) => POINTER_FIRST + u32::from(k),
PlaneNode::Type(ty) => match module[ty].tbaa() {
Some(node) => identifier(names.resolve(node.name)),
None => UNTYPED,
},
}
}
pub(crate) fn identifier(name: &str) -> u32 {
FIRST_INTERNED + fnv(name) % (LAST_INTERNED - FIRST_INTERNED + 1)
}
fn fnv(name: &str) -> u32 {
let mut hash: u32 = 0x811c_9dc5;
for &byte in name.as_bytes() {
hash ^= u32::from(byte);
hash = hash.wrapping_mul(0x0100_0193);
}
hash
}
#[cfg(test)]
mod tests {
use rucc_ir::TbaaNode;
use rucc_target::{Arch, Env, Os, TargetInfo, Triple};
use super::*;
const VOCABULARY: &[&str] = &[
"bool",
"short",
"int",
"long",
"long long",
"__int128",
"_Float16",
"float",
"_Float32",
"double",
"_Float32x",
"_Float64",
"long double",
"_Float64x",
"_Float128",
"_Complex float",
"_Complex double",
"_Complex long double",
"pointer",
"_BitInt(2)",
"_BitInt(7)",
"_BitInt(64)",
"_BitInt(128)",
];
fn module(names: &mut Interner) -> Module {
let target = TargetInfo::new(Triple::new(Arch::X86_64, Os::Linux, Env::Gnu));
Module::new(names.intern("plane.c"), &target)
}
fn tree(module: &mut Module, names: &mut Interner, under: &[&str]) -> (Meta, Vec<Meta>) {
let name = names.intern("char");
let root = module.add_meta(MetaNode::Tbaa(TbaaNode { name, parent: None, offset: 0 }));
let nodes = under
.iter()
.map(|spelling| {
let name = names.intern(spelling);
module.add_meta(MetaNode::Tbaa(TbaaNode { name, parent: Some(root), offset: 0 }))
})
.collect();
(root, nodes)
}
#[test]
fn no_two_types_the_front_end_can_spell_share_a_number() {
let mut seen: Vec<u32> = VOCABULARY.iter().map(|name| identifier(name)).collect();
seen.sort_unstable();
let count = seen.len();
seen.dedup();
assert_eq!(seen.len(), count, "two of {VOCABULARY:?} hash to one number");
}
#[test]
fn no_type_is_numbered_as_one_of_the_values_that_is_not_a_type() {
for name in VOCABULARY {
assert!(identifier(name) >= FIRST_INTERNED, "{name} is numbered as a reserved value");
}
}
#[test]
fn no_type_is_numbered_past_what_the_plane_can_hold() {
for name in VOCABULARY {
assert!(identifier(name) <= LAST_INTERNED, "{name} is numbered past the plane");
}
}
#[test]
fn a_spelling_whose_hash_has_the_top_bit_set_is_still_numbered_inside_the_range() {
for n in 0..2000 {
let name = format!("type{n}");
let id = identifier(&name);
assert!((FIRST_INTERNED..=LAST_INTERNED).contains(&id), "{name} is numbered {id}");
}
}
#[test]
fn the_same_spelling_is_the_same_number_in_two_modules() {
let mut names = Interner::new();
let mut one = module(&mut names);
let (_, first) = tree(&mut one, &mut names, &["long", "int"]);
let mut two = module(&mut names);
let (_, second) = tree(&mut two, &mut names, &["int"]);
let one_plane = Plane::build(&mut one);
let two_plane = Plane::build(&mut two);
let first = numbers(&one, &names)[&one_plane.entry(Some(first[1]))];
let second = numbers(&two, &names)[&two_plane.entry(Some(second[0]))];
assert_eq!(first, second);
}
#[test]
fn a_store_through_a_character_type_records_that_and_not_the_type() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (root, _) = tree(&mut module, &mut names, &["int"]);
let plane = Plane::build(&mut module);
assert_eq!(module[plane.entry(Some(root))], MetaNode::Plane(PlaneNode::Character));
assert_eq!(numbers(&module, &names)[&plane.entry(Some(root))], CHARACTER);
}
#[test]
fn a_store_that_names_no_type_records_that_the_bytes_are_untyped() {
let mut names = Interner::new();
let mut module = module(&mut names);
tree(&mut module, &mut names, &["int"]);
let plane = Plane::build(&mut module);
assert_eq!(module[plane.entry(None)], MetaNode::Plane(PlaneNode::NoType));
assert_eq!(numbers(&module, &names)[&plane.entry(None)], UNTYPED);
}
#[test]
fn two_stores_through_one_type_record_one_entry() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (_, nodes) = tree(&mut module, &mut names, &["int"]);
let plane = Plane::build(&mut module);
assert_eq!(plane.entry(Some(nodes[0])), plane.entry(Some(nodes[0])));
}
#[test]
fn every_type_in_the_module_gets_an_entry_that_points_back_at_it() {
let mut names = Interner::new();
let mut module = module(&mut names);
let (_, nodes) = tree(&mut module, &mut names, &["int", "float"]);
let plane = Plane::build(&mut module);
for node in nodes {
assert_eq!(module[plane.entry(Some(node))], MetaNode::Plane(PlaneNode::Type(node)));
}
}
}