Skip to main content

cubecl_ir/dialect/
cf.rs

1//! Mainly for testing, but can be used as an intermediate CFG dialect that still uses `cube.bool`
2
3use core::ops::Range;
4
5use pliron::{
6    attribute::AttrObj,
7    basic_block::BasicBlock,
8    builtin::{attributes::IntegerAttr, op_interfaces, types::IntegerType},
9    combine::{self, Parser, between, parser::char::spaces, token},
10    common_traits::Named,
11    identifier::Identifier,
12    input_err,
13    irfmt::{
14        self,
15        parsers::{
16            block_opd_parser, delimited_list_parser, process_parsed_ssa_defs, spaced,
17            ssa_opd_parser,
18        },
19        printers::{iter_with_sep, list_with_sep},
20    },
21    location::Location,
22    op::OpObj,
23    opts::constants::BranchOpFoldInterface,
24    parsable::{IntoParseResult, Parsable, ParseResult, StateStream},
25    printable::{Printable, indented_nl},
26    verify_err,
27};
28use thiserror::Error;
29
30use crate::{
31    NoMemoryEffect,
32    attributes::{BoolAttr, IntegerVecAttr, ZeroAttr},
33    prelude::*,
34    types::scalar::BoolType,
35};
36
37#[pliron_op(
38    name = "cf.branch",
39    format = "succ($0) `(` operands(CharSpace(`,`)) `)`",
40    interfaces = [
41        IsTerminatorInterface,
42        NResultsInterface<0>,
43        NSuccsInterface<1>,
44        OneSuccInterface
45    ],
46    verifier = "succ"
47)]
48#[op_traits(NoMemoryEffect)]
49pub struct BranchOp;
50
51#[op_interface_impl]
52impl BranchOpInterface for BranchOp {
53    fn verify_successor_operand_layout(&self, ctx: &Context) -> Result<()> {
54        <Self as OneSuccInterface>::verify(self, ctx)
55    }
56
57    fn successor_operand_range(&self, ctx: &Context, succ_idx: usize) -> Range<usize> {
58        assert!(succ_idx == 0, "BranchOp has exactly one successor");
59        0..self.get_operation().deref(ctx).get_num_operands()
60    }
61
62    fn add_successor_operand(&self, ctx: &mut Context, succ_idx: usize, operand: Value) -> usize {
63        assert!(succ_idx == 0, "BrOp has exactly one successor");
64        Operation::push_operand(self.get_operation(), ctx, operand)
65    }
66
67    fn remove_successor_operand(
68        &self,
69        ctx: &mut Context,
70        succ_idx: usize,
71        opd_idx: usize,
72    ) -> Value {
73        assert!(succ_idx == 0, "BrOp has exactly one successor");
74        Operation::remove_operand(self.get_operation(), ctx, opd_idx)
75    }
76}
77
78impl BranchOp {
79    /// Create a new [`BranchOp`].
80    pub fn new(ctx: &mut Context, dest: Ptr<BasicBlock>, dest_opds: Vec<Value>) -> Self {
81        BranchOp {
82            op: Operation::new(
83                ctx,
84                Self::get_concrete_op_info(),
85                vec![],
86                dest_opds,
87                vec![dest],
88                0,
89            ),
90        }
91    }
92}
93
94#[pliron_op(
95    name = "cf.branch_conditional",
96    operands = (condition: BoolType),
97    verifier = "succ"
98)]
99#[op_interfaces(IsTerminatorInterface, NResultsInterface<0>, NSuccsInterface<2>)]
100#[op_traits(NoMemoryEffect)]
101pub struct BranchConditionalOp;
102impl BranchConditionalOp {
103    /// Create a new [`BranchConditionalOp`].
104    pub fn new(
105        ctx: &mut Context,
106        condition: Value,
107        true_dest: Ptr<BasicBlock>,
108        true_dest_opds: Vec<Value>,
109        false_dest: Ptr<BasicBlock>,
110        false_dest_opds: Vec<Value>,
111    ) -> Self {
112        let (operands, segment_sizes) =
113            Self::compute_segment_sizes(vec![vec![condition], true_dest_opds, false_dest_opds]);
114
115        let op = BranchConditionalOp {
116            op: Operation::new(
117                ctx,
118                Self::get_concrete_op_info(),
119                vec![],
120                operands,
121                vec![true_dest, false_dest],
122                0,
123            ),
124        };
125
126        // Set the operand segment sizes attribute.
127        op.set_operand_segment_sizes(ctx, segment_sizes);
128        op
129    }
130}
131
132#[op_interface_impl]
133impl OperandSegmentInterface for BranchConditionalOp {
134    fn expected_num_segments(&self, _ctx: &Context) -> Option<usize> {
135        // The condition, the true destination operands and the false destination operands.
136        Some(3)
137    }
138}
139
140impl Printable for BranchConditionalOp {
141    fn fmt(
142        &self,
143        ctx: &Context,
144        state: &pliron::printable::State,
145        f: &mut core::fmt::Formatter<'_>,
146    ) -> core::fmt::Result {
147        let op = self.get_operation().deref(ctx);
148        let condition = self.get_operand_condition(ctx);
149        let true_dest_opds = self.successor_operands(ctx, 0);
150        let false_dest_opds = self.successor_operands(ctx, 1);
151        let res = write!(
152            f,
153            "{} if {} ^{}({}) else ^{}({})",
154            Self::get_opid_static(),
155            condition.print(ctx, state),
156            op.get_successor(0).deref(ctx).unique_name(ctx),
157            iter_with_sep(
158                true_dest_opds.iter(),
159                pliron::printable::ListSeparator::CharSpace(',')
160            )
161            .print(ctx, state),
162            op.get_successor(1).deref(ctx).unique_name(ctx),
163            iter_with_sep(
164                false_dest_opds.iter(),
165                pliron::printable::ListSeparator::CharSpace(',')
166            )
167            .print(ctx, state),
168        );
169        res
170    }
171}
172
173impl Parsable for BranchConditionalOp {
174    type Arg = Vec<(Identifier, Location)>;
175    type Parsed = OpObj;
176    fn parse<'a>(
177        state_stream: &mut StateStream<'a>,
178        results: Self::Arg,
179    ) -> ParseResult<'a, Self::Parsed> {
180        if !results.is_empty() {
181            input_err!(
182                state_stream.loc(),
183                op_interfaces::NResultsVerifyErr(0, results.len())
184            )?
185        }
186
187        // Parse the condition operand.
188        let r#if = irfmt::parsers::spaced::<StateStream, _>(combine::parser::char::string("if"));
189
190        let condition = ssa_opd_parser();
191
192        let true_operands = delimited_list_parser('(', ')', ',', ssa_opd_parser());
193
194        let r_else =
195            irfmt::parsers::spaced::<StateStream, _>(combine::parser::char::string("else"));
196
197        let false_operands = delimited_list_parser('(', ')', ',', ssa_opd_parser());
198
199        let final_parser = r#if
200            .with(spaced(condition))
201            .and(spaced(block_opd_parser()))
202            .and(true_operands)
203            .and(spaced(r_else).with(spaced(block_opd_parser()).and(false_operands)));
204
205        final_parser
206            .then(
207                move |(((condition, true_dest), true_dest_opds), (false_dest, false_dest_opds))| {
208                    let results = results.clone();
209                    combine::parser(move |parsable_state: &mut StateStream<'a>| {
210                        let ctx = &mut parsable_state.state.ctx;
211                        let op = BranchConditionalOp::new(
212                            ctx,
213                            condition,
214                            true_dest,
215                            true_dest_opds.clone(),
216                            false_dest,
217                            false_dest_opds.clone(),
218                        );
219
220                        process_parsed_ssa_defs(parsable_state, &results, op.get_operation())?;
221                        Ok(OpObj::new(op)).into_parse_result()
222                    })
223                },
224            )
225            .parse_stream(state_stream)
226            .into()
227    }
228}
229
230#[op_interface_impl]
231impl BranchOpInterface for BranchConditionalOp {
232    fn verify_successor_operand_layout(&self, ctx: &Context) -> Result<()> {
233        <Self as OperandSegmentInterface>::verify(self, ctx)?;
234        <Self as NSuccsInterface<2>>::verify(self, ctx)
235    }
236
237    fn successor_operand_range(&self, ctx: &Context, succ_idx: usize) -> Range<usize> {
238        assert!(
239            succ_idx == 0 || succ_idx == 1,
240            "CondBrOp has exactly two successors"
241        );
242
243        // Skip the first segment, which is the condition.
244        self.segment_range(ctx, succ_idx + 1)
245    }
246
247    fn add_successor_operand(&self, ctx: &mut Context, succ_idx: usize, operand: Value) -> usize {
248        // The successor operands start at segment 1, since segment 0 is the condition operand.
249        self.push_to_segment(ctx, succ_idx + 1, operand)
250    }
251
252    fn remove_successor_operand(
253        &self,
254        ctx: &mut Context,
255        succ_idx: usize,
256        opd_idx: usize,
257    ) -> Value {
258        // The successor operands start at segment 1, since segment 0 is the condition operand.
259        self.remove_from_segment(ctx, succ_idx + 1, opd_idx)
260    }
261}
262
263#[pliron_op(
264    name = "cf.switch",
265    operands = (value: IntegerType),
266    attributes = (cf_switch_case_values: IntegerVecAttr)
267)]
268#[op_interfaces(IsTerminatorInterface, NResultsInterface<0>)]
269#[op_traits(NoMemoryEffect)]
270pub struct SwitchOp;
271
272#[op_interface_impl]
273impl OperandSegmentInterface for SwitchOp {
274    fn expected_num_segments(&self, ctx: &Context) -> Option<usize> {
275        // One segment for the condition, and one segment for each successor.
276        Some(self.get_operation().deref(ctx).get_num_successors() + 1)
277    }
278}
279
280/// One case of a switch statement.
281#[derive(Clone)]
282pub struct SwitchCase {
283    /// The value being matched against.
284    pub value: IntegerAttr,
285    /// The destination block to jump to if this case is taken.
286    pub dest: Ptr<BasicBlock>,
287    /// The operands to pass to the destination block.
288    pub dest_opds: Vec<Value>,
289}
290
291impl Printable for SwitchCase {
292    fn fmt(
293        &self,
294        ctx: &Context,
295        state: &pliron::printable::State,
296        f: &mut core::fmt::Formatter<'_>,
297    ) -> core::fmt::Result {
298        write!(
299            f,
300            "{{ {}: ^{}({}) }}",
301            self.value.print(ctx, state),
302            self.dest.deref(ctx).unique_name(ctx),
303            list_with_sep(
304                &self.dest_opds,
305                pliron::printable::ListSeparator::CharSpace(',')
306            )
307            .print(ctx, state)
308        )
309    }
310}
311
312impl Parsable for SwitchCase {
313    type Arg = ();
314    type Parsed = Self;
315
316    fn parse<'a>(
317        state_stream: &mut StateStream<'a>,
318        _arg: Self::Arg,
319    ) -> ParseResult<'a, Self::Parsed> {
320        let mut parser = between(
321            token('{'),
322            token('}'),
323            (
324                spaced(IntegerAttr::parser(())),
325                spaced(token(':')),
326                spaced(block_opd_parser()),
327                delimited_list_parser('(', ')', ',', ssa_opd_parser()),
328                spaces(),
329            ),
330        );
331
332        let ((value, _colon, dest, dest_opds, _spaces), _) =
333            parser.parse_stream(state_stream).into_result()?;
334
335        Ok(SwitchCase {
336            value,
337            dest,
338            dest_opds,
339        })
340        .into_parse_result()
341    }
342}
343
344impl Printable for SwitchOp {
345    fn fmt(
346        &self,
347        ctx: &Context,
348        state: &pliron::printable::State,
349        f: &mut core::fmt::Formatter<'_>,
350    ) -> core::fmt::Result {
351        let op = self.get_operation().deref(ctx);
352        let value = self.get_operand_value(ctx);
353
354        let default_successor = op
355            .successors()
356            .next()
357            .expect("SwitchOp must have at least one successor");
358        let num_total_successors = op.get_num_successors();
359
360        write!(
361            f,
362            "{} {}, ^{}({})",
363            Self::get_opid_static(),
364            value.print(ctx, state),
365            default_successor.unique_name(ctx).print(ctx, state),
366            iter_with_sep(
367                self.successor_operands(ctx, 0).iter(),
368                pliron::printable::ListSeparator::CharSpace(',')
369            )
370            .print(ctx, state),
371        )?;
372
373        if num_total_successors < 2 {
374            writeln!(f, "[]")?;
375            return Ok(());
376        }
377
378        let cases = self.cases(ctx);
379
380        write!(f, "{}[", indented_nl(state))?;
381        {
382            let _indent = state.indent();
383            write!(f, "{}", indented_nl(state))?;
384            list_with_sep(&cases, pliron::printable::ListSeparator::CharNewline(','))
385                .fmt(ctx, state, f)?;
386        }
387        write!(f, "{}]", indented_nl(state))?;
388
389        Ok(())
390    }
391}
392
393impl Parsable for SwitchOp {
394    type Arg = Vec<(Identifier, Location)>;
395    type Parsed = OpObj;
396
397    fn parse<'a>(
398        state_stream: &mut StateStream<'a>,
399        arg: Self::Arg,
400    ) -> ParseResult<'a, Self::Parsed> {
401        if !arg.is_empty() {
402            input_err!(
403                state_stream.loc(),
404                op_interfaces::NResultsVerifyErr(0, arg.len())
405            )?
406        }
407
408        // Parse the condition operand.
409        let condition = ssa_opd_parser().skip(spaced(token(',')));
410        let default_successor = block_opd_parser();
411        let default_operands = delimited_list_parser('(', ')', ',', ssa_opd_parser());
412        let cases = delimited_list_parser('[', ']', ',', SwitchCase::parser(()));
413
414        let final_parser = spaced(condition)
415            .and(default_successor)
416            .skip(spaces())
417            .and(default_operands)
418            .skip(spaces())
419            .and(cases);
420
421        final_parser
422            .then(
423                move |(((condition, default_dest), default_dest_opds), cases)| {
424                    let results = arg.clone();
425                    combine::parser(move |parsable_state: &mut StateStream<'a>| {
426                        let ctx = &mut parsable_state.state.ctx;
427                        let op = SwitchOp::new(
428                            ctx,
429                            condition,
430                            default_dest,
431                            default_dest_opds.clone(),
432                            cases.clone(),
433                        );
434
435                        process_parsed_ssa_defs(parsable_state, &results, op.get_operation())?;
436                        Ok(OpObj::new(op)).into_parse_result()
437                    })
438                },
439            )
440            .parse_stream(state_stream)
441            .into()
442    }
443}
444
445impl SwitchOp {
446    /// Create a new [`SwitchOp`].
447    pub fn new(
448        ctx: &mut Context,
449        condition: Value,
450        default_dest: Ptr<BasicBlock>,
451        default_dest_opds: Vec<Value>,
452        cases: Vec<SwitchCase>,
453    ) -> Self {
454        let case_values: Vec<IntegerAttr> = cases.iter().map(|case| case.value.clone()).collect();
455
456        let case_operands = cases
457            .iter()
458            .map(|case| case.dest_opds.clone())
459            .collect::<Vec<_>>();
460
461        let mut operand_segments = vec![vec![condition], default_dest_opds];
462        operand_segments.extend(case_operands);
463        let (operands, segment_sizes) = Self::compute_segment_sizes(operand_segments);
464
465        let case_dests = cases.iter().map(|case| case.dest);
466        let successors = vec![default_dest].into_iter().chain(case_dests).collect();
467        let op = SwitchOp {
468            op: Operation::new(
469                ctx,
470                Self::get_concrete_op_info(),
471                vec![],
472                operands,
473                successors,
474                0,
475            ),
476        };
477
478        // Set the operand segment sizes attribute.
479        op.set_operand_segment_sizes(ctx, segment_sizes);
480        // Set the case values
481        op.set_attr_cf_switch_case_values(ctx, IntegerVecAttr(case_values));
482        op
483    }
484
485    /// Get the cases of this switch operation.
486    /// (The default case cannot be / isn't included here).
487    pub fn cases(&self, ctx: &Context) -> Vec<SwitchCase> {
488        let case_values = &*self
489            .get_attr_cf_switch_case_values(ctx)
490            .expect("SwitchOp missing or incorrect case values attribute");
491
492        let op = self.get_operation().deref(ctx);
493        // Skip the first one, which is the default successor.
494        let successors = op.successors().skip(1);
495
496        successors
497            .zip(case_values.0.iter())
498            .enumerate()
499            .map(|(i, (dest, value))| {
500                // i+1 here because the first successor is the default destination.
501                let dest_opds = self.successor_operands(ctx, i + 1);
502                SwitchCase {
503                    value: value.clone(),
504                    dest,
505                    dest_opds,
506                }
507            })
508            .collect()
509    }
510
511    /// Get the default destination of this switch operation.
512    pub fn default_dest(&self, ctx: &Context) -> Ptr<BasicBlock> {
513        self.get_operation().deref(ctx).get_successor(0)
514    }
515
516    /// Get the operands to pass to the default destination.
517    pub fn default_dest_operands(&self, ctx: &Context) -> Vec<Value> {
518        self.successor_operands(ctx, 0)
519    }
520}
521
522#[op_interface_impl]
523impl BranchOpInterface for SwitchOp {
524    fn verify_successor_operand_layout(&self, ctx: &Context) -> Result<()> {
525        <Self as OperandSegmentInterface>::verify(self, ctx)
526    }
527
528    fn successor_operand_range(&self, ctx: &Context, succ_idx: usize) -> Range<usize> {
529        // Skip the first segment, which is the condition.
530        self.segment_range(ctx, succ_idx + 1)
531    }
532
533    fn add_successor_operand(&self, ctx: &mut Context, succ_idx: usize, operand: Value) -> usize {
534        // The successor operands start at segment 1, since segment 0 is the condition operand.
535        self.push_to_segment(ctx, succ_idx + 1, operand)
536    }
537
538    fn remove_successor_operand(
539        &self,
540        ctx: &mut Context,
541        succ_idx: usize,
542        opd_idx: usize,
543    ) -> Value {
544        // The successor operands start at segment 1, since segment 0 is the condition operand.
545        self.remove_from_segment(ctx, succ_idx + 1, opd_idx)
546    }
547}
548
549#[derive(Error, Debug)]
550pub enum SwitchOpVerifyErr {
551    #[error("SwitchOp has no or incorrect case values attribute")]
552    CaseValuesAttrErr,
553    #[error("SwitchOp has no or incorrect default destination")]
554    DefaultDestErr,
555}
556
557impl Verify for SwitchOp {
558    fn verify(&self, ctx: &Context) -> Result<()> {
559        let loc = self.loc(ctx);
560
561        let op = &*self.get_operation().deref(ctx);
562        if op.get_num_successors() < 1 {
563            verify_err!(loc.clone(), SwitchOpVerifyErr::DefaultDestErr)?;
564        }
565
566        Ok(())
567    }
568}
569
570#[op_interface_impl]
571impl BranchOpFoldInterface for BranchOp {
572    fn check_fold(&self, ctx: &Context, _operands: &[Option<AttrObj>]) -> Vec<Ptr<BasicBlock>> {
573        self.get_operation().deref(ctx).successors().collect()
574    }
575    fn fold_in_place(
576        &self,
577        _ctx: &mut Context,
578        _ops: &[Option<AttrObj>],
579        _rw: &mut dyn Rewriter,
580    ) -> IRStatus {
581        IRStatus::Unchanged
582    }
583}
584
585impl BranchConditionalOp {
586    fn possible_successor_indices(
587        &self,
588        ctx: &Context,
589        operands: &[Option<AttrObj>],
590    ) -> Vec<usize> {
591        let Some(cond_attr) = operands.first().unwrap().as_ref() else {
592            let num_successors = self.get_operation().deref(ctx).successors().count();
593            return (0..num_successors).collect();
594        };
595        let zero = cond_attr.downcast_ref::<ZeroAttr>().map(|_| false);
596        let bool = cond_attr.downcast_ref::<BoolAttr>().map(|it| it.0);
597        let Some(const_cond) = zero.or(bool) else {
598            let num_successors = self.get_operation().deref(ctx).successors().count();
599            return (0..num_successors).collect();
600        };
601        let taken = if const_cond { 0 } else { 1 };
602        vec![taken]
603    }
604}
605
606#[op_interface_impl]
607impl BranchOpFoldInterface for BranchConditionalOp {
608    fn check_fold(&self, ctx: &Context, operands: &[Option<AttrObj>]) -> Vec<Ptr<BasicBlock>> {
609        let successors: Vec<Ptr<BasicBlock>> =
610            self.get_operation().deref(ctx).successors().collect();
611
612        self.possible_successor_indices(ctx, operands)
613            .iter()
614            .map(|ind| successors[*ind])
615            .collect()
616    }
617
618    fn fold_in_place(
619        &self,
620        _ctx: &mut Context,
621        _ops: &[Option<AttrObj>],
622        _rewriter: &mut dyn Rewriter,
623    ) -> IRStatus {
624        IRStatus::Unchanged
625    }
626}
627
628#[op_interface_impl]
629impl BranchOpFoldInterface for SwitchOp {
630    fn check_fold(&self, ctx: &Context, operands: &[Option<AttrObj>]) -> Vec<Ptr<BasicBlock>> {
631        let successors: Vec<Ptr<BasicBlock>> =
632            self.get_operation().deref(ctx).successors().collect();
633        let Some(cond_attr) = operands.first().and_then(|o| o.as_ref()) else {
634            return successors;
635        };
636        let cond_int = cond_attr
637            .downcast_ref::<IntegerAttr>()
638            .expect("Switch condition operand must be an IntegerAttr")
639            .value();
640        // Successor 0 is the default destination; successors 1..N correspond to case_values[0..N-1].
641        let case_values = self
642            .get_attr_cf_switch_case_values(ctx)
643            .expect("SwitchOp missing case values attribute");
644        let taken = case_values
645            .0
646            .iter()
647            .position(|case| case.value() == cond_int)
648            .map(|i| i + 1)
649            .unwrap_or(0);
650        vec![successors[taken]]
651    }
652
653    fn fold_in_place(
654        &self,
655        _ctx: &mut Context,
656        _ops: &[Option<AttrObj>],
657        _rewriter: &mut dyn Rewriter,
658    ) -> IRStatus {
659        IRStatus::Unchanged
660    }
661}