Skip to main content

rucc_opt/
readonly.rs

1//! Read only data a function pass has come to need, on its way to the module.
2//!
3//! A [`crate::Pass`] is handed one function and nothing else, which is what lets it be reasoned
4//! about alone and is why it cannot add a global: the globals are the module's, and the module is
5//! the thing the pass is not given. Switch conversion to a lookup table is the one pass that needs
6//! one anyway. Section 24.4 of `spec/optimizer/24-switch-lowering.md` puts it in the middle end so
7//! that what it writes is an ordinary load every pass after it can read, and a load has to load
8//! from somewhere.
9//!
10//! So the pass asks this for a name, writes its load against that name, and leaves the table here.
11//! The pipeline adds every table to the module as soon as the pass has finished with the function,
12//! before the verifier looks at it, so no function is ever seen naming a table the module does not
13//! have. The same shape as `crate::libcall`, which is handed the module whole because it needs
14//! one, but narrower: a pass that goes through this can add a constant array and do nothing else
15//! to the module, which is what keeps it a function pass.
16
17use std::collections::HashSet;
18
19use rucc_base::{Interner, Symbol};
20use rucc_ir::Type;
21
22/// One array a pass has asked for.
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct Table {
25    /// The name its load was written against.
26    pub name: Symbol,
27    /// The type of every cell, which is an integer of a whole number of bytes.
28    pub ty: Type,
29    /// The cells in order, each read with its own sign and held at the type's width when written.
30    pub cells: Vec<i128>,
31}
32
33/// Where a pass puts the tables it asks for, until the pipeline takes them.
34#[derive(Debug)]
35pub struct ReadOnly<'a> {
36    names: &'a mut Interner,
37    taken: &'a HashSet<Symbol>,
38    pointer_bits: u32,
39    next: u32,
40    tables: Vec<Table>,
41}
42
43impl<'a> ReadOnly<'a> {
44    /// A place for tables in a module where `taken` are the names already in use.
45    ///
46    /// `next` is the number the next name is made from. The pipeline makes one of these for every
47    /// function a pass runs over, so the count is handed in and read back out with
48    /// [`ReadOnly::next`], and two functions never get the same name.
49    #[must_use]
50    pub fn new(
51        names: &'a mut Interner,
52        taken: &'a HashSet<Symbol>,
53        pointer_bits: u32,
54        next: u32,
55    ) -> Self {
56        Self { names, taken, pointer_bits, next, tables: Vec::new() }
57    }
58
59    /// The width of an address on the target, which is how wide an index into a table is made.
60    #[must_use]
61    pub const fn pointer_bits(&self) -> u32 {
62        self.pointer_bits
63    }
64
65    /// Asks for a table and gets back the name to load from it by.
66    ///
67    /// The name is gcc's, `CSWTCH.` and a number, which nothing written in C can spell because of
68    /// the dot and which reads the same in a disassembly of either compiler. A name the module
69    /// already has is stepped over rather than trusted not to be there, since an `asm` label can
70    /// spell anything.
71    pub fn table(&mut self, ty: Type, cells: Vec<i128>) -> Symbol {
72        let name = loop {
73            let name = self.names.intern(&format!("CSWTCH.{}", self.next));
74            self.next += 1;
75            if !self.taken.contains(&name) {
76                break name;
77            }
78        };
79        self.tables.push(Table { name, ty, cells });
80        name
81    }
82
83    /// The number the next name will be made from.
84    #[must_use]
85    pub const fn next(&self) -> u32 {
86        self.next
87    }
88
89    /// Every table asked for so far, in the order they were asked for.
90    #[must_use]
91    pub fn into_tables(self) -> Vec<Table> {
92        self.tables
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use std::collections::HashSet;
99
100    use rucc_base::Interner;
101    use rucc_ir::Type;
102
103    use super::ReadOnly;
104
105    #[test]
106    fn two_tables_get_two_names_and_a_taken_name_is_stepped_over() {
107        let mut names = Interner::new();
108        let taken: HashSet<_> = [names.intern("CSWTCH.1")].into_iter().collect();
109        let mut data = ReadOnly::new(&mut names, &taken, 64, 0);
110        let first = data.table(Type::int(8), vec![1, 2]);
111        let second = data.table(Type::int(8), vec![3]);
112        assert_eq!(data.next(), 3);
113        let tables = data.into_tables();
114        assert_eq!(tables.len(), 2);
115        assert_eq!(names.resolve(first), "CSWTCH.0");
116        assert_eq!(names.resolve(second), "CSWTCH.2");
117    }
118}