Skip to main content

pliron_llvm/
interface_impls.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) The pliron contributors
3
4//! Implementation of various op interfaces for LLVM IR instructions.
5
6use alloc::{boxed::Box, vec, vec::Vec};
7use core::{cmp::Ordering, num::NonZero};
8use thiserror::Error;
9
10use pliron::{
11    arg_err,
12    attribute::{AttrObj, Attribute, attr_cast},
13    basic_block::BasicBlock,
14    builtin::{
15        attr_interfaces::{FloatAttr, TypedAttrInterface},
16        attributes::{FPBFloatAttr, FPDoubleAttr, FPHalfAttr, FPSingleAttr, IntegerAttr},
17        op_interfaces::{BranchOpInterface, OneResultInterface},
18        types::{BF16Type, FP16Type, FP32Type, FP64Type, IntegerType, Signedness},
19    },
20    context::{Context, Ptr},
21    derive::op_interface_impl,
22    irbuild::{IRStatus, inserter::Inserter, rewriter::Rewriter},
23    op::Op,
24    operation::Operation,
25    opts::{
26        constants::{BranchOpFoldInterface, ConstFoldInterface},
27        dce::{BlockArgRemoval, SideEffects},
28        mem2reg::{
29            AllocInfo, PromotableAllocationInterface, PromotableOpInterface, PromotableOpKind,
30        },
31    },
32    result::Result,
33    r#type::{TypeHandle, Typed, TypedHandle},
34    utils::{
35        apfloat::{BFloat, Double, Float, FloatConvert, Half, Round, Single, Status},
36        apint::{APInt, bw},
37    },
38    value::Value,
39};
40
41use crate::{
42    attributes::{
43        AggregateAttr, FCmpPredicateAttr, FastmathFlags, FastmathFlagsAttr, ICmpPredicateAttr,
44        IntegerOverflowFlagsAttr, PoisonAttr, SplatAttr, UndefAttr,
45    },
46    op_interfaces::{FastMathFlags, IntBinArithOpWithOverflowFlag, NNegFlag, PointerTypeResult},
47    ops::{
48        AShrOp, AddOp, AddressOfOp, AllocaOp, AndOp, BitcastOp, BrOp, CondBrOp, ConstantOp,
49        ExtractElementOp, ExtractValueOp, FAddOp, FCmpOp, FDivOp, FMulOp, FNegOp, FPExtOp,
50        FPToSIOp, FPToUIOp, FPTruncOp, FRemOp, FSubOp, FreezeOp, FuncOp, GetElementPtrOp, ICmpOp,
51        InsertElementOp, InsertValueOp, IntToPtrOp, LShrOp, LoadOp, MulOp, OrOp, PoisonOp,
52        PtrToIntOp, SDivOp, SExtOp, SIToFPOp, SRemOp, SelectOp, ShlOp, ShuffleVectorOp, StoreOp,
53        SubOp, SwitchOp, TruncOp, UDivOp, UIToFPOp, URemOp, UndefOp, UnreachableOp, XorOp, ZExtOp,
54        ZeroOp,
55    },
56    types::VectorType,
57};
58
59#[derive(Error, Debug)]
60#[error("Register Promotion: Allocation info provided is not related to this operation")]
61pub struct UnrelatedAllocInfo;
62
63#[op_interface_impl]
64impl PromotableAllocationInterface for AllocaOp {
65    fn alloc_info(&self, ctx: &Context) -> Vec<AllocInfo> {
66        vec![AllocInfo {
67            ptr: self.get_result(ctx),
68            ty: self.result_pointee_type(ctx),
69        }]
70    }
71
72    fn default_value(
73        &self,
74        ctx: &mut Context,
75        inserter: &mut dyn Inserter,
76        alloc_info: &AllocInfo,
77    ) -> Result<Value> {
78        if alloc_info.ptr != self.get_result(ctx) {
79            return arg_err!(self.loc(ctx), UnrelatedAllocInfo);
80        }
81        let poison = PoisonOp::new(ctx, alloc_info.ty);
82        let poison_val = poison.get_result(ctx);
83        inserter.insert_op(ctx, &poison);
84        Ok(poison_val)
85    }
86
87    fn promote(
88        &self,
89        ctx: &mut Context,
90        rewriter: &mut dyn Rewriter,
91        alloc_infos: &[AllocInfo],
92    ) -> Result<()> {
93        if alloc_infos.len() != 1 || alloc_infos[0].ptr != self.get_result(ctx) {
94            return arg_err!(self.loc(ctx), UnrelatedAllocInfo);
95        }
96        rewriter.erase_operation(ctx, self.get_operation());
97        Ok(())
98    }
99}
100
101#[op_interface_impl]
102impl PromotableOpInterface for StoreOp {
103    fn promotion_kind(&self, ctx: &Context, alloc_info: &AllocInfo) -> PromotableOpKind {
104        if self.get_operand_address(ctx) == alloc_info.ptr {
105            PromotableOpKind::Store(self.get_operand_value(ctx))
106        } else {
107            PromotableOpKind::NonPromotableUse
108        }
109    }
110
111    fn promote(
112        &self,
113        ctx: &mut Context,
114        alloc_info_reaching_defs: &[(AllocInfo, Value)],
115        rewriter: &mut dyn Rewriter,
116    ) -> Result<()> {
117        if alloc_info_reaching_defs.len() != 1 {
118            return arg_err!(self.loc(ctx), UnrelatedAllocInfo);
119        }
120        let (alloc_info, _reaching_def) = &alloc_info_reaching_defs[0];
121        if self.get_operand_address(ctx) != alloc_info.ptr {
122            return arg_err!(self.loc(ctx), UnrelatedAllocInfo);
123        }
124        rewriter.erase_operation(ctx, self.get_operation());
125        Ok(())
126    }
127}
128
129#[op_interface_impl]
130impl PromotableOpInterface for LoadOp {
131    fn promotion_kind(&self, ctx: &Context, alloc_info: &AllocInfo) -> PromotableOpKind {
132        if self.get_operand_address(ctx) == alloc_info.ptr {
133            PromotableOpKind::Load
134        } else {
135            PromotableOpKind::NonPromotableUse
136        }
137    }
138
139    fn promote(
140        &self,
141        ctx: &mut Context,
142        alloc_info_reaching_defs: &[(AllocInfo, Value)],
143        rewriter: &mut dyn Rewriter,
144    ) -> Result<()> {
145        if alloc_info_reaching_defs.len() != 1 {
146            return arg_err!(self.loc(ctx), UnrelatedAllocInfo);
147        }
148        let (alloc_info, reaching_def) = &alloc_info_reaching_defs[0];
149        if self.get_operand_address(ctx) != alloc_info.ptr {
150            return arg_err!(self.loc(ctx), UnrelatedAllocInfo);
151        }
152        rewriter.replace_operation_with_values(ctx, self.get_operation(), vec![*reaching_def]);
153        Ok(())
154    }
155}
156
157// Implement [SideEffects] with `has_side_effects` returning `false`
158macro_rules! impl_side_effects_false {
159  ($($op:ty),+ $(,)?) => {
160    $(
161      #[op_interface_impl]
162      impl SideEffects for $op {
163        fn has_side_effects(&self, _ctx: &Context) -> bool {
164          false
165        }
166      }
167    )+
168  };
169}
170
171// Pure value-producing ops with no memory/control side effects.
172// We don't need to implement [SideEffects] for the other ops,
173// because the assumption is that the absense of the interface
174// implies the presence of side effects, which is a safe default for DCE.
175impl_side_effects_false!(
176    AddOp,
177    SubOp,
178    MulOp,
179    ShlOp,
180    UDivOp,
181    SDivOp,
182    URemOp,
183    SRemOp,
184    AndOp,
185    OrOp,
186    XorOp,
187    LShrOp,
188    AShrOp,
189    ICmpOp,
190    AllocaOp,
191    BitcastOp,
192    IntToPtrOp,
193    PtrToIntOp,
194    ConstantOp,
195    UndefOp,
196    PoisonOp,
197    FreezeOp,
198    ZeroOp,
199    AddressOfOp,
200    SExtOp,
201    ZExtOp,
202    FPExtOp,
203    TruncOp,
204    FPTruncOp,
205    FPToSIOp,
206    FPToUIOp,
207    SIToFPOp,
208    UIToFPOp,
209    InsertValueOp,
210    ExtractValueOp,
211    InsertElementOp,
212    ExtractElementOp,
213    ShuffleVectorOp,
214    SelectOp,
215    FNegOp,
216    FAddOp,
217    FSubOp,
218    FMulOp,
219    FDivOp,
220    FRemOp,
221    FCmpOp,
222    GetElementPtrOp,
223);
224
225#[op_interface_impl]
226impl BlockArgRemoval for FuncOp {
227    fn can_remove_block_args(&self, ctx: &Context, block: Ptr<BasicBlock>) -> bool {
228        !matches!(self.get_entry_block(ctx), Some(entry) if entry == block)
229    }
230}
231
232/// Checks for and return two constant integer operands.
233fn get_int_bin_operands(operand_attrs: &[Option<AttrObj>]) -> Option<(IntegerAttr, IntegerAttr)> {
234    let [Some(lhs), Some(rhs)] = operand_attrs else {
235        assert!(operand_attrs.len() == 2);
236        return None;
237    };
238    let lhs_int = lhs.downcast_ref::<IntegerAttr>()?;
239    let rhs_int = rhs.downcast_ref::<IntegerAttr>()?;
240    Some((lhs_int.clone(), rhs_int.clone()))
241}
242
243/// Create an `i1` attribute.
244fn bool_attr(ctx: &Context, value: bool) -> AttrObj {
245    let bool_ty = IntegerType::get(ctx, 1, Signedness::Signless);
246    Box::new(IntegerAttr::new(
247        bool_ty,
248        APInt::from_u8(value as u8, bw(1)),
249    )) as AttrObj
250}
251
252#[op_interface_impl]
253impl ConstFoldInterface for ConstantOp {
254    fn check_fold(
255        &self,
256        ctx: &Context,
257        _operand_attrs: &[Option<AttrObj>],
258    ) -> Vec<Option<AttrObj>> {
259        vec![Some(
260            pliron::dyn_clone::clone_box(&*self.get_value(ctx)) as AttrObj
261        )]
262    }
263
264    fn fold_in_place(
265        &self,
266        _ctx: &mut Context,
267        _operand_attrs: &[Option<AttrObj>],
268        _rewriter: &mut dyn Rewriter,
269    ) -> IRStatus {
270        IRStatus::Unchanged
271    }
272}
273
274/// Is `attr` an `undef` or a `poison` value?
275fn is_undef_or_poison_attr(attr: &dyn Attribute) -> bool {
276    attr.downcast_ref::<UndefAttr>().is_some() || attr.downcast_ref::<PoisonAttr>().is_some()
277}
278
279/// Constant fold an operation whose result LLVM defines to be `poison`
280/// whenever any operand is `poison`.
281///
282/// `fold` is called with each `poison` operand set to `None`.
283/// Each result of `fold` becomes:
284///   - `Some(c)`: `Some(c)`. The constant is independent of `poison` operands.
285///   - `None`: `Some(PoisonAttr)` if any operand is `poison`, else `None`.
286fn check_fold_poison_propagating_op(
287    op: Ptr<Operation>,
288    ctx: &Context,
289    ops: &[Option<AttrObj>],
290    fold: impl FnOnce(&[Option<AttrObj>]) -> Vec<Option<AttrObj>>,
291) -> Vec<Option<AttrObj>> {
292    let is_poison = |attr: &Option<AttrObj>| {
293        attr.as_ref()
294            .is_some_and(|attr| attr.downcast_ref::<PoisonAttr>().is_some())
295    };
296    if !ops.iter().any(is_poison) {
297        return fold(ops);
298    }
299    let non_poison_ops: Vec<_> = ops
300        .iter()
301        .map(|attr| if is_poison(attr) { None } else { attr.clone() })
302        .collect();
303    let result_types: Vec<_> = op
304        .deref(ctx)
305        .results()
306        .map(|res| res.get_type(ctx))
307        .collect();
308    fold(&non_poison_ops)
309        .into_iter()
310        .zip(result_types)
311        .map(|(folded, ty)| folded.or_else(|| Some(Box::new(PoisonAttr(ty)) as AttrObj)))
312        .collect()
313}
314
315#[op_interface_impl]
316impl ConstFoldInterface for PoisonOp {
317    fn check_fold(&self, ctx: &Context, _ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
318        let ty = self.get_result(ctx).get_type(ctx);
319        vec![Some(Box::new(PoisonAttr(ty)) as AttrObj)]
320    }
321
322    fn fold_in_place(
323        &self,
324        _ctx: &mut Context,
325        _ops: &[Option<AttrObj>],
326        _rewriter: &mut dyn Rewriter,
327    ) -> IRStatus {
328        IRStatus::Unchanged
329    }
330}
331
332#[op_interface_impl]
333impl ConstFoldInterface for UndefOp {
334    fn check_fold(&self, ctx: &Context, _ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
335        let ty = self.get_result(ctx).get_type(ctx);
336        vec![Some(Box::new(UndefAttr(ty)) as AttrObj)]
337    }
338
339    fn fold_in_place(
340        &self,
341        _ctx: &mut Context,
342        _ops: &[Option<AttrObj>],
343        _rewriter: &mut dyn Rewriter,
344    ) -> IRStatus {
345        IRStatus::Unchanged
346    }
347}
348
349/// Constant fold this binary integer operation, taking integer overflow flags
350/// into account.
351///
352/// `operand_attrs` contains `Some(attr)` for operands inferred constant
353/// and `None` for operands not inferred constant.
354///
355/// `flags` contains the llvm integer overflow flags associated with this operation
356///
357/// `combine` computes the wrapped result together with whether the operation
358/// unsigned- and signed-overflowed (the two booleans, in that order). The
359///
360/// Returns a singleton vector containing the folded result, or `None` if folding
361/// is not possible.
362fn check_fold_int_bin_op_with_overflow(
363    operand_attrs: &[Option<AttrObj>],
364    flags: IntegerOverflowFlagsAttr,
365    combine: impl Fn(&APInt, &APInt) -> (APInt, bool, bool),
366) -> Vec<Option<AttrObj>> {
367    let Some((lhs, rhs)) = get_int_bin_operands(operand_attrs) else {
368        return vec![None];
369    };
370    let (res, unsigned_overflow, signed_overflow) = combine(&lhs.value(), &rhs.value());
371    if (flags.nsw && signed_overflow) || (flags.nuw && unsigned_overflow) {
372        return vec![None];
373    }
374    let res = Box::new(IntegerAttr::new(lhs.get_type(), res)) as AttrObj;
375    vec![Some(res)]
376}
377
378#[op_interface_impl]
379impl ConstFoldInterface for AddOp {
380    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
381        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
382            check_fold_int_bin_op_with_overflow(
383                ops,
384                self.integer_overflow_flag(ctx),
385                APInt::add_overflow,
386            )
387        })
388    }
389    fn fold_in_place(
390        &self,
391        ctx: &mut Context,
392        ops: &[Option<AttrObj>],
393        rw: &mut dyn Rewriter,
394    ) -> IRStatus {
395        self.fold_with_materialization(ctx, ops, rw)
396    }
397}
398
399#[op_interface_impl]
400impl ConstFoldInterface for SubOp {
401    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
402        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
403            check_fold_int_bin_op_with_overflow(
404                ops,
405                self.integer_overflow_flag(ctx),
406                APInt::sub_overflow,
407            )
408        })
409    }
410    fn fold_in_place(
411        &self,
412        ctx: &mut Context,
413        ops: &[Option<AttrObj>],
414        rw: &mut dyn Rewriter,
415    ) -> IRStatus {
416        self.fold_with_materialization(ctx, ops, rw)
417    }
418}
419
420#[op_interface_impl]
421impl ConstFoldInterface for MulOp {
422    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
423        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
424            check_fold_int_bin_op_with_overflow(
425                ops,
426                self.integer_overflow_flag(ctx),
427                APInt::mul_overflow,
428            )
429        })
430    }
431    fn fold_in_place(
432        &self,
433        ctx: &mut Context,
434        ops: &[Option<AttrObj>],
435        rw: &mut dyn Rewriter,
436    ) -> IRStatus {
437        self.fold_with_materialization(ctx, ops, rw)
438    }
439}
440
441#[op_interface_impl]
442impl ConstFoldInterface for ShlOp {
443    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
444        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
445            match get_int_bin_operands(ops) {
446                Some((lhs, rhs)) => {
447                    let shamt = rhs.value();
448                    let lhs_bw: usize = lhs.value().bw();
449                    let lhs_bw: APInt = APInt::from_usize(lhs_bw, NonZero::new(lhs_bw).unwrap());
450                    if shamt.ult(&lhs_bw) {
451                        check_fold_int_bin_op_with_overflow(
452                            ops,
453                            self.integer_overflow_flag(ctx),
454                            APInt::shl_overflow,
455                        )
456                    } else {
457                        vec![None]
458                    }
459                }
460                None => vec![None],
461            }
462        })
463    }
464    fn fold_in_place(
465        &self,
466        ctx: &mut Context,
467        ops: &[Option<AttrObj>],
468        rw: &mut dyn Rewriter,
469    ) -> IRStatus {
470        self.fold_with_materialization(ctx, ops, rw)
471    }
472}
473
474/// Constant fold a binary integer operation.
475fn check_fold_int_bin_op(
476    operand_attrs: &[Option<AttrObj>],
477    combine: impl Fn(&APInt, &APInt) -> APInt,
478) -> Vec<Option<AttrObj>> {
479    let Some((lhs, rhs)) = get_int_bin_operands(operand_attrs) else {
480        return vec![None];
481    };
482    let res = Box::new(IntegerAttr::new(
483        lhs.get_type(),
484        combine(&lhs.value(), &rhs.value()),
485    )) as AttrObj;
486    vec![Some(res)]
487}
488
489#[op_interface_impl]
490impl ConstFoldInterface for UDivOp {
491    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
492        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
493            match get_int_bin_operands(ops) {
494                Some((_, rhs)) if rhs.value().is_zero() => vec![None],
495                _ => check_fold_int_bin_op(ops, APInt::udiv),
496            }
497        })
498    }
499    fn fold_in_place(
500        &self,
501        ctx: &mut Context,
502        ops: &[Option<AttrObj>],
503        rw: &mut dyn Rewriter,
504    ) -> IRStatus {
505        self.fold_with_materialization(ctx, ops, rw)
506    }
507}
508
509/// Is signed-dividing/remaindering `lhs` by `rhs` undefined behavior in LLVM?
510fn is_signed_div_ub(lhs: &APInt, rhs: &APInt) -> bool {
511    let bw = NonZero::new(rhs.bw()).expect("operand has zero bitwidth");
512    // `-1` is the all-ones bit pattern, i.e. the unsigned max.
513    rhs.is_zero() || (*lhs == APInt::imin(bw) && *rhs == APInt::umax(bw))
514}
515
516#[op_interface_impl]
517impl ConstFoldInterface for SDivOp {
518    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
519        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
520            match get_int_bin_operands(ops) {
521                Some((lhs, rhs)) if is_signed_div_ub(&lhs.value(), &rhs.value()) => vec![None],
522                _ => check_fold_int_bin_op(ops, APInt::sdiv),
523            }
524        })
525    }
526    fn fold_in_place(
527        &self,
528        ctx: &mut Context,
529        ops: &[Option<AttrObj>],
530        rw: &mut dyn Rewriter,
531    ) -> IRStatus {
532        self.fold_with_materialization(ctx, ops, rw)
533    }
534}
535
536#[op_interface_impl]
537impl ConstFoldInterface for URemOp {
538    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
539        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
540            match get_int_bin_operands(ops) {
541                Some((_, rhs)) if rhs.value().is_zero() => vec![None],
542                _ => check_fold_int_bin_op(ops, APInt::urem),
543            }
544        })
545    }
546    fn fold_in_place(
547        &self,
548        ctx: &mut Context,
549        ops: &[Option<AttrObj>],
550        rw: &mut dyn Rewriter,
551    ) -> IRStatus {
552        self.fold_with_materialization(ctx, ops, rw)
553    }
554}
555
556#[op_interface_impl]
557impl ConstFoldInterface for SRemOp {
558    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
559        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
560            match get_int_bin_operands(ops) {
561                Some((lhs, rhs)) if is_signed_div_ub(&lhs.value(), &rhs.value()) => vec![None],
562                _ => check_fold_int_bin_op(ops, APInt::srem),
563            }
564        })
565    }
566    fn fold_in_place(
567        &self,
568        ctx: &mut Context,
569        ops: &[Option<AttrObj>],
570        rw: &mut dyn Rewriter,
571    ) -> IRStatus {
572        self.fold_with_materialization(ctx, ops, rw)
573    }
574}
575
576#[op_interface_impl]
577impl ConstFoldInterface for AndOp {
578    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
579        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
580            assert!(ops.len() == 2);
581            for op in ops.iter().flatten() {
582                let Some(int) = op.downcast_ref::<IntegerAttr>() else {
583                    return vec![None];
584                };
585                if int.value().is_zero() {
586                    let zero = APInt::zero(NonZero::new(int.value().bw()).expect("zero bitwidth"));
587                    let res = Box::new(IntegerAttr::new(int.get_type(), zero)) as AttrObj;
588                    return vec![Some(res)];
589                }
590            }
591            check_fold_int_bin_op(ops, APInt::and)
592        })
593    }
594
595    fn fold_in_place(
596        &self,
597        ctx: &mut Context,
598        ops: &[Option<AttrObj>],
599        rw: &mut dyn Rewriter,
600    ) -> IRStatus {
601        self.fold_with_materialization(ctx, ops, rw)
602    }
603}
604
605#[op_interface_impl]
606impl ConstFoldInterface for OrOp {
607    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
608        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
609            assert!(ops.len() == 2);
610            for op in ops.iter().flatten() {
611                let Some(int) = op.downcast_ref::<IntegerAttr>() else {
612                    return vec![None];
613                };
614                let bw = NonZero::new(int.value().bw()).expect("zero bitwidth");
615                if int.value() == APInt::umax(bw) {
616                    let all_ones = APInt::umax(bw);
617                    let res = Box::new(IntegerAttr::new(int.get_type(), all_ones)) as AttrObj;
618                    return vec![Some(res)];
619                }
620            }
621            check_fold_int_bin_op(ops, APInt::or)
622        })
623    }
624
625    fn fold_in_place(
626        &self,
627        ctx: &mut Context,
628        ops: &[Option<AttrObj>],
629        rw: &mut dyn Rewriter,
630    ) -> IRStatus {
631        self.fold_with_materialization(ctx, ops, rw)
632    }
633}
634
635#[op_interface_impl]
636impl ConstFoldInterface for XorOp {
637    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
638        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
639            check_fold_int_bin_op(ops, APInt::xor)
640        })
641    }
642
643    fn fold_in_place(
644        &self,
645        ctx: &mut Context,
646        ops: &[Option<AttrObj>],
647        rw: &mut dyn Rewriter,
648    ) -> IRStatus {
649        self.fold_with_materialization(ctx, ops, rw)
650    }
651}
652
653#[op_interface_impl]
654impl ConstFoldInterface for LShrOp {
655    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
656        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
657            // A shift amount >= the bitwidth is undefined behavior in LLVM, so it
658            // must not be folded.
659            match get_int_bin_operands(ops) {
660                Some((lhs, rhs)) => {
661                    let lhs_bw = lhs.value().bw();
662                    let lhs_bw = APInt::from_usize(lhs_bw, NonZero::new(lhs_bw).unwrap());
663                    if rhs.value().ult(&lhs_bw) {
664                        check_fold_int_bin_op(ops, APInt::lshr)
665                    } else {
666                        vec![None]
667                    }
668                }
669                None => vec![None],
670            }
671        })
672    }
673    fn fold_in_place(
674        &self,
675        ctx: &mut Context,
676        ops: &[Option<AttrObj>],
677        rw: &mut dyn Rewriter,
678    ) -> IRStatus {
679        self.fold_with_materialization(ctx, ops, rw)
680    }
681}
682
683#[op_interface_impl]
684impl ConstFoldInterface for AShrOp {
685    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
686        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
687            // A shift amount >= the bitwidth is undefined behavior in LLVM, so it
688            // must not be folded.
689            match get_int_bin_operands(ops) {
690                Some((lhs, rhs)) => {
691                    let lhs_bw = lhs.value().bw();
692                    let lhs_bw = APInt::from_usize(lhs_bw, NonZero::new(lhs_bw).unwrap());
693                    if rhs.value().ult(&lhs_bw) {
694                        check_fold_int_bin_op(ops, APInt::ashr)
695                    } else {
696                        vec![None]
697                    }
698                }
699                None => vec![None],
700            }
701        })
702    }
703    fn fold_in_place(
704        &self,
705        ctx: &mut Context,
706        ops: &[Option<AttrObj>],
707        rw: &mut dyn Rewriter,
708    ) -> IRStatus {
709        self.fold_with_materialization(ctx, ops, rw)
710    }
711}
712
713/// Evaluate an integer comparison `lhs <pred> rhs`.
714fn eval_icmp(pred: &ICmpPredicateAttr, lhs: &APInt, rhs: &APInt) -> bool {
715    assert!(lhs.bw() == rhs.bw());
716    match pred {
717        ICmpPredicateAttr::EQ => lhs == rhs,
718        ICmpPredicateAttr::NE => lhs != rhs,
719        ICmpPredicateAttr::SLT => lhs.slt(rhs),
720        ICmpPredicateAttr::SLE => lhs.sle(rhs),
721        ICmpPredicateAttr::SGT => lhs.sgt(rhs),
722        ICmpPredicateAttr::SGE => lhs.sge(rhs),
723        ICmpPredicateAttr::ULT => lhs.ult(rhs),
724        ICmpPredicateAttr::ULE => lhs.ule(rhs),
725        ICmpPredicateAttr::UGT => lhs.ugt(rhs),
726        ICmpPredicateAttr::UGE => lhs.uge(rhs),
727    }
728}
729
730#[op_interface_impl]
731impl ConstFoldInterface for ICmpOp {
732    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
733        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
734            let Some((lhs, rhs)) = get_int_bin_operands(ops) else {
735                return vec![None];
736            };
737            let result = eval_icmp(&self.predicate(ctx), &lhs.value(), &rhs.value());
738            vec![Some(bool_attr(ctx, result))]
739        })
740    }
741    fn fold_in_place(
742        &self,
743        ctx: &mut Context,
744        ops: &[Option<AttrObj>],
745        rw: &mut dyn Rewriter,
746    ) -> IRStatus {
747        self.fold_with_materialization(ctx, ops, rw)
748    }
749}
750
751/// Return a fixed vector constant's type and length.
752fn fixed_vector_shape(ctx: &Context, vector: &dyn Attribute) -> Option<(TypeHandle, usize)> {
753    if let Some(aggregate) = vector.downcast_ref::<AggregateAttr>() {
754        let ty = aggregate.ty();
755        if ty.deref(ctx).downcast_ref::<VectorType>()?.is_scalable() {
756            return None;
757        }
758        return Some((ty, aggregate.elements().len()));
759    }
760
761    let splat = vector.downcast_ref::<SplatAttr>()?;
762    let ty = splat.ty();
763    let vector_ty = ty.deref(ctx);
764    if vector_ty.is_scalable() {
765        return None;
766    }
767    Some((ty.into(), vector_ty.num_elements() as usize))
768}
769
770/// Clone a fixed vector element. Panics if `index` is out of bounds.
771fn fixed_vector_element(vector: &dyn Attribute, index: usize) -> Box<dyn TypedAttrInterface> {
772    if let Some(aggregate) = vector.downcast_ref::<AggregateAttr>() {
773        return aggregate.elements()[index].clone();
774    }
775    let splat = vector
776        .downcast_ref::<SplatAttr>()
777        .expect("not a fixed-length vector constant");
778    pliron::dyn_clone::clone_box(splat.element())
779}
780
781/// Fold a shuffle of two fixed vector constants.
782fn fold_shuffle_vector(
783    ctx: &Context,
784    lhs: &dyn Attribute,
785    rhs: &dyn Attribute,
786    mask: &[i32],
787    result_ty: TypeHandle,
788) -> Option<AttrObj> {
789    let (_, lhs_len) = fixed_vector_shape(ctx, lhs)?;
790    let (_, rhs_len) = fixed_vector_shape(ctx, rhs)?;
791
792    // The mask indexes the concatenated inputs.
793    let mut elements: Vec<Box<dyn TypedAttrInterface>> = Vec::with_capacity(mask.len());
794    for &mask_index in mask {
795        // Negative entries are poison and remain unfolded.
796        let index = usize::try_from(mask_index).ok()?;
797        let element = if index < lhs_len {
798            fixed_vector_element(lhs, index)
799        } else if index - lhs_len < rhs_len {
800            fixed_vector_element(rhs, index - lhs_len)
801        } else {
802            return None;
803        };
804        elements.push(element);
805    }
806
807    Some(Box::new(AggregateAttr::new(elements, result_ty)) as AttrObj)
808}
809
810#[op_interface_impl]
811impl ConstFoldInterface for ShuffleVectorOp {
812    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
813        let [Some(lhs), Some(rhs)] = ops else {
814            assert!(ops.len() == 2);
815            return vec![None];
816        };
817        let mask = self
818            .get_attr_llvm_shuffle_vector_mask(ctx)
819            .expect("ShuffleVectorOp missing mask attribute");
820        let result_ty = self.get_result(ctx).get_type(ctx);
821        vec![fold_shuffle_vector(ctx, &**lhs, &**rhs, &mask.0, result_ty)]
822    }
823    fn fold_in_place(
824        &self,
825        ctx: &mut Context,
826        ops: &[Option<AttrObj>],
827        rw: &mut dyn Rewriter,
828    ) -> IRStatus {
829        self.fold_with_materialization(ctx, ops, rw)
830    }
831}
832
833/// Convert `index` to an in-bounds vector index.
834fn constant_vector_index(index: &IntegerAttr, length: usize) -> Option<usize> {
835    let value = index.value();
836    // Conversion supports at most 128 bits.
837    if value.bw() > 128 {
838        return None;
839    }
840    let index: usize = value.to_u128().try_into().ok()?;
841    (index < length).then_some(index)
842}
843
844#[op_interface_impl]
845impl ConstFoldInterface for ExtractElementOp {
846    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
847        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
848            let [Some(vector), Some(index)] = ops else {
849                assert!(ops.len() == 2);
850                return vec![None];
851            };
852            let Some(index) = index.downcast_ref::<IntegerAttr>() else {
853                return vec![None];
854            };
855            let folded = fixed_vector_shape(ctx, &**vector)
856                .and_then(|(_, length)| constant_vector_index(index, length))
857                .map(|index| fixed_vector_element(&**vector, index) as AttrObj);
858            vec![folded]
859        })
860    }
861    fn fold_in_place(
862        &self,
863        ctx: &mut Context,
864        ops: &[Option<AttrObj>],
865        rw: &mut dyn Rewriter,
866    ) -> IRStatus {
867        self.fold_with_materialization(ctx, ops, rw)
868    }
869}
870
871/// Return a fixed vector constant's elements and type.
872fn fixed_vector_elements(
873    ctx: &Context,
874    vector: &dyn Attribute,
875) -> Option<(Vec<Box<dyn TypedAttrInterface>>, TypeHandle)> {
876    let (ty, length) = fixed_vector_shape(ctx, vector)?;
877    let elements = (0..length)
878        .map(|index| fixed_vector_element(vector, index))
879        .collect();
880    Some((elements, ty))
881}
882
883#[op_interface_impl]
884impl ConstFoldInterface for InsertElementOp {
885    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
886        let [Some(vector), Some(element), Some(index)] = ops else {
887            assert!(ops.len() == 3);
888            return vec![None];
889        };
890        let element = attr_cast::<dyn TypedAttrInterface>(&**element)
891            .expect("invalid operand type: typecheck before optimizing");
892        let Some(index) = index.downcast_ref::<IntegerAttr>() else {
893            return vec![None];
894        };
895        let folded = fixed_vector_elements(ctx, &**vector).and_then(|(mut elements, ty)| {
896            let index = constant_vector_index(index, elements.len())?;
897            *elements.get_mut(index)? = pliron::dyn_clone::clone_box(element);
898            Some(Box::new(AggregateAttr::new(elements, ty)) as AttrObj)
899        });
900        vec![folded]
901    }
902    fn fold_in_place(
903        &self,
904        ctx: &mut Context,
905        ops: &[Option<AttrObj>],
906        rw: &mut dyn Rewriter,
907    ) -> IRStatus {
908        self.fold_with_materialization(ctx, ops, rw)
909    }
910}
911
912/// Extract an element along `indices`.
913fn extract_from_aggregate_attr(aggregate: &AggregateAttr, indices: &[u32]) -> Option<AttrObj> {
914    let (&index, rest) = indices.split_first()?;
915    let element = aggregate.elements().get(index as usize)?;
916
917    if rest.is_empty() {
918        return Some(pliron::dyn_clone::clone_box(&**element as &dyn Attribute));
919    }
920
921    let nested = (&**element as &dyn Attribute).downcast_ref::<AggregateAttr>()?;
922    extract_from_aggregate_attr(nested, rest)
923}
924
925#[op_interface_impl]
926impl ConstFoldInterface for ExtractValueOp {
927    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
928        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
929            let [Some(aggregate)] = ops else {
930                assert!(ops.len() == 1);
931                return vec![None];
932            };
933            let Some(aggregate) = aggregate.downcast_ref::<AggregateAttr>() else {
934                return vec![None];
935            };
936            vec![extract_from_aggregate_attr(aggregate, &self.indices(ctx))]
937        })
938    }
939    fn fold_in_place(
940        &self,
941        ctx: &mut Context,
942        ops: &[Option<AttrObj>],
943        rw: &mut dyn Rewriter,
944    ) -> IRStatus {
945        self.fold_with_materialization(ctx, ops, rw)
946    }
947}
948
949/// Insert `value` along `indices`.
950fn insert_into_aggregate_attr(
951    aggregate: &AggregateAttr,
952    value: &dyn TypedAttrInterface,
953    indices: &[u32],
954) -> Option<AggregateAttr> {
955    let (&index, rest) = indices.split_first()?;
956    let index = index as usize;
957
958    let replacement: Box<dyn TypedAttrInterface> = if rest.is_empty() {
959        pliron::dyn_clone::clone_box(value)
960    } else {
961        let current = aggregate.elements().get(index)?;
962        let nested = (&**current as &dyn Attribute).downcast_ref::<AggregateAttr>()?;
963        Box::new(insert_into_aggregate_attr(nested, value, rest)?)
964    };
965
966    let mut elements = aggregate.elements().to_vec();
967    *elements.get_mut(index)? = replacement;
968    Some(AggregateAttr::new(elements, aggregate.ty()))
969}
970
971#[op_interface_impl]
972impl ConstFoldInterface for InsertValueOp {
973    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
974        let [Some(aggregate), Some(value)] = ops else {
975            assert!(ops.len() == 2);
976            return vec![None];
977        };
978        let Some(aggregate) = aggregate.downcast_ref::<AggregateAttr>() else {
979            return vec![None];
980        };
981        let value = attr_cast::<dyn TypedAttrInterface>(&**value)
982            .expect("invalid operand type: typecheck before optimizing");
983        vec![
984            insert_into_aggregate_attr(aggregate, value, &self.indices(ctx))
985                .map(|result| Box::new(result) as AttrObj),
986        ]
987    }
988    fn fold_in_place(
989        &self,
990        ctx: &mut Context,
991        ops: &[Option<AttrObj>],
992        rw: &mut dyn Rewriter,
993    ) -> IRStatus {
994        self.fold_with_materialization(ctx, ops, rw)
995    }
996}
997
998/// For an integer type, return its signless integer type and its width.
999fn cast_dest_to_signless(
1000    ctx: &Context,
1001    res_ty: TypeHandle,
1002) -> Option<(TypedHandle<IntegerType>, NonZero<usize>)> {
1003    let dest_width = res_ty.deref(ctx).downcast_ref::<IntegerType>()?.width();
1004    Some((
1005        IntegerType::get(ctx, dest_width, Signedness::Signless),
1006        NonZero::new(dest_width as usize).expect("result has zero bitwidth"),
1007    ))
1008}
1009
1010/// Fold a scalar integer cast with `resize`.
1011fn check_fold_int_cast(
1012    ctx: &Context,
1013    operand_attrs: &[Option<AttrObj>],
1014    res_ty: TypeHandle,
1015    resize: impl Fn(&APInt, NonZero<usize>) -> APInt,
1016) -> Vec<Option<AttrObj>> {
1017    let [Some(operand)] = operand_attrs else {
1018        assert!(operand_attrs.len() == 1);
1019        return vec![None];
1020    };
1021    let Some(operand) = operand.downcast_ref::<IntegerAttr>() else {
1022        return vec![None];
1023    };
1024    let Some((dest_ty, dest_width)) = cast_dest_to_signless(ctx, res_ty) else {
1025        return vec![None];
1026    };
1027    vec![Some(Box::new(IntegerAttr::new(
1028        dest_ty,
1029        resize(&operand.value(), dest_width),
1030    )) as AttrObj)]
1031}
1032
1033#[op_interface_impl]
1034impl ConstFoldInterface for SExtOp {
1035    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1036        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1037            check_fold_int_cast(ctx, ops, self.result_type(ctx), APInt::sext)
1038        })
1039    }
1040    fn fold_in_place(
1041        &self,
1042        ctx: &mut Context,
1043        ops: &[Option<AttrObj>],
1044        rw: &mut dyn Rewriter,
1045    ) -> IRStatus {
1046        self.fold_with_materialization(ctx, ops, rw)
1047    }
1048}
1049
1050#[op_interface_impl]
1051impl ConstFoldInterface for ZExtOp {
1052    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1053        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1054            // `zext nneg` asserts the operand is non-negative; if it isn't, the
1055            // result is poison, so we must not fold it to a concrete value.
1056            if self.nneg(ctx)
1057                && let [Some(operand)] = ops
1058                && let Some(operand) = operand.downcast_ref::<IntegerAttr>()
1059                && operand.value().is_negative()
1060            {
1061                return vec![None];
1062            }
1063            check_fold_int_cast(ctx, ops, self.result_type(ctx), APInt::zext)
1064        })
1065    }
1066    fn fold_in_place(
1067        &self,
1068        ctx: &mut Context,
1069        ops: &[Option<AttrObj>],
1070        rw: &mut dyn Rewriter,
1071    ) -> IRStatus {
1072        self.fold_with_materialization(ctx, ops, rw)
1073    }
1074}
1075
1076#[op_interface_impl]
1077impl ConstFoldInterface for TruncOp {
1078    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1079        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1080            check_fold_int_cast(ctx, ops, self.result_type(ctx), APInt::trunc)
1081        })
1082    }
1083    fn fold_in_place(
1084        &self,
1085        ctx: &mut Context,
1086        ops: &[Option<AttrObj>],
1087        rw: &mut dyn Rewriter,
1088    ) -> IRStatus {
1089        self.fold_with_materialization(ctx, ops, rw)
1090    }
1091}
1092
1093/// Fold a scalar float-to-integer cast, rounding toward zero.
1094fn check_fold_float_to_int(
1095    ctx: &Context,
1096    operand_attrs: &[Option<AttrObj>],
1097    result_ty: TypeHandle,
1098    signed: bool,
1099) -> Vec<Option<AttrObj>> {
1100    let [Some(operand)] = operand_attrs else {
1101        assert!(operand_attrs.len() == 1);
1102        return vec![None];
1103    };
1104    let Some(value) = attr_cast::<dyn FloatAttr>(&**operand) else {
1105        return vec![None];
1106    };
1107
1108    let Some((dest_ty, dest_width)) = cast_dest_to_signless(ctx, result_ty) else {
1109        return vec![None];
1110    };
1111    let dest_width = dest_width.get();
1112    if dest_width > 128 {
1113        return vec![None];
1114    }
1115
1116    let mut is_exact = false;
1117    let converted = if signed {
1118        // Avoid rustc_apfloat's unsupported zero-bit conversion for signed i1.
1119        let conversion_width = dest_width.max(2);
1120        let converted = value.to_i128_r(conversion_width, Round::TowardZero, &mut is_exact);
1121        if converted.status.contains(Status::INVALID_OP)
1122            || (dest_width == 1 && converted.value != -1 && converted.value != 0)
1123        {
1124            return vec![None];
1125        }
1126        APInt::from_i128(converted.value, bw(dest_width))
1127    } else {
1128        let converted = value.to_u128_r(dest_width, Round::TowardZero, &mut is_exact);
1129        if converted.status.contains(Status::INVALID_OP) {
1130            return vec![None];
1131        }
1132        APInt::from_u128(converted.value, bw(dest_width))
1133    };
1134
1135    vec![Some(
1136        Box::new(IntegerAttr::new(dest_ty, converted)) as AttrObj
1137    )]
1138}
1139
1140#[op_interface_impl]
1141impl ConstFoldInterface for FPToSIOp {
1142    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1143        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1144            check_fold_float_to_int(ctx, ops, self.result_type(ctx), true)
1145        })
1146    }
1147    fn fold_in_place(
1148        &self,
1149        ctx: &mut Context,
1150        ops: &[Option<AttrObj>],
1151        rw: &mut dyn Rewriter,
1152    ) -> IRStatus {
1153        self.fold_with_materialization(ctx, ops, rw)
1154    }
1155}
1156
1157#[op_interface_impl]
1158impl ConstFoldInterface for FPToUIOp {
1159    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1160        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1161            check_fold_float_to_int(ctx, ops, self.result_type(ctx), false)
1162        })
1163    }
1164    fn fold_in_place(
1165        &self,
1166        ctx: &mut Context,
1167        ops: &[Option<AttrObj>],
1168        rw: &mut dyn Rewriter,
1169    ) -> IRStatus {
1170        self.fold_with_materialization(ctx, ops, rw)
1171    }
1172}
1173
1174/// Return zero value of a builtin float type `ty`.
1175fn zero_float_attr(ctx: &Context, ty: TypeHandle) -> Option<Box<dyn FloatAttr>> {
1176    let ty = ty.deref(ctx);
1177    if ty.is::<FP16Type>() {
1178        Some(Box::new(FPHalfAttr(Half::ZERO)))
1179    } else if ty.is::<BF16Type>() {
1180        Some(Box::new(FPBFloatAttr(BFloat::ZERO)))
1181    } else if ty.is::<FP32Type>() {
1182        Some(Box::new(FPSingleAttr(Single::ZERO)))
1183    } else if ty.is::<FP64Type>() {
1184        Some(Box::new(FPDoubleAttr(Double::ZERO)))
1185    } else {
1186        None
1187    }
1188}
1189
1190/// Fold a scalar integer-to-float cast.
1191fn check_fold_int_to_float(
1192    ctx: &Context,
1193    operand_attrs: &[Option<AttrObj>],
1194    result_ty: TypeHandle,
1195    signed: bool,
1196    nneg: bool,
1197) -> Vec<Option<AttrObj>> {
1198    let [Some(operand)] = operand_attrs else {
1199        assert!(operand_attrs.len() == 1);
1200        return vec![None];
1201    };
1202    let Some(value) = operand.downcast_ref::<IntegerAttr>() else {
1203        return vec![None];
1204    };
1205    let value = value.value();
1206
1207    // A violated `nneg` produces poison.
1208    if value.bw() > 128 || (nneg && value.is_negative()) {
1209        return vec![None];
1210    }
1211
1212    let Some(result) = zero_float_attr(ctx, result_ty) else {
1213        return vec![None];
1214    };
1215    let result = if signed {
1216        result.build_from_i128(value.to_i128())
1217    } else {
1218        result.build_from_u128(value.to_u128())
1219    };
1220    vec![Some(result.value as AttrObj)]
1221}
1222
1223#[op_interface_impl]
1224impl ConstFoldInterface for SIToFPOp {
1225    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1226        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1227            check_fold_int_to_float(ctx, ops, self.result_type(ctx), true, false)
1228        })
1229    }
1230    fn fold_in_place(
1231        &self,
1232        ctx: &mut Context,
1233        ops: &[Option<AttrObj>],
1234        rw: &mut dyn Rewriter,
1235    ) -> IRStatus {
1236        self.fold_with_materialization(ctx, ops, rw)
1237    }
1238}
1239
1240#[op_interface_impl]
1241impl ConstFoldInterface for UIToFPOp {
1242    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1243        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1244            check_fold_int_to_float(ctx, ops, self.result_type(ctx), false, self.nneg(ctx))
1245        })
1246    }
1247    fn fold_in_place(
1248        &self,
1249        ctx: &mut Context,
1250        ops: &[Option<AttrObj>],
1251        rw: &mut dyn Rewriter,
1252    ) -> IRStatus {
1253        self.fold_with_materialization(ctx, ops, rw)
1254    }
1255}
1256
1257/// Return whether any `value` violates the fast-math flags.
1258fn fast_math_forbids_fold(flags: FastmathFlagsAttr, values: &[&dyn FloatAttr]) -> bool {
1259    let flags = flags.0;
1260    (flags.contains(FastmathFlags::NNAN) && values.iter().any(|v| v.is_nan()))
1261        || (flags.contains(FastmathFlags::NINF) && values.iter().any(|v| v.is_infinite()))
1262}
1263
1264/// Convert a scalar float attribute to `result_ty`.
1265fn convert_float_attr(ctx: &Context, operand: &AttrObj, result_ty: TypeHandle) -> Option<AttrObj> {
1266    /// Convert `value` to `result_ty`, rounding to nearest-even.
1267    fn convert<S>(ctx: &Context, value: S, result_ty: TypeHandle) -> Option<AttrObj>
1268    where
1269        S: FloatConvert<Half> + FloatConvert<BFloat> + FloatConvert<Single> + FloatConvert<Double>,
1270    {
1271        let result_ty = result_ty.deref(ctx);
1272        if result_ty.is::<FP16Type>() {
1273            Some(Box::new(FPHalfAttr(value.convert(&mut false).value)) as AttrObj)
1274        } else if result_ty.is::<BF16Type>() {
1275            Some(Box::new(FPBFloatAttr(value.convert(&mut false).value)) as AttrObj)
1276        } else if result_ty.is::<FP32Type>() {
1277            Some(Box::new(FPSingleAttr(value.convert(&mut false).value)) as AttrObj)
1278        } else if result_ty.is::<FP64Type>() {
1279            Some(Box::new(FPDoubleAttr(value.convert(&mut false).value)) as AttrObj)
1280        } else {
1281            None
1282        }
1283    }
1284
1285    if let Some(operand) = operand.downcast_ref::<FPHalfAttr>() {
1286        convert(ctx, operand.0, result_ty)
1287    } else if let Some(operand) = operand.downcast_ref::<FPBFloatAttr>() {
1288        convert(ctx, operand.0, result_ty)
1289    } else if let Some(operand) = operand.downcast_ref::<FPSingleAttr>() {
1290        convert(ctx, operand.0, result_ty)
1291    } else if let Some(operand) = operand.downcast_ref::<FPDoubleAttr>() {
1292        convert(ctx, operand.0, result_ty)
1293    } else {
1294        None
1295    }
1296}
1297
1298/// Fold a scalar float cast.
1299fn check_fold_float_cast(
1300    ctx: &Context,
1301    operand_attrs: &[Option<AttrObj>],
1302    result_ty: TypeHandle,
1303    flags: FastmathFlagsAttr,
1304) -> Vec<Option<AttrObj>> {
1305    let [Some(operand)] = operand_attrs else {
1306        assert!(operand_attrs.len() == 1);
1307        return vec![None];
1308    };
1309    let Some(source) = attr_cast::<dyn FloatAttr>(&**operand) else {
1310        return vec![None];
1311    };
1312    let Some(result) = convert_float_attr(ctx, operand, result_ty) else {
1313        return vec![None];
1314    };
1315    let result_float = attr_cast::<dyn FloatAttr>(&*result)
1316        .expect("floating-point conversion must produce a floating-point attribute");
1317
1318    if fast_math_forbids_fold(flags, &[source, result_float]) {
1319        return vec![None];
1320    }
1321    vec![Some(result)]
1322}
1323
1324#[op_interface_impl]
1325impl ConstFoldInterface for FPExtOp {
1326    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1327        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1328            check_fold_float_cast(ctx, ops, self.result_type(ctx), self.fast_math_flags(ctx))
1329        })
1330    }
1331    fn fold_in_place(
1332        &self,
1333        ctx: &mut Context,
1334        ops: &[Option<AttrObj>],
1335        rw: &mut dyn Rewriter,
1336    ) -> IRStatus {
1337        self.fold_with_materialization(ctx, ops, rw)
1338    }
1339}
1340
1341#[op_interface_impl]
1342impl ConstFoldInterface for FPTruncOp {
1343    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1344        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1345            check_fold_float_cast(ctx, ops, self.result_type(ctx), self.fast_math_flags(ctx))
1346        })
1347    }
1348    fn fold_in_place(
1349        &self,
1350        ctx: &mut Context,
1351        ops: &[Option<AttrObj>],
1352        rw: &mut dyn Rewriter,
1353    ) -> IRStatus {
1354        self.fold_with_materialization(ctx, ops, rw)
1355    }
1356}
1357
1358#[op_interface_impl]
1359impl ConstFoldInterface for FNegOp {
1360    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1361        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1362            let [Some(operand)] = ops else {
1363                assert!(ops.len() == 1);
1364                return vec![None];
1365            };
1366            let Some(float_val) = attr_cast::<dyn FloatAttr>(&**operand) else {
1367                return vec![None];
1368            };
1369            let negated = float_val.neg();
1370            // Negation cannot create or destroy NaN/Inf, so checking the operand
1371            // covers the result too.
1372            if fast_math_forbids_fold(self.fast_math_flags(ctx), &[float_val]) {
1373                return vec![None];
1374            }
1375            vec![Some(negated as AttrObj)]
1376        })
1377    }
1378    fn fold_in_place(
1379        &self,
1380        ctx: &mut Context,
1381        ops: &[Option<AttrObj>],
1382        rw: &mut dyn Rewriter,
1383    ) -> IRStatus {
1384        self.fold_with_materialization(ctx, ops, rw)
1385    }
1386}
1387
1388/// An accessor to get the two constant operands of a binary float op.
1389fn get_float_bin_operands(
1390    operand_attrs: &[Option<AttrObj>],
1391) -> Option<(&dyn FloatAttr, &dyn FloatAttr)> {
1392    let [Some(lhs), Some(rhs)] = operand_attrs else {
1393        assert!(operand_attrs.len() == 2);
1394        return None;
1395    };
1396    let lhs = attr_cast::<dyn FloatAttr>(&**lhs)?;
1397    let rhs = attr_cast::<dyn FloatAttr>(&**rhs)?;
1398    Some((lhs, rhs))
1399}
1400
1401/// Constant fold a binary floating-point operation.
1402fn check_fold_float_bin_op(
1403    operand_attrs: &[Option<AttrObj>],
1404    flags: FastmathFlagsAttr,
1405    combine: impl Fn(&dyn FloatAttr, &dyn FloatAttr) -> Box<dyn FloatAttr>,
1406) -> Vec<Option<AttrObj>> {
1407    let Some((lhs, rhs)) = get_float_bin_operands(operand_attrs) else {
1408        return vec![None];
1409    };
1410    let res = combine(lhs, rhs);
1411    if fast_math_forbids_fold(flags, &[lhs, rhs, &*res]) {
1412        return vec![None];
1413    }
1414    vec![Some(res as AttrObj)]
1415}
1416
1417#[op_interface_impl]
1418impl ConstFoldInterface for FAddOp {
1419    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1420        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1421            check_fold_float_bin_op(ops, self.fast_math_flags(ctx), |lhs, rhs| {
1422                lhs.add(rhs).value
1423            })
1424        })
1425    }
1426    fn fold_in_place(
1427        &self,
1428        ctx: &mut Context,
1429        ops: &[Option<AttrObj>],
1430        rw: &mut dyn Rewriter,
1431    ) -> IRStatus {
1432        self.fold_with_materialization(ctx, ops, rw)
1433    }
1434}
1435
1436#[op_interface_impl]
1437impl ConstFoldInterface for FSubOp {
1438    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1439        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1440            check_fold_float_bin_op(ops, self.fast_math_flags(ctx), |lhs, rhs| {
1441                lhs.sub(rhs).value
1442            })
1443        })
1444    }
1445    fn fold_in_place(
1446        &self,
1447        ctx: &mut Context,
1448        ops: &[Option<AttrObj>],
1449        rw: &mut dyn Rewriter,
1450    ) -> IRStatus {
1451        self.fold_with_materialization(ctx, ops, rw)
1452    }
1453}
1454
1455#[op_interface_impl]
1456impl ConstFoldInterface for FMulOp {
1457    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1458        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1459            check_fold_float_bin_op(ops, self.fast_math_flags(ctx), |lhs, rhs| {
1460                lhs.mul(rhs).value
1461            })
1462        })
1463    }
1464    fn fold_in_place(
1465        &self,
1466        ctx: &mut Context,
1467        ops: &[Option<AttrObj>],
1468        rw: &mut dyn Rewriter,
1469    ) -> IRStatus {
1470        self.fold_with_materialization(ctx, ops, rw)
1471    }
1472}
1473
1474#[op_interface_impl]
1475impl ConstFoldInterface for FDivOp {
1476    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1477        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1478            check_fold_float_bin_op(ops, self.fast_math_flags(ctx), |lhs, rhs| {
1479                lhs.div(rhs).value
1480            })
1481        })
1482    }
1483    fn fold_in_place(
1484        &self,
1485        ctx: &mut Context,
1486        ops: &[Option<AttrObj>],
1487        rw: &mut dyn Rewriter,
1488    ) -> IRStatus {
1489        self.fold_with_materialization(ctx, ops, rw)
1490    }
1491}
1492
1493#[op_interface_impl]
1494impl ConstFoldInterface for FRemOp {
1495    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1496        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1497            check_fold_float_bin_op(ops, self.fast_math_flags(ctx), |lhs, rhs| {
1498                lhs.rem(rhs).value
1499            })
1500        })
1501    }
1502    fn fold_in_place(
1503        &self,
1504        ctx: &mut Context,
1505        ops: &[Option<AttrObj>],
1506        rw: &mut dyn Rewriter,
1507    ) -> IRStatus {
1508        self.fold_with_materialization(ctx, ops, rw)
1509    }
1510}
1511
1512/// Evaluate `lhs <pred> rhs` for same-typed floats.
1513fn eval_fcmp(pred: &FCmpPredicateAttr, lhs: &dyn FloatAttr, rhs: &dyn FloatAttr) -> bool {
1514    let ord = lhs.partial_cmp(rhs);
1515    match pred {
1516        FCmpPredicateAttr::False => false,
1517        FCmpPredicateAttr::True => true,
1518        FCmpPredicateAttr::ORD => ord.is_some(),
1519        FCmpPredicateAttr::UNO => ord.is_none(),
1520        FCmpPredicateAttr::OEQ => ord == Some(Ordering::Equal),
1521        FCmpPredicateAttr::OGT => ord == Some(Ordering::Greater),
1522        FCmpPredicateAttr::OGE => matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
1523        FCmpPredicateAttr::OLT => ord == Some(Ordering::Less),
1524        FCmpPredicateAttr::OLE => matches!(ord, Some(Ordering::Less | Ordering::Equal)),
1525        FCmpPredicateAttr::ONE => matches!(ord, Some(Ordering::Less | Ordering::Greater)),
1526        FCmpPredicateAttr::UEQ => !matches!(ord, Some(Ordering::Less | Ordering::Greater)),
1527        FCmpPredicateAttr::UGT => !matches!(ord, Some(Ordering::Less | Ordering::Equal)),
1528        FCmpPredicateAttr::UGE => ord != Some(Ordering::Less),
1529        FCmpPredicateAttr::ULT => !matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
1530        FCmpPredicateAttr::ULE => ord != Some(Ordering::Greater),
1531        FCmpPredicateAttr::UNE => ord != Some(Ordering::Equal),
1532    }
1533}
1534
1535#[op_interface_impl]
1536impl ConstFoldInterface for FCmpOp {
1537    fn check_fold(&self, ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1538        check_fold_poison_propagating_op(self.get_operation(), ctx, ops, |ops| {
1539            let Some((lhs, rhs)) = get_float_bin_operands(ops) else {
1540                return vec![None];
1541            };
1542            // Only the operands can violate fast-math assumptions.
1543            if fast_math_forbids_fold(self.fast_math_flags(ctx), &[lhs, rhs]) {
1544                return vec![None];
1545            }
1546            let result = eval_fcmp(&self.predicate(ctx), lhs, rhs);
1547            vec![Some(bool_attr(ctx, result))]
1548        })
1549    }
1550    fn fold_in_place(
1551        &self,
1552        ctx: &mut Context,
1553        ops: &[Option<AttrObj>],
1554        rw: &mut dyn Rewriter,
1555    ) -> IRStatus {
1556        self.fold_with_materialization(ctx, ops, rw)
1557    }
1558}
1559
1560#[op_interface_impl]
1561impl ConstFoldInterface for SelectOp {
1562    fn check_fold(&self, _ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1563        let [cond, true_val, false_val] = ops else {
1564            assert!(ops.len() == 3);
1565            return vec![None];
1566        };
1567        match cond {
1568            Some(cond_attr) => {
1569                // Vector conditions require element-wise folding.
1570                let Some(cond_int) = cond_attr.downcast_ref::<IntegerAttr>() else {
1571                    return vec![None];
1572                };
1573                let chosen = if cond_int.value().is_zero() {
1574                    false_val
1575                } else {
1576                    true_val
1577                };
1578                vec![chosen.clone()]
1579            }
1580            // Equal constants make the condition irrelevant.
1581            None => match (true_val, false_val) {
1582                (Some(t), Some(f)) if t == f => vec![Some(t.clone())],
1583                _ => vec![None],
1584            },
1585        }
1586    }
1587    fn fold_in_place(
1588        &self,
1589        ctx: &mut Context,
1590        ops: &[Option<AttrObj>],
1591        rw: &mut dyn Rewriter,
1592    ) -> IRStatus {
1593        // A constant condition forwards one operand directly.
1594        if let Some(cond_attr) = &ops[0]
1595            && let Some(cond_int) = cond_attr.downcast_ref::<IntegerAttr>()
1596        {
1597            let (chosen, result) = {
1598                let op = self.get_operation().deref(ctx);
1599                let chosen_idx = if cond_int.value().is_zero() { 2 } else { 1 };
1600                (op.get_operand(chosen_idx), op.get_result(0))
1601            };
1602            if !result.is_used(ctx) {
1603                return IRStatus::Unchanged;
1604            }
1605            rw.replace_value_uses_with(ctx, result, chosen);
1606            return IRStatus::Changed;
1607        }
1608        self.fold_with_materialization(ctx, ops, rw)
1609    }
1610}
1611
1612/// Is `attr` an `undef` or a `poison` value, or does it contain one?
1613fn attr_contains_undef_or_poison(attr: &dyn Attribute) -> bool {
1614    if is_undef_or_poison_attr(attr) {
1615        return true;
1616    }
1617    if let Some(aggregate) = attr.downcast_ref::<AggregateAttr>() {
1618        return aggregate
1619            .elements()
1620            .iter()
1621            .any(|element| attr_contains_undef_or_poison(&**element as &dyn Attribute));
1622    }
1623    if let Some(splat) = attr.downcast_ref::<SplatAttr>() {
1624        return attr_contains_undef_or_poison(splat.element() as &dyn Attribute);
1625    }
1626    false
1627}
1628
1629#[op_interface_impl]
1630impl ConstFoldInterface for FreezeOp {
1631    fn check_fold(&self, _ctx: &Context, ops: &[Option<AttrObj>]) -> Vec<Option<AttrObj>> {
1632        let [operand] = ops else {
1633            assert!(ops.len() == 1);
1634            return vec![None];
1635        };
1636        // A constant without `undef` or `poison` parts is its own frozen value.
1637        vec![
1638            operand
1639                .as_ref()
1640                .filter(|attr| !attr_contains_undef_or_poison(&***attr))
1641                .cloned(),
1642        ]
1643    }
1644    fn fold_in_place(
1645        &self,
1646        ctx: &mut Context,
1647        ops: &[Option<AttrObj>],
1648        rw: &mut dyn Rewriter,
1649    ) -> IRStatus {
1650        let is_undef_or_poison = ops[0]
1651            .as_ref()
1652            .is_some_and(|attr| is_undef_or_poison_attr(&**attr));
1653        if !is_undef_or_poison {
1654            return self.fold_with_materialization(ctx, ops, rw);
1655        }
1656        // All uses of a frozen `undef` or `poison` see one arbitrary value.
1657        // Zero is a valid choice for that value.
1658        let result = self.get_result(ctx);
1659        if !result.is_used(ctx) {
1660            return IRStatus::Unchanged;
1661        }
1662        let zero = ZeroOp::new(ctx, result.get_type(ctx));
1663        rw.append_operation(ctx, zero.get_operation());
1664        rw.replace_value_uses_with(ctx, result, zero.get_result(ctx));
1665        IRStatus::Changed
1666    }
1667}
1668
1669#[op_interface_impl]
1670impl BranchOpFoldInterface for BrOp {
1671    fn check_fold(&self, ctx: &Context, _operands: &[Option<AttrObj>]) -> Vec<Ptr<BasicBlock>> {
1672        self.get_operation().deref(ctx).successors().collect()
1673    }
1674    fn fold_in_place(
1675        &self,
1676        _ctx: &mut Context,
1677        _operand_attrs: &[Option<AttrObj>],
1678        _rw: &mut dyn Rewriter,
1679    ) -> IRStatus {
1680        IRStatus::Unchanged
1681    }
1682}
1683
1684/// Is the branch condition (the first operand) `undef` or `poison`?
1685/// Branching on such a value is undefined behavior, so no successor is feasible.
1686fn is_undef_or_poison_condition(operands: &[Option<AttrObj>]) -> bool {
1687    operands
1688        .first()
1689        .and_then(|cond| cond.as_ref())
1690        .is_some_and(|cond| is_undef_or_poison_attr(&**cond))
1691}
1692
1693/// Replace the branch `op` with `llvm.unreachable`.
1694fn replace_with_unreachable(
1695    ctx: &mut Context,
1696    op: Ptr<Operation>,
1697    rewriter: &mut dyn Rewriter,
1698) -> IRStatus {
1699    let unreachable = UnreachableOp::new(ctx).get_operation();
1700    rewriter.insert_operation(ctx, unreachable);
1701    rewriter.replace_operation(ctx, op, unreachable);
1702    IRStatus::Changed
1703}
1704
1705impl CondBrOp {
1706    /// Returns the possible successor indices based on the condition operand.
1707    fn possible_successor_indices(
1708        &self,
1709        ctx: &Context,
1710        operands: &[Option<AttrObj>],
1711    ) -> Vec<usize> {
1712        if is_undef_or_poison_condition(operands) {
1713            return vec![];
1714        }
1715        // Any other constant that is not an integer gives no information.
1716        let Some(cond_int) = operands
1717            .first()
1718            .unwrap()
1719            .as_ref()
1720            .and_then(|cond_attr| cond_attr.downcast_ref::<IntegerAttr>())
1721        else {
1722            let num_successors = self.get_operation().deref(ctx).successors().count();
1723            return (0..num_successors).collect();
1724        };
1725        let taken = if cond_int.value().is_zero() { 1 } else { 0 };
1726        vec![taken]
1727    }
1728}
1729
1730#[op_interface_impl]
1731impl BranchOpFoldInterface for CondBrOp {
1732    fn check_fold(&self, ctx: &Context, operands: &[Option<AttrObj>]) -> Vec<Ptr<BasicBlock>> {
1733        let successors: Vec<Ptr<BasicBlock>> =
1734            self.get_operation().deref(ctx).successors().collect();
1735
1736        self.possible_successor_indices(ctx, operands)
1737            .iter()
1738            .map(|ind| successors[*ind])
1739            .collect()
1740    }
1741
1742    fn fold_in_place(
1743        &self,
1744        ctx: &mut Context,
1745        operand_attrs: &[Option<AttrObj>],
1746        rewriter: &mut dyn Rewriter,
1747    ) -> IRStatus {
1748        let successor_ind = match self.possible_successor_indices(ctx, operand_attrs)[..] {
1749            [] => return replace_with_unreachable(ctx, self.get_operation(), rewriter),
1750            [successor_ind] => successor_ind,
1751            _ => return IRStatus::Unchanged,
1752        };
1753        let successors: Vec<Ptr<BasicBlock>> =
1754            self.get_operation().deref(ctx).successors().collect();
1755        let new_op = BrOp::new(
1756            ctx,
1757            successors[successor_ind],
1758            self.successor_operands(ctx, successor_ind),
1759        )
1760        .get_operation();
1761        let old_op = self.get_operation();
1762        rewriter.insert_operation(ctx, new_op);
1763        rewriter.replace_operation(ctx, old_op, new_op);
1764        IRStatus::Changed
1765    }
1766}
1767
1768#[op_interface_impl]
1769impl BranchOpFoldInterface for SwitchOp {
1770    fn check_fold(&self, ctx: &Context, operands: &[Option<AttrObj>]) -> Vec<Ptr<BasicBlock>> {
1771        let successors: Vec<Ptr<BasicBlock>> =
1772            self.get_operation().deref(ctx).successors().collect();
1773        if is_undef_or_poison_condition(operands) {
1774            return vec![];
1775        }
1776        // Any other constant that is not an integer gives no information.
1777        let Some(cond_int) = operands
1778            .first()
1779            .and_then(|o| o.as_ref())
1780            .and_then(|cond_attr| cond_attr.downcast_ref::<IntegerAttr>())
1781            .map(|cond_int| cond_int.value())
1782        else {
1783            return successors;
1784        };
1785        // Successor 0 is the default destination; successors 1..N correspond to case_values[0..N-1].
1786        let case_values = self
1787            .get_attr_llvm_switch_case_values(ctx)
1788            .expect("SwitchOp missing case values attribute");
1789        let taken = case_values
1790            .0
1791            .iter()
1792            .position(|case| case.value() == cond_int)
1793            .map(|i| i + 1)
1794            .unwrap_or(0);
1795        vec![successors[taken]]
1796    }
1797
1798    fn fold_in_place(
1799        &self,
1800        ctx: &mut Context,
1801        operand_attrs: &[Option<AttrObj>],
1802        rewriter: &mut dyn Rewriter,
1803    ) -> IRStatus {
1804        if is_undef_or_poison_condition(operand_attrs) {
1805            return replace_with_unreachable(ctx, self.get_operation(), rewriter);
1806        }
1807        let Some(cond_int) = operand_attrs
1808            .first()
1809            .unwrap()
1810            .as_ref()
1811            .and_then(|cond_attr| cond_attr.downcast_ref::<IntegerAttr>())
1812            .map(|cond_int| cond_int.value())
1813        else {
1814            return IRStatus::Unchanged;
1815        };
1816        let successor_ind = {
1817            let case_values = self
1818                .get_attr_llvm_switch_case_values(ctx)
1819                .expect("SwitchOp missing case values attribute");
1820            case_values
1821                .0
1822                .iter()
1823                .position(|case| case.value() == cond_int)
1824                // There is no case value corresponding to the default successor,
1825                // so case_values index 0 corresponds to succesors index 1, etc.
1826                .map(|i| i + 1)
1827                // successor index 0 is the default successor
1828                .unwrap_or(0)
1829        };
1830        let successors: Vec<Ptr<BasicBlock>> =
1831            self.get_operation().deref(ctx).successors().collect();
1832        let new_op = BrOp::new(
1833            ctx,
1834            successors[successor_ind],
1835            self.successor_operands(ctx, successor_ind),
1836        )
1837        .get_operation();
1838        let old_op = self.get_operation();
1839        rewriter.insert_operation(ctx, new_op);
1840        rewriter.replace_operation(ctx, old_op, new_op);
1841        IRStatus::Changed
1842    }
1843}