Skip to main content

solar_codegen/transform/
pre.rs

1//! Partial redundancy elimination for pure MIR expressions.
2//!
3//! This pass handles the conservative PRE case that CSE cannot: an expression
4//! is recomputed in a join block, but is already available along at least as
5//! many incoming edges as the number of edges where it must be inserted. We
6//! only move pure word expressions. A jump-terminated insertion predecessor
7//! receives the computation directly; a branch- or switch-terminated one ends
8//! a critical edge, which is split first so the computation runs only on the
9//! edge into the join.
10//!
11//! Availability at a predecessor's end is checked in the predecessor itself and
12//! then up its dominator tree: a def of the translated expression in any
13//! dominator is available with no further checks, so it can feed the join phi
14//! without inserting a duplicate computation.
15//!
16//! # Termination
17//!
18//! Joins that are mutual predecessors can ping-pong an expression between each
19//! other forever: each rewrite is net-zero and re-creates a candidate in the
20//! other block. Three rules guarantee termination:
21//! 1. An instruction inserted by this run is never picked as an elimination candidate, so every
22//!    rewrite retires an instruction that existed when the run started, bounding rewrites by the
23//!    initial instruction count.
24//! 2. An expression key is never inserted into a block it was previously eliminated from in the
25//!    same run.
26//! 3. A function-size-derived rewrite limit backstops the above.
27//!
28//! Edge splitting does not weaken these rules: split blocks have a single
29//! predecessor, so they are never join targets, and the only instructions they
30//! hold are inserted-this-run and excluded by rule 1.
31
32use crate::{
33    analysis::{CfgInfo, DominatorTree},
34    mir::{
35        BlockId, Function, Immediate, InstId, InstKind, Instruction, InstructionMetadata, MirType,
36        Terminator, Value, ValueId,
37        utils::{repair_reachability_phis, split_edge},
38    },
39    pass::FunctionPass,
40};
41use solar_data_structures::map::{FxHashMap, FxHashSet};
42use std::cmp::Ordering;
43
44const MAX_INSERTIONS_PER_REWRITE: usize = 2;
45
46/// Statistics for pure expression PRE.
47#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
48pub struct PreStats {
49    /// Number of join-block expressions replaced by PRE phis.
50    pub expressions_eliminated: usize,
51    /// Number of predecessor computations inserted.
52    pub expressions_inserted: usize,
53}
54
55impl PreStats {
56    /// Returns the total number of MIR edits made by this pass.
57    pub const fn total(self) -> usize {
58        self.expressions_eliminated + self.expressions_inserted
59    }
60}
61
62/// Partial redundancy eliminator for pure expressions.
63#[derive(Debug, Default)]
64pub struct PartialRedundancyEliminator {
65    stats: PreStats,
66}
67
68/// Function pass for pure expression PRE.
69pub struct PrePass;
70
71impl FunctionPass for PrePass {
72    fn name(&self) -> &str {
73        "pre"
74    }
75
76    fn run_on_function(&mut self, func: &mut Function) -> bool {
77        PartialRedundancyEliminator::new().run(func).total() != 0
78    }
79}
80
81#[derive(Clone, Debug, PartialEq, Eq, Hash)]
82enum ExprKey {
83    Add(OperandKey, OperandKey),
84    Sub(OperandKey, OperandKey),
85    Mul(OperandKey, OperandKey),
86    Div(OperandKey, OperandKey),
87    SDiv(OperandKey, OperandKey),
88    Mod(OperandKey, OperandKey),
89    SMod(OperandKey, OperandKey),
90    Exp(OperandKey, OperandKey),
91    AddMod(OperandKey, OperandKey, OperandKey),
92    MulMod(OperandKey, OperandKey, OperandKey),
93    And(OperandKey, OperandKey),
94    Or(OperandKey, OperandKey),
95    Xor(OperandKey, OperandKey),
96    Not(OperandKey),
97    Shl(OperandKey, OperandKey),
98    Shr(OperandKey, OperandKey),
99    Sar(OperandKey, OperandKey),
100    Byte(OperandKey, OperandKey),
101    Lt(OperandKey, OperandKey),
102    Gt(OperandKey, OperandKey),
103    SLt(OperandKey, OperandKey),
104    SGt(OperandKey, OperandKey),
105    Eq(OperandKey, OperandKey),
106    IsZero(OperandKey),
107    Select(OperandKey, OperandKey, OperandKey),
108    SignExtend(OperandKey, OperandKey),
109}
110
111#[derive(Clone, Debug, PartialEq, Eq, Hash)]
112enum OperandKey {
113    Value(ValueId),
114    Immediate(Immediate),
115}
116
117struct PreCandidate {
118    target: BlockId,
119    inst: InstId,
120    result: ValueId,
121    result_ty: MirType,
122    metadata: InstructionMetadata,
123    incoming: Vec<(BlockId, ValueId)>,
124    insertions: Vec<(BlockId, InstKind)>,
125}
126
127impl PartialRedundancyEliminator {
128    /// Creates a new PRE pass.
129    pub fn new() -> Self {
130        Self::default()
131    }
132
133    /// Returns statistics from the most recent run.
134    pub const fn stats(&self) -> PreStats {
135        self.stats
136    }
137
138    /// Runs PRE to a fixed point.
139    pub fn run(&mut self, func: &mut Function) -> PreStats {
140        self.stats = PreStats::default();
141        repair_reachability_phis(func);
142
143        let mut inst_results = func.inst_results();
144        let mut inst_blocks = func.inst_blocks();
145
146        let mut eliminated_keys = FxHashSet::default();
147        let mut inserted_insts = FxHashSet::default();
148        let rewrite_limit = func.instructions.len().saturating_mul(2).max(64);
149        let mut rewrites = 0usize;
150
151        while rewrites < rewrite_limit {
152            // Edge splitting grows the CFG between batches, so the dominator
153            // tree is recomputed before each scan.
154            let cfg = CfgInfo::new(func);
155            let batch = self.collect_candidates(
156                func,
157                cfg.dominators(),
158                &inst_results,
159                &inst_blocks,
160                &eliminated_keys,
161                &inserted_insts,
162                rewrite_limit - rewrites,
163            );
164            if batch.is_empty() {
165                break;
166            }
167            rewrites += batch.len();
168            for candidate in batch {
169                self.apply_candidate(
170                    func,
171                    candidate,
172                    &mut inst_results,
173                    &mut inst_blocks,
174                    &mut eliminated_keys,
175                    &mut inserted_insts,
176                );
177            }
178            repair_reachability_phis(func);
179        }
180
181        self.stats
182    }
183
184    /// Collects non-interfering candidates from a single scan over the
185    /// function so they can be applied as one batch.
186    #[allow(clippy::too_many_arguments)]
187    fn collect_candidates(
188        &self,
189        func: &Function,
190        dominators: &DominatorTree,
191        inst_results: &FxHashMap<InstId, ValueId>,
192        inst_blocks: &FxHashMap<InstId, BlockId>,
193        eliminated_keys: &FxHashSet<(ExprKey, BlockId)>,
194        inserted_insts: &FxHashSet<InstId>,
195        limit: usize,
196    ) -> Vec<PreCandidate> {
197        let mut batch = Vec::new();
198        // Candidates whose analysis would be invalidated by an earlier
199        // candidate in this batch are deferred to the next scan.
200        let mut modified_blocks: FxHashSet<BlockId> = FxHashSet::default();
201        let mut eliminated_values: FxHashSet<ValueId> = FxHashSet::default();
202
203        'targets: for target in func.blocks.indices() {
204            let predecessors = func.unique_predecessors(target);
205            if predecessors.len() < 2 {
206                continue;
207            }
208
209            for &inst in &func.blocks[target].instructions {
210                if batch.len() >= limit {
211                    break 'targets;
212                }
213                // Termination rule 1: never re-eliminate an instruction this
214                // run inserted.
215                if inserted_insts.contains(&inst) {
216                    continue;
217                }
218                let instruction = &func.instructions[inst];
219                if !Self::is_pre_expression(&instruction.kind) {
220                    continue;
221                }
222                let Some(result_ty) = instruction.result_ty else {
223                    continue;
224                };
225                let Some(&result) = inst_results.get(&inst) else {
226                    continue;
227                };
228
229                let Some(candidate) = self.candidate_for_inst(
230                    func,
231                    target,
232                    inst,
233                    result,
234                    result_ty,
235                    instruction.metadata.clone(),
236                    &predecessors,
237                    inst_results,
238                    inst_blocks,
239                    dominators,
240                    eliminated_keys,
241                ) else {
242                    continue;
243                };
244
245                if Self::interferes_with_batch(&candidate, &modified_blocks, &eliminated_values) {
246                    continue;
247                }
248                modified_blocks.insert(candidate.target);
249                modified_blocks.extend(candidate.insertions.iter().map(|(block, _)| *block));
250                eliminated_values.insert(candidate.result);
251                batch.push(candidate);
252            }
253        }
254
255        batch
256    }
257
258    /// Returns true if applying earlier candidates in the batch invalidates
259    /// this candidate's analysis: its blocks were already rewritten, or it
260    /// references a value whose defining instruction the batch removes.
261    fn interferes_with_batch(
262        candidate: &PreCandidate,
263        modified_blocks: &FxHashSet<BlockId>,
264        eliminated_values: &FxHashSet<ValueId>,
265    ) -> bool {
266        modified_blocks.contains(&candidate.target)
267            || candidate.insertions.iter().any(|(block, _)| modified_blocks.contains(block))
268            || candidate.incoming.iter().any(|(_, value)| eliminated_values.contains(value))
269            || candidate.insertions.iter().any(|(_, kind)| {
270                kind.operands().into_iter().any(|value| eliminated_values.contains(&value))
271            })
272    }
273
274    #[allow(clippy::too_many_arguments)]
275    fn candidate_for_inst(
276        &self,
277        func: &Function,
278        target: BlockId,
279        inst: InstId,
280        result: ValueId,
281        result_ty: MirType,
282        metadata: InstructionMetadata,
283        predecessors: &[BlockId],
284        inst_results: &FxHashMap<InstId, ValueId>,
285        inst_blocks: &FxHashMap<InstId, BlockId>,
286        dominators: &DominatorTree,
287        eliminated_keys: &FxHashSet<(ExprKey, BlockId)>,
288    ) -> Option<PreCandidate> {
289        let original = &func.instructions[inst].kind;
290        let mut incoming = Vec::with_capacity(predecessors.len());
291        let mut insertions = Vec::new();
292        let mut available = 0usize;
293
294        for &pred in predecessors {
295            let translated =
296                Self::translate_kind_for_predecessor(func, original, target, pred, inst_blocks)?;
297            if !Self::operands_available_at_end(func, &translated, pred, inst_blocks, dominators) {
298                return None;
299            }
300            let key = Self::make_expr_key(func, &translated)?;
301            if let Some(value) =
302                Self::available_value_at_end(func, dominators, pred, &key, inst_results)
303            {
304                available += 1;
305                incoming.push((pred, value));
306                continue;
307            }
308
309            // Termination rule 2: never insert an expression into a block it
310            // was previously eliminated from, which would ping-pong it between
311            // mutually-preceding join blocks.
312            if eliminated_keys.contains(&(key, pred)) {
313                return None;
314            }
315            insertions.push((pred, translated));
316        }
317
318        // Every insertion must be paid for by a predecessor where the
319        // expression is already available, so no path computes it more often
320        // than before; paths through available predecessors compute it
321        // strictly less often. The constant bounds code growth at joins with
322        // many predecessors.
323        if insertions.len() > available
324            || insertions.len() > MAX_INSERTIONS_PER_REWRITE.max(available)
325        {
326            return None;
327        }
328
329        Some(PreCandidate { target, inst, result, result_ty, metadata, incoming, insertions })
330    }
331
332    fn apply_candidate(
333        &mut self,
334        func: &mut Function,
335        candidate: PreCandidate,
336        inst_results: &mut FxHashMap<InstId, ValueId>,
337        inst_blocks: &mut FxHashMap<InstId, BlockId>,
338        eliminated_keys: &mut FxHashSet<(ExprKey, BlockId)>,
339        inserted_insts: &mut FxHashSet<InstId>,
340    ) {
341        let PreCandidate { target, inst, result, result_ty, metadata, mut incoming, insertions } =
342            candidate;
343
344        if let Some(key) = Self::make_expr_key(func, &func.instructions[inst].kind) {
345            eliminated_keys.insert((key, target));
346        }
347
348        let fully_available = insertions.is_empty();
349        for (pred, kind) in insertions {
350            // A jump-terminated predecessor owns its single outgoing edge, so
351            // the computation can go at its end. Any other terminator makes
352            // the edge critical: split it so the computation runs only on the
353            // edge into the join. The split block sits on that edge, so the
354            // per-edge phi translation that held for `pred` holds for it too.
355            let block = match func.blocks[pred].terminator {
356                Some(Terminator::Jump(jump_target)) => {
357                    debug_assert_eq!(jump_target, target);
358                    pred
359                }
360                _ => split_edge(func, pred, target),
361            };
362            let new_inst = func.alloc_inst(Instruction {
363                kind,
364                result_ty: Some(result_ty),
365                metadata: metadata.clone(),
366            });
367            let value = func.alloc_value(Value::Inst(new_inst));
368            func.blocks[block].instructions.push(new_inst);
369            incoming.push((block, value));
370            inst_results.insert(new_inst, value);
371            inst_blocks.insert(new_inst, block);
372            inserted_insts.insert(new_inst);
373            self.stats.expressions_inserted += 1;
374        }
375        incoming.sort_by_key(|(block, _)| block.index());
376
377        // A fully-available expression whose predecessors all reuse the same
378        // value needs no phi: that value's def dominates every predecessor and
379        // therefore the join itself.
380        let replacement = match incoming.first() {
381            Some(&(_, first))
382                if fully_available
383                    && first != result
384                    && incoming.iter().all(|&(_, value)| value == first) =>
385            {
386                first
387            }
388            _ => {
389                let phi_inst =
390                    func.alloc_inst(Instruction::new(InstKind::Phi(incoming), Some(result_ty)));
391                let phi_value = func.alloc_value(Value::Inst(phi_inst));
392                let phi_count = func.blocks[target]
393                    .instructions
394                    .iter()
395                    .take_while(|&&inst_id| {
396                        matches!(func.instructions[inst_id].kind, InstKind::Phi(_))
397                    })
398                    .count();
399                func.blocks[target].instructions.insert(phi_count, phi_inst);
400                inst_results.insert(phi_inst, phi_value);
401                inst_blocks.insert(phi_inst, target);
402                phi_value
403            }
404        };
405
406        let replacements = FxHashMap::from_iter([(result, replacement)]);
407        func.replace_uses(&replacements);
408        func.blocks[target].instructions.retain(|&inst_id| inst_id != inst);
409        inst_results.remove(&inst);
410        inst_blocks.remove(&inst);
411        self.stats.expressions_eliminated += 1;
412    }
413
414    fn translate_kind_for_predecessor(
415        func: &Function,
416        kind: &InstKind,
417        target: BlockId,
418        pred: BlockId,
419        inst_blocks: &FxHashMap<InstId, BlockId>,
420    ) -> Option<InstKind> {
421        let mut translated = kind.clone();
422        let mut ok = true;
423        translated.visit_operands_mut(|value| {
424            if let Some(translated) =
425                Self::translate_value_for_predecessor(func, *value, target, pred, inst_blocks)
426            {
427                *value = translated;
428            } else {
429                ok = false;
430            }
431        });
432        ok.then_some(translated)
433    }
434
435    fn translate_value_for_predecessor(
436        func: &Function,
437        value: ValueId,
438        target: BlockId,
439        pred: BlockId,
440        inst_blocks: &FxHashMap<InstId, BlockId>,
441    ) -> Option<ValueId> {
442        match func.value(value) {
443            Value::Inst(inst_id)
444                if inst_blocks.get(inst_id).copied() == Some(target)
445                    && matches!(func.instructions[*inst_id].kind, InstKind::Phi(_)) =>
446            {
447                let InstKind::Phi(incoming) = &func.instructions[*inst_id].kind else {
448                    return None;
449                };
450                incoming.iter().find_map(|(incoming_pred, incoming_value)| {
451                    (*incoming_pred == pred).then_some(*incoming_value)
452                })
453            }
454            _ => Some(value),
455        }
456    }
457
458    fn operands_available_at_end(
459        func: &Function,
460        kind: &InstKind,
461        block: BlockId,
462        inst_blocks: &FxHashMap<InstId, BlockId>,
463        dominators: &DominatorTree,
464    ) -> bool {
465        kind.operands()
466            .into_iter()
467            .all(|value| Self::value_available_at_end(func, value, block, inst_blocks, dominators))
468    }
469
470    fn value_available_at_end(
471        func: &Function,
472        value: ValueId,
473        block: BlockId,
474        inst_blocks: &FxHashMap<InstId, BlockId>,
475        dominators: &DominatorTree,
476    ) -> bool {
477        match func.value(value) {
478            Value::Immediate(_) | Value::Arg { .. } | Value::Undef(_) | Value::Error(_) => true,
479            Value::Inst(inst) => inst_blocks
480                .get(inst)
481                .is_some_and(|def_block| dominators.dominates(*def_block, block)),
482        }
483    }
484
485    /// Finds the translated expression in `block` or any of its dominators; a
486    /// def in a dominator is available at `block`'s end with no further
487    /// checks.
488    fn available_value_at_end(
489        func: &Function,
490        dominators: &DominatorTree,
491        block: BlockId,
492        key: &ExprKey,
493        inst_results: &FxHashMap<InstId, ValueId>,
494    ) -> Option<ValueId> {
495        dominators
496            .self_and_dominators(block)
497            .into_iter()
498            .find_map(|block| Self::available_value_in_block(func, block, key, inst_results))
499    }
500
501    fn available_value_in_block(
502        func: &Function,
503        block: BlockId,
504        key: &ExprKey,
505        inst_results: &FxHashMap<InstId, ValueId>,
506    ) -> Option<ValueId> {
507        func.blocks[block].instructions.iter().rev().find_map(|&inst| {
508            let instruction = &func.instructions[inst];
509            if !Self::is_pre_expression(&instruction.kind) {
510                return None;
511            }
512            let candidate_key = Self::make_expr_key(func, &instruction.kind)?;
513            (candidate_key == *key).then(|| inst_results.get(&inst).copied()).flatten()
514        })
515    }
516
517    fn is_pre_expression(kind: &InstKind) -> bool {
518        matches!(
519            kind,
520            InstKind::Add(_, _)
521                | InstKind::Sub(_, _)
522                | InstKind::Mul(_, _)
523                | InstKind::Div(_, _)
524                | InstKind::SDiv(_, _)
525                | InstKind::Mod(_, _)
526                | InstKind::SMod(_, _)
527                | InstKind::Exp(_, _)
528                | InstKind::AddMod(_, _, _)
529                | InstKind::MulMod(_, _, _)
530                | InstKind::And(_, _)
531                | InstKind::Or(_, _)
532                | InstKind::Xor(_, _)
533                | InstKind::Not(_)
534                | InstKind::Shl(_, _)
535                | InstKind::Shr(_, _)
536                | InstKind::Sar(_, _)
537                | InstKind::Byte(_, _)
538                | InstKind::Lt(_, _)
539                | InstKind::Gt(_, _)
540                | InstKind::SLt(_, _)
541                | InstKind::SGt(_, _)
542                | InstKind::Eq(_, _)
543                | InstKind::IsZero(_)
544                | InstKind::Select(_, _, _)
545                | InstKind::SignExtend(_, _)
546        )
547    }
548
549    fn make_expr_key(func: &Function, kind: &InstKind) -> Option<ExprKey> {
550        let operand = |value| Self::operand_key(func, value);
551        match kind {
552            InstKind::Add(a, b) => {
553                let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
554                Some(ExprKey::Add(a, b))
555            }
556            InstKind::Mul(a, b) => {
557                let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
558                Some(ExprKey::Mul(a, b))
559            }
560            InstKind::And(a, b) => {
561                let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
562                Some(ExprKey::And(a, b))
563            }
564            InstKind::Or(a, b) => {
565                let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
566                Some(ExprKey::Or(a, b))
567            }
568            InstKind::Xor(a, b) => {
569                let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
570                Some(ExprKey::Xor(a, b))
571            }
572            InstKind::Eq(a, b) => {
573                let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
574                Some(ExprKey::Eq(a, b))
575            }
576            InstKind::AddMod(a, b, n) => {
577                let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
578                Some(ExprKey::AddMod(a, b, operand(*n)))
579            }
580            InstKind::MulMod(a, b, n) => {
581                let (a, b) = Self::ordered_pair(operand(*a), operand(*b));
582                Some(ExprKey::MulMod(a, b, operand(*n)))
583            }
584            InstKind::Sub(a, b) => Some(ExprKey::Sub(operand(*a), operand(*b))),
585            InstKind::Div(a, b) => Some(ExprKey::Div(operand(*a), operand(*b))),
586            InstKind::SDiv(a, b) => Some(ExprKey::SDiv(operand(*a), operand(*b))),
587            InstKind::Mod(a, b) => Some(ExprKey::Mod(operand(*a), operand(*b))),
588            InstKind::SMod(a, b) => Some(ExprKey::SMod(operand(*a), operand(*b))),
589            InstKind::Exp(a, b) => Some(ExprKey::Exp(operand(*a), operand(*b))),
590            InstKind::Not(a) => Some(ExprKey::Not(operand(*a))),
591            InstKind::Shl(a, b) => Some(ExprKey::Shl(operand(*a), operand(*b))),
592            InstKind::Shr(a, b) => Some(ExprKey::Shr(operand(*a), operand(*b))),
593            InstKind::Sar(a, b) => Some(ExprKey::Sar(operand(*a), operand(*b))),
594            InstKind::Byte(a, b) => Some(ExprKey::Byte(operand(*a), operand(*b))),
595            InstKind::Lt(a, b) => Some(ExprKey::Lt(operand(*a), operand(*b))),
596            InstKind::Gt(a, b) => Some(ExprKey::Gt(operand(*a), operand(*b))),
597            InstKind::SLt(a, b) => Some(ExprKey::SLt(operand(*a), operand(*b))),
598            InstKind::SGt(a, b) => Some(ExprKey::SGt(operand(*a), operand(*b))),
599            InstKind::IsZero(a) => Some(ExprKey::IsZero(operand(*a))),
600            InstKind::Select(a, b, c) => {
601                Some(ExprKey::Select(operand(*a), operand(*b), operand(*c)))
602            }
603            InstKind::SignExtend(a, b) => Some(ExprKey::SignExtend(operand(*a), operand(*b))),
604            _ => None,
605        }
606    }
607
608    fn operand_key(func: &Function, value: ValueId) -> OperandKey {
609        match func.value(value) {
610            Value::Immediate(imm) => OperandKey::Immediate(imm.clone()),
611            _ => OperandKey::Value(value),
612        }
613    }
614
615    fn ordered_pair(a: OperandKey, b: OperandKey) -> (OperandKey, OperandKey) {
616        if Self::compare_operand_key(&a, &b) == Ordering::Greater { (b, a) } else { (a, b) }
617    }
618
619    fn compare_operand_key(a: &OperandKey, b: &OperandKey) -> Ordering {
620        match (a, b) {
621            (OperandKey::Value(a), OperandKey::Value(b)) => a.index().cmp(&b.index()),
622            (OperandKey::Value(_), OperandKey::Immediate(_)) => Ordering::Less,
623            (OperandKey::Immediate(_), OperandKey::Value(_)) => Ordering::Greater,
624            (OperandKey::Immediate(a), OperandKey::Immediate(b)) => Self::compare_immediate(a, b),
625        }
626    }
627
628    fn compare_immediate(a: &Immediate, b: &Immediate) -> Ordering {
629        let rank = |imm: &Immediate| match imm {
630            Immediate::Bool(_) => 0,
631            Immediate::UInt(_, _) => 1,
632            Immediate::Int(_, _) => 2,
633            Immediate::Address(_) => 3,
634            Immediate::FixedBytes(_, _) => 4,
635        };
636        rank(a).cmp(&rank(b)).then_with(|| match (a, b) {
637            (Immediate::Bool(a), Immediate::Bool(b)) => a.cmp(b),
638            (Immediate::UInt(a_value, a_bits), Immediate::UInt(b_value, b_bits))
639            | (Immediate::Int(a_value, a_bits), Immediate::Int(b_value, b_bits)) => {
640                a_bits.cmp(b_bits).then_with(|| a_value.cmp(b_value))
641            }
642            (Immediate::Address(a), Immediate::Address(b)) => a.cmp(b),
643            (Immediate::FixedBytes(a_value, a_len), Immediate::FixedBytes(b_value, b_len)) => {
644                a_len.cmp(b_len).then_with(|| a_value.cmp(b_value))
645            }
646            _ => Ordering::Equal,
647        })
648    }
649}