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    /// What each cell also holds the distance to, in the same order, or nothing at all when every
32    /// cell is only a number. A cell with a name here is that name's address less the address of
33    /// the cell's own table, plus its number, which the linker works out.
34    pub to: Vec<Option<Symbol>>,
35}
36
37/// Where a pass puts the tables it asks for, until the pipeline takes them.
38#[derive(Debug)]
39pub struct ReadOnly<'a> {
40    names: &'a mut Interner,
41    taken: &'a HashSet<Symbol>,
42    pointer_bits: u32,
43    measures: bool,
44    next: u32,
45    tables: Vec<Table>,
46}
47
48impl<'a> ReadOnly<'a> {
49    /// A place for tables in a module where `taken` are the names already in use.
50    ///
51    /// `next` is the number the next name is made from. The pipeline makes one of these for every
52    /// function a pass runs over, so the count is handed in and read back out with
53    /// [`ReadOnly::next`], and two functions never get the same name.
54    #[must_use]
55    pub fn new(
56        names: &'a mut Interner,
57        taken: &'a HashSet<Symbol>,
58        pointer_bits: u32,
59        next: u32,
60    ) -> Self {
61        Self { names, taken, pointer_bits, measures: false, next, tables: Vec::new() }
62    }
63
64    /// The same place, able to hold a table of distances when `measures` is true.
65    #[must_use]
66    pub const fn measuring(mut self, measures: bool) -> Self {
67        self.measures = measures;
68        self
69    }
70
71    /// Whether a cell may be how far a name is from its table, which [`ReadOnly::distances`]
72    /// makes.
73    ///
74    /// That needs a four byte relocation measured from where it is written. x86-64 ELF has one,
75    /// and it is the only target the pipeline says yes for.
76    #[must_use]
77    pub const fn measures(&self) -> bool {
78        self.measures
79    }
80
81    /// The width of an address on the target, which is how wide an index into a table is made.
82    #[must_use]
83    pub const fn pointer_bits(&self) -> u32 {
84        self.pointer_bits
85    }
86
87    /// Asks for a table and gets back the name to load from it by.
88    ///
89    /// The name is gcc's, `CSWTCH.` and a number, which nothing written in C can spell because of
90    /// the dot and which reads the same in a disassembly of either compiler. A name the module
91    /// already has is stepped over rather than trusted not to be there, since an `asm` label can
92    /// spell anything.
93    pub fn table(&mut self, ty: Type, cells: Vec<i128>) -> Symbol {
94        let name = self.name();
95        self.tables.push(Table { name, ty, cells, to: Vec::new() });
96        name
97    }
98
99    /// Asks for a table of how far names are from it and gets back the name to load from it by.
100    ///
101    /// Cell `k` is four bytes holding the address of the name in `to[k]` plus the bytes beside it,
102    /// less the address of the table, and zero where `to[k]` is `None`, which is a hole nothing
103    /// reads. Only asked for where [`ReadOnly::measures`] says it may be, and named the way
104    /// [`ReadOnly::table`] names one.
105    pub fn distances(&mut self, to: &[Option<(Symbol, i128)>]) -> Symbol {
106        let name = self.name();
107        let cells = to.iter().map(|cell| cell.map_or(0, |(_, bytes)| bytes)).collect();
108        let to = to.iter().map(|cell| cell.map(|(name, _)| name)).collect();
109        self.tables.push(Table { name, ty: Type::int(32), cells, to });
110        name
111    }
112
113    /// A name for the next table, which is one the module does not have.
114    fn name(&mut self) -> Symbol {
115        loop {
116            let name = self.names.intern(&format!("CSWTCH.{}", self.next));
117            self.next += 1;
118            if !self.taken.contains(&name) {
119                return name;
120            }
121        }
122    }
123
124    /// The number the next name will be made from.
125    #[must_use]
126    pub const fn next(&self) -> u32 {
127        self.next
128    }
129
130    /// Every table asked for so far, in the order they were asked for.
131    #[must_use]
132    pub fn into_tables(self) -> Vec<Table> {
133        self.tables
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    use std::collections::HashSet;
140
141    use rucc_base::Interner;
142    use rucc_ir::Type;
143
144    use super::ReadOnly;
145
146    #[test]
147    fn two_tables_get_two_names_and_a_taken_name_is_stepped_over() {
148        let mut names = Interner::new();
149        let taken: HashSet<_> = [names.intern("CSWTCH.1")].into_iter().collect();
150        let mut data = ReadOnly::new(&mut names, &taken, 64, 0);
151        let first = data.table(Type::int(8), vec![1, 2]);
152        let second = data.table(Type::int(8), vec![3]);
153        assert_eq!(data.next(), 3);
154        let tables = data.into_tables();
155        assert_eq!(tables.len(), 2);
156        assert_eq!(names.resolve(first), "CSWTCH.0");
157        assert_eq!(names.resolve(second), "CSWTCH.2");
158    }
159
160    #[test]
161    fn a_table_of_distances_is_four_byte_cells_named_like_any_other() {
162        let mut names = Interner::new();
163        let taken = HashSet::new();
164        let to = [names.intern("a"), names.intern("b")].map(Some);
165        let mut data = ReadOnly::new(&mut names, &taken, 64, 0).measuring(true);
166        assert!(data.measures());
167        let name = data.distances(&[to[0].map(|it| (it, 0)), None, to[1].map(|it| (it, 8))]);
168        let tables = data.into_tables();
169        assert_eq!(names.resolve(name), "CSWTCH.0");
170        assert_eq!(tables[0].ty, Type::int(32));
171        assert_eq!(tables[0].cells, [0, 0, 8]);
172        assert_eq!(tables[0].to, [to[0], None, to[1]]);
173    }
174}