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}