Skip to main content

celox_analysis/
ssa.rs

1//! Sparse pruned SSA construction over caller-defined variable identities.
2
3use std::collections::{BTreeMap, BTreeSet, VecDeque};
4use std::fmt;
5
6use crate::cfg::{ControlFlowGraph, ForwardControlFlowGraph};
7
8/// Minimal CFG view required by SSA construction.
9///
10/// Clients that already own dominance information can implement this view
11/// without rebuilding postdominators, control dependence, SCCs, or loops.
12pub trait SsaCfg {
13    type FrontierIter<'a>: Iterator<Item = usize>
14    where
15        Self: 'a;
16
17    fn root(&self) -> usize;
18    fn predecessors(&self) -> &[Vec<usize>];
19    fn successors(&self) -> &[Vec<usize>];
20    fn dominator_children(&self) -> &[Vec<usize>];
21    fn dominance_frontier_len(&self) -> usize;
22    fn dominance_frontier(&self, block: usize) -> Self::FrontierIter<'_>;
23}
24
25impl SsaCfg for ControlFlowGraph {
26    type FrontierIter<'a> = std::iter::Copied<std::slice::Iter<'a, usize>>;
27
28    fn root(&self) -> usize {
29        self.root
30    }
31
32    fn predecessors(&self) -> &[Vec<usize>] {
33        &self.predecessors
34    }
35
36    fn successors(&self) -> &[Vec<usize>] {
37        &self.successors
38    }
39
40    fn dominator_children(&self) -> &[Vec<usize>] {
41        &self.dominators.children
42    }
43
44    fn dominance_frontier_len(&self) -> usize {
45        self.dominance_frontier.len()
46    }
47
48    fn dominance_frontier(&self, block: usize) -> Self::FrontierIter<'_> {
49        self.dominance_frontier[block].iter().copied()
50    }
51}
52
53impl SsaCfg for ForwardControlFlowGraph {
54    type FrontierIter<'a> = std::iter::Copied<std::slice::Iter<'a, usize>>;
55
56    fn root(&self) -> usize {
57        self.root
58    }
59
60    fn predecessors(&self) -> &[Vec<usize>] {
61        &self.predecessors
62    }
63
64    fn successors(&self) -> &[Vec<usize>] {
65        &self.successors
66    }
67
68    fn dominator_children(&self) -> &[Vec<usize>] {
69        &self.dominators.children
70    }
71
72    fn dominance_frontier_len(&self) -> usize {
73        self.dominance_frontier.len()
74    }
75
76    fn dominance_frontier(&self, block: usize) -> Self::FrontierIter<'_> {
77        self.dominance_frontier[block].iter().copied()
78    }
79}
80
81#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
82pub enum Version<V, D> {
83    Entry(V),
84    Definition { variable: V, definition: D },
85    Phi { variable: V, block: usize },
86}
87
88#[derive(Debug, Clone, Copy, PartialEq, Eq)]
89pub enum Event<V, D, U> {
90    Use { variable: V, usage: U },
91    Definition { variable: V, definition: D },
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
95pub struct Phi<V, D> {
96    pub variable: V,
97    pub block: usize,
98    pub version: Version<V, D>,
99    pub inputs: Vec<(usize, Version<V, D>)>,
100}
101
102#[derive(Debug, Clone, PartialEq, Eq)]
103pub struct SparseSsa<V, D, U> {
104    pub phis: Vec<Phi<V, D>>,
105    pub phis_by_block: Vec<Vec<usize>>,
106    pub uses: BTreeMap<U, Version<V, D>>,
107}
108
109#[derive(Debug, Clone, PartialEq, Eq)]
110pub struct SsaError {
111    pub rule: &'static str,
112    pub block: Option<usize>,
113    pub message: String,
114}
115
116impl SsaError {
117    fn new(rule: &'static str, block: Option<usize>, message: impl Into<String>) -> Self {
118        Self {
119            rule,
120            block,
121            message: message.into(),
122        }
123    }
124}
125
126impl fmt::Display for SsaError {
127    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
128        write!(formatter, "{}", self.rule)?;
129        if let Some(block) = self.block {
130            write!(formatter, " at block {block}")?;
131        }
132        write!(formatter, ": {}", self.message)
133    }
134}
135
136impl std::error::Error for SsaError {}
137
138/// Construct pruned SSA from events already ordered within each dense block.
139pub fn build<V, D, U>(
140    cfg: &impl SsaCfg,
141    events: &[Vec<Event<V, D, U>>],
142) -> Result<SparseSsa<V, D, U>, SsaError>
143where
144    V: Copy + Ord,
145    D: Copy + Ord,
146    U: Copy + Ord,
147{
148    let blocks = cfg.successors().len();
149    if events.len() != blocks
150        || cfg.predecessors().len() != blocks
151        || cfg.dominator_children().len() != blocks
152        || cfg.dominance_frontier_len() != blocks
153    {
154        return Err(SsaError::new(
155            "SSA.MODEL_SHAPE",
156            None,
157            "CFG and event tables do not cover the same block domain",
158        ));
159    }
160
161    let mut definitions = BTreeMap::<V, BTreeSet<usize>>::new();
162    let mut definition_ids = BTreeSet::<(V, D)>::new();
163    let mut upward_uses = BTreeMap::<V, Vec<usize>>::new();
164    let mut usage_ids = BTreeSet::<U>::new();
165    for (block, block_events) in events.iter().enumerate() {
166        let mut locally_defined = BTreeSet::<V>::new();
167        for event in block_events {
168            match *event {
169                Event::Use { variable, usage } => {
170                    if !usage_ids.insert(usage) {
171                        return Err(SsaError::new(
172                            "SSA.USE_IDENTITY",
173                            Some(block),
174                            "one use identity occurs more than once",
175                        ));
176                    }
177                    if !locally_defined.contains(&variable) {
178                        let uses = upward_uses.entry(variable).or_default();
179                        if uses.last().copied() != Some(block) {
180                            uses.push(block);
181                        }
182                    }
183                }
184                Event::Definition {
185                    variable,
186                    definition,
187                } => {
188                    if !definition_ids.insert((variable, definition)) {
189                        return Err(SsaError::new(
190                            "SSA.DEFINITION_IDENTITY",
191                            Some(block),
192                            "one variable-definition identity occurs more than once",
193                        ));
194                    }
195                    locally_defined.insert(variable);
196                    definitions.entry(variable).or_default().insert(block);
197                }
198            }
199        }
200    }
201
202    drop(definition_ids);
203    // Phi placement only needs the current variable's live-in set. Retaining
204    // all (variable, block) pairs makes large sparse memory partitions consume
205    // gigabytes. Reuse block marks without clearing every block per variable.
206    let mut defined = vec![0usize; blocks];
207    let mut live_in = vec![0usize; blocks];
208    let mut queued = vec![0usize; blocks];
209    let mut work = VecDeque::new();
210    let mut phi_pairs = BTreeSet::<(usize, V)>::new();
211    for (index, (&variable, original_definitions)) in definitions.iter().enumerate() {
212        let Some(uses) = upward_uses.get(&variable) else {
213            continue;
214        };
215        let generation = index + 1;
216        for &block in original_definitions {
217            defined[block] = generation;
218        }
219        for &block in uses {
220            live_in[block] = generation;
221            work.push_back(block);
222        }
223        while let Some(block) = work.pop_front() {
224            for &predecessor in &cfg.predecessors()[block] {
225                if defined[predecessor] != generation && live_in[predecessor] != generation {
226                    live_in[predecessor] = generation;
227                    work.push_back(predecessor);
228                }
229            }
230        }
231        for &block in original_definitions {
232            queued[block] = generation;
233            work.push_back(block);
234        }
235        while let Some(definition) = work.pop_front() {
236            for frontier in cfg.dominance_frontier(definition) {
237                if live_in[frontier] != generation || !phi_pairs.insert((frontier, variable)) {
238                    continue;
239                }
240                if queued[frontier] != generation {
241                    queued[frontier] = generation;
242                    work.push_back(frontier);
243                }
244            }
245        }
246    }
247    drop((defined, live_in, queued, work, definitions, upward_uses));
248
249    let mut phis = Vec::<Phi<V, D>>::with_capacity(phi_pairs.len());
250    let mut phis_by_block = vec![Vec::<usize>::new(); blocks];
251    for (block, variable) in phi_pairs {
252        let phi = phis.len();
253        phis.push(Phi {
254            variable,
255            block,
256            version: Version::Phi { variable, block },
257            inputs: Vec::with_capacity(cfg.predecessors()[block].len()),
258        });
259        phis_by_block[block].push(phi);
260    }
261
262    enum Action<V, D> {
263        Enter(usize),
264        Exit(Vec<(V, Option<Version<V, D>>)>),
265    }
266    let mut current = BTreeMap::<V, Version<V, D>>::new();
267    let mut uses = BTreeMap::<U, Version<V, D>>::new();
268    let mut actions = vec![Action::Enter(cfg.root())];
269    while let Some(action) = actions.pop() {
270        let block = match action {
271            Action::Exit(changes) => {
272                for (variable, previous) in changes.into_iter().rev() {
273                    if let Some(previous) = previous {
274                        current.insert(variable, previous);
275                    } else {
276                        current.remove(&variable);
277                    }
278                }
279                continue;
280            }
281            Action::Enter(block) => block,
282        };
283        let mut changes = Vec::new();
284        for &phi in &phis_by_block[block] {
285            let variable = phis[phi].variable;
286            changes.push((variable, current.insert(variable, phis[phi].version)));
287        }
288        for event in &events[block] {
289            match *event {
290                Event::Use { variable, usage } => {
291                    let version = current
292                        .get(&variable)
293                        .copied()
294                        .unwrap_or(Version::Entry(variable));
295                    if uses.insert(usage, version).is_some() {
296                        return Err(SsaError::new(
297                            "SSA.USE_RENAME",
298                            Some(block),
299                            "dominator rename visited one use more than once",
300                        ));
301                    }
302                }
303                Event::Definition {
304                    variable,
305                    definition,
306                } => {
307                    let version = Version::Definition {
308                        variable,
309                        definition,
310                    };
311                    changes.push((variable, current.insert(variable, version)));
312                }
313            }
314        }
315        for &successor in &cfg.successors()[block] {
316            for &phi in &phis_by_block[successor] {
317                let variable = phis[phi].variable;
318                let version = current
319                    .get(&variable)
320                    .copied()
321                    .unwrap_or(Version::Entry(variable));
322                phis[phi].inputs.push((block, version));
323            }
324        }
325        actions.push(Action::Exit(changes));
326        actions.extend(
327            cfg.dominator_children()[block]
328                .iter()
329                .rev()
330                .copied()
331                .map(Action::Enter),
332        );
333    }
334
335    if uses.len() != usage_ids.len() {
336        return Err(SsaError::new(
337            "SSA.USE_COVERAGE",
338            None,
339            "dominator rename did not visit every use",
340        ));
341    }
342    for phi in &mut phis {
343        phi.inputs
344            .sort_unstable_by_key(|(predecessor, _)| *predecessor);
345        if phi.inputs.len() != cfg.predecessors()[phi.block].len()
346            || phi
347                .inputs
348                .iter()
349                .zip(&cfg.predecessors()[phi.block])
350                .any(|((actual, _), expected)| actual != expected)
351        {
352            return Err(SsaError::new(
353                "SSA.PHI_INPUTS",
354                Some(phi.block),
355                "phi inputs do not cover every CFG predecessor exactly once",
356            ));
357        }
358    }
359
360    Ok(SparseSsa {
361        phis,
362        phis_by_block,
363        uses,
364    })
365}
366
367#[cfg(test)]
368mod tests {
369    use super::*;
370
371    #[test]
372    fn overlapping_variable_lifetimes_preserve_loop_and_local_definition_semantics() {
373        let cfg = ControlFlowGraph::analyze(
374            vec![vec![1, 2], vec![3], vec![3], vec![4, 5], vec![3], vec![]],
375            0,
376        )
377        .unwrap();
378        let (a, b, entry_only, carried) = (3usize, 17usize, usize::MAX - 1, usize::MAX);
379        let definition = |variable, definition| Event::Definition {
380            variable,
381            definition,
382        };
383        let usage = |variable, usage| Event::Use { variable, usage };
384        let events = vec![
385            vec![definition(a, 100), definition(b, 200)],
386            vec![definition(a, 101)],
387            vec![definition(b, 202)],
388            vec![
389                usage(a, 300),
390                usage(carried, 301),
391                definition(b, 203),
392                definition(carried, 303),
393            ],
394            vec![definition(a, 104), usage(b, 400)],
395            vec![usage(a, 500), usage(entry_only, 501), usage(carried, 502)],
396        ];
397        let ssa = build(&cfg, &events).unwrap();
398        assert_eq!(ssa.phis.len(), 2);
399        assert_eq!(ssa.phis[0].variable, a);
400        assert_eq!(ssa.phis[0].block, 3);
401        assert_eq!(
402            ssa.phis[0].inputs,
403            vec![
404                (
405                    1,
406                    Version::Definition {
407                        variable: a,
408                        definition: 101
409                    }
410                ),
411                (
412                    2,
413                    Version::Definition {
414                        variable: a,
415                        definition: 100
416                    }
417                ),
418                (
419                    4,
420                    Version::Definition {
421                        variable: a,
422                        definition: 104
423                    }
424                ),
425            ]
426        );
427        assert_eq!(ssa.phis[1].variable, carried);
428        assert_eq!(ssa.phis[1].block, 3);
429        assert_eq!(
430            ssa.phis[1].inputs,
431            vec![
432                (1, Version::Entry(carried)),
433                (2, Version::Entry(carried)),
434                (
435                    4,
436                    Version::Definition {
437                        variable: carried,
438                        definition: 303
439                    }
440                ),
441            ]
442        );
443        assert_eq!(
444            ssa.uses[&300],
445            Version::Phi {
446                variable: a,
447                block: 3
448            }
449        );
450        assert_eq!(ssa.uses[&500], ssa.uses[&300]);
451        assert_eq!(
452            ssa.uses[&301],
453            Version::Phi {
454                variable: carried,
455                block: 3
456            }
457        );
458        assert_eq!(
459            ssa.uses[&400],
460            Version::Definition {
461                variable: b,
462                definition: 203
463            }
464        );
465        assert_eq!(ssa.uses[&501], Version::Entry(entry_only));
466        assert_eq!(
467            ssa.uses[&502],
468            Version::Definition {
469                variable: carried,
470                definition: 303
471            }
472        );
473    }
474
475    #[test]
476    fn many_live_variables_in_a_long_chain_reach_their_entry_definitions() {
477        let blocks = 256;
478        let cfg = ControlFlowGraph::analyze(
479            (0..blocks)
480                .map(|block| {
481                    if block + 1 == blocks {
482                        vec![]
483                    } else {
484                        vec![block + 1]
485                    }
486                })
487                .collect(),
488            0,
489        )
490        .unwrap();
491        let mut events = vec![Vec::new(); blocks];
492        for usage in 0..128 {
493            let variable = usize::MAX - usage;
494            events[0].push(Event::Definition {
495                variable,
496                definition: 0,
497            });
498            events[blocks - 1].push(Event::Use { variable, usage });
499        }
500        let ssa = build(&cfg, &events).unwrap();
501        assert!(ssa.phis.is_empty());
502        assert_eq!(ssa.uses.len(), 128);
503        for usage in 0..128 {
504            assert_eq!(
505                ssa.uses[&usage],
506                Version::Definition {
507                    variable: usize::MAX - usage,
508                    definition: 0
509                }
510            );
511        }
512    }
513
514    #[test]
515    fn branch_definitions_create_one_live_join_phi() {
516        let cfg = ControlFlowGraph::analyze(vec![vec![1, 2], vec![3], vec![3], vec![]], 0).unwrap();
517        let events = vec![
518            vec![],
519            vec![Event::Definition {
520                variable: 7,
521                definition: 10,
522            }],
523            vec![Event::Definition {
524                variable: 7,
525                definition: 20,
526            }],
527            vec![Event::Use {
528                variable: 7,
529                usage: 30,
530            }],
531        ];
532
533        let ssa = build(&cfg, &events).unwrap();
534
535        assert_eq!(ssa.phis.len(), 1);
536        assert_eq!(ssa.phis[0].block, 3);
537        assert_eq!(ssa.phis[0].inputs.len(), 2);
538        assert_eq!(
539            ssa.uses[&30],
540            Version::Phi {
541                variable: 7,
542                block: 3
543            }
544        );
545    }
546
547    #[test]
548    fn dead_join_does_not_receive_a_phi() {
549        let cfg = ControlFlowGraph::analyze(vec![vec![1, 2], vec![3], vec![3], vec![]], 0).unwrap();
550        let events = vec![
551            vec![],
552            vec![Event::Definition {
553                variable: 7,
554                definition: 10,
555            }],
556            vec![Event::Definition {
557                variable: 7,
558                definition: 20,
559            }],
560            vec![],
561        ];
562
563        let ssa = build::<_, _, usize>(&cfg, &events).unwrap();
564
565        assert!(ssa.phis.is_empty());
566    }
567
568    #[test]
569    fn loop_use_observes_header_phi() {
570        let cfg = ControlFlowGraph::analyze(vec![vec![1], vec![2, 3], vec![1], vec![]], 0).unwrap();
571        let events = vec![
572            vec![Event::Definition {
573                variable: 1,
574                definition: 0,
575            }],
576            vec![Event::Use {
577                variable: 1,
578                usage: 10,
579            }],
580            vec![Event::Definition {
581                variable: 1,
582                definition: 20,
583            }],
584            vec![],
585        ];
586
587        let ssa = build(&cfg, &events).unwrap();
588
589        assert_eq!(
590            ssa.uses[&10],
591            Version::Phi {
592                variable: 1,
593                block: 1
594            }
595        );
596        assert_eq!(ssa.phis[0].inputs.len(), 2);
597    }
598
599    #[test]
600    fn same_block_uses_observe_event_order() {
601        let cfg = ControlFlowGraph::analyze(vec![vec![]], 0).unwrap();
602        let events = vec![vec![
603            Event::Use {
604                variable: 1,
605                usage: 1,
606            },
607            Event::Definition {
608                variable: 1,
609                definition: 2,
610            },
611            Event::Use {
612                variable: 1,
613                usage: 3,
614            },
615        ]];
616
617        let ssa = build(&cfg, &events).unwrap();
618
619        assert_eq!(ssa.uses[&1], Version::Entry(1));
620        assert_eq!(
621            ssa.uses[&3],
622            Version::Definition {
623                variable: 1,
624                definition: 2
625            }
626        );
627    }
628}