1use 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
157macro_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
171impl_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
232fn 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
243fn 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
274fn is_undef_or_poison_attr(attr: &dyn Attribute) -> bool {
276 attr.downcast_ref::<UndefAttr>().is_some() || attr.downcast_ref::<PoisonAttr>().is_some()
277}
278
279fn 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
349fn 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
474fn 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
509fn is_signed_div_ub(lhs: &APInt, rhs: &APInt) -> bool {
511 let bw = NonZero::new(rhs.bw()).expect("operand has zero bitwidth");
512 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 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 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
713fn 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
751fn 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
770fn 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
781fn 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 let mut elements: Vec<Box<dyn TypedAttrInterface>> = Vec::with_capacity(mask.len());
794 for &mask_index in mask {
795 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
833fn constant_vector_index(index: &IntegerAttr, length: usize) -> Option<usize> {
835 let value = index.value();
836 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
871fn 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
912fn 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
949fn 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
998fn 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
1010fn 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 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
1093fn 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 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
1174fn 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
1190fn 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 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
1257fn 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
1264fn convert_float_attr(ctx: &Context, operand: &AttrObj, result_ty: TypeHandle) -> Option<AttrObj> {
1266 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
1298fn 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 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
1388fn 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
1401fn 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
1512fn 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 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 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 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 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
1612fn 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 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 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
1684fn 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
1693fn 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 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 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 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 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 .map(|i| i + 1)
1827 .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}