plotnik_compiler/emit/
layout.rs1use std::collections::{BTreeMap, HashSet};
7
8use crate::bytecode::{InstructionIR, Label, LayoutResult};
9
10const CACHE_LINE: usize = 64;
11const STEP_SIZE: usize = 8;
12
13struct Graph {
15 successors: BTreeMap<Label, Vec<Label>>,
17 predecessors: BTreeMap<Label, Vec<Label>>,
19}
20
21impl Graph {
22 fn build(instructions: &[InstructionIR]) -> Self {
23 let mut successors: BTreeMap<Label, Vec<Label>> = BTreeMap::new();
24 let mut predecessors: BTreeMap<Label, Vec<Label>> = BTreeMap::new();
25
26 for instr in instructions {
27 let label = instr.label();
28 successors.entry(label).or_default();
29
30 for succ in instr.successors() {
31 successors.entry(label).or_default().push(succ);
32 predecessors.entry(succ).or_default().push(label);
33 }
34 }
35
36 Self {
37 successors,
38 predecessors,
39 }
40 }
41
42 fn successors(&self, label: Label) -> &[Label] {
43 self.successors
44 .get(&label)
45 .map(|v| v.as_slice())
46 .unwrap_or(&[])
47 }
48
49 fn predecessor_count(&self, label: Label) -> usize {
50 self.predecessors.get(&label).map(|v| v.len()).unwrap_or(0)
51 }
52}
53
54pub struct CacheAligned;
56
57impl CacheAligned {
58 pub fn layout(instructions: &[InstructionIR], entries: &[Label]) -> LayoutResult {
62 if instructions.is_empty() {
63 return LayoutResult::empty();
64 }
65
66 let graph = Graph::build(instructions);
67 let label_to_instr: BTreeMap<Label, &InstructionIR> =
68 instructions.iter().map(|i| (i.label(), i)).collect();
69
70 let chains = extract_chains(&graph, instructions, entries);
71 let ordered = order_chains(chains, entries);
72
73 assign_step_ids(ordered, &label_to_instr)
74 }
75}
76
77fn extract_chains(
79 graph: &Graph,
80 instructions: &[InstructionIR],
81 entries: &[Label],
82) -> Vec<Vec<Label>> {
83 let mut visited = HashSet::new();
84 let mut chains = Vec::new();
85
86 for &entry in entries {
88 if visited.contains(&entry) {
89 continue;
90 }
91 chains.push(build_chain(entry, graph, &mut visited));
92 }
93
94 for instr in instructions {
96 let label = instr.label();
97 if visited.contains(&label) {
98 continue;
99 }
100 chains.push(build_chain(label, graph, &mut visited));
101 }
102
103 chains
104}
105
106fn build_chain(start: Label, graph: &Graph, visited: &mut HashSet<Label>) -> Vec<Label> {
110 let mut chain = vec![start];
111 visited.insert(start);
112
113 let mut current = start;
114 while let [next] = graph.successors(current)
115 && !visited.contains(next)
116 && graph.predecessor_count(*next) == 1
117 {
118 chain.push(*next);
119 visited.insert(*next);
120 current = *next;
121 }
122
123 chain
124}
125
126fn order_chains(mut chains: Vec<Vec<Label>>, entries: &[Label]) -> Vec<Vec<Label>> {
128 let entry_set: HashSet<Label> = entries.iter().copied().collect();
129
130 let (mut entry_chains, mut other_chains): (Vec<_>, Vec<_>) =
132 chains.drain(..).partition(|chain| {
133 chain
134 .first()
135 .map(|l| entry_set.contains(l))
136 .unwrap_or(false)
137 });
138
139 other_chains.sort_by_key(|chain| std::cmp::Reverse(chain.len()));
141
142 entry_chains.extend(other_chains);
144 entry_chains
145}
146
147fn assign_step_ids(
149 chains: Vec<Vec<Label>>,
150 label_to_instr: &BTreeMap<Label, &InstructionIR>,
151) -> LayoutResult {
152 let mut mapping = BTreeMap::new();
153
154 let mut current_step = 0u16;
155 let mut current_offset = 0usize; for chain in chains {
158 for label in chain {
159 let Some(instr) = label_to_instr.get(&label) else {
160 continue;
161 };
162 let size = instr.size();
163
164 let line_offset = current_offset % CACHE_LINE;
166 if line_offset + size > CACHE_LINE {
167 let padding_bytes = CACHE_LINE - line_offset;
168 let padding_steps = (padding_bytes / STEP_SIZE) as u16;
169 current_step += padding_steps;
170 current_offset += padding_bytes;
171 }
172
173 assert!(
175 current_offset % CACHE_LINE + size <= CACHE_LINE,
176 "instruction at offset {} with size {} straddles 64-byte cache line",
177 current_offset,
178 size
179 );
180
181 mapping.insert(label, current_step);
182 let step_count = (size / STEP_SIZE) as u16;
183 current_step += step_count;
184 current_offset += size;
185 }
186 }
187
188 LayoutResult::new(mapping, current_step)
189}