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