1use pliron::{
2 attribute::AttrObj,
3 basic_block::BasicBlock,
4 builtin::attributes::{IntegerAttr, VecAttr},
5 irbuild::inserter::OpInsertionPoint,
6 linked_list::ContainsLinkedList,
7 opts::{constants::ConstFoldInterface, dce::SideEffects},
8 region::Region,
9 utils::const_bound_n::I,
10 verify_err,
11};
12use thiserror::Error;
13
14use crate::{
15 CanMaterialize, NoMemoryEffect, Pure,
16 attributes::{BoolAttr, ZeroAttr},
17 prelude::*,
18 types::scalar::BoolType,
19};
20
21#[op_interface]
23pub trait IsExitTerminator {
24 fn verify(_op: &dyn Op, _ctx: &Context) -> Result<()>
25 where
26 Self: Sized,
27 {
28 Ok(())
29 }
30}
31
32#[derive(Error, Debug)]
33pub enum YieldOpVerifyErr {
34 #[error("YieldOp operand types do not match parent operation result types")]
35 OperandTypeMismatch,
36 #[error("YieldOp must have a parent operation to verify against")]
37 MissingParentOp,
38}
39
40#[pliron_op(name = "branch.yield", format = "`(` operands(CharSpace(`,`)) `)`")]
41#[op_interfaces(IsTerminatorInterface)]
42#[op_traits(CanMaterialize, NoMemoryEffect)]
43pub struct YieldOp;
44
45impl YieldOp {
46 pub fn new(ctx: &mut Context) -> Self {
47 let op = Operation::new(ctx, Self::get_concrete_op_info(), vec![], vec![], vec![], 0);
48 Self { op }
49 }
50
51 pub fn yield_values(&self, ctx: &Context) -> Vec<Value> {
52 self.get_operation().operands(ctx)
53 }
54}
55
56impl Verify for YieldOp {
57 fn verify(&self, ctx: &Context) -> pliron::result::Result<()> {
58 let Some(parent_op) = self.get_operation().deref(ctx).get_parent_op(ctx) else {
59 return verify_err!(self.loc(ctx), YieldOpVerifyErr::MissingParentOp);
60 };
61
62 let expected_types: Vec<_> = parent_op
63 .deref(ctx)
64 .results()
65 .map(|r| r.get_type(ctx))
66 .collect();
67 let actual_types: Vec<_> = self
68 .get_operation()
69 .deref(ctx)
70 .operands()
71 .map(|o| o.get_type(ctx))
72 .collect();
73
74 if expected_types != actual_types {
75 return verify_err!(self.loc(ctx), YieldOpVerifyErr::OperandTypeMismatch);
76 }
77
78 Ok(())
79 }
80}
81
82#[pliron_op(name = "branch.condition", format = "`(` operands(CharSpace(`,`)) `)`")]
83#[op_interfaces(IsTerminatorInterface, OperandNOfType<0, BoolType>)]
84#[op_traits(CanMaterialize, NoMemoryEffect)]
85pub struct ConditionOp;
86
87impl ConditionOp {
88 pub fn new(ctx: &mut Context, cond: Value) -> Self {
89 let op = Operation::new(
90 ctx,
91 Self::get_concrete_op_info(),
92 vec![],
93 vec![cond],
94 vec![],
95 0,
96 );
97 Self { op }
98 }
99
100 pub fn condition(&self, ctx: &Context) -> Value {
101 self.get_operation().operand(ctx, 0)
102 }
103
104 pub fn forward_values(&self, ctx: &Context) -> Vec<Value> {
105 self.get_operation().deref(ctx).operands().skip(1).collect()
106 }
107}
108
109impl Verify for ConditionOp {
110 fn verify(&self, ctx: &Context) -> pliron::result::Result<()> {
111 let Some(parent_op) = self.get_operation().deref(ctx).get_parent_op(ctx) else {
112 return verify_err!(self.loc(ctx), YieldOpVerifyErr::MissingParentOp);
113 };
114
115 let expected_types: Vec<_> = parent_op
116 .deref(ctx)
117 .results()
118 .map(|r| r.get_type(ctx))
119 .collect();
120 let actual_types: Vec<_> = self
121 .forward_values(ctx)
122 .into_iter()
123 .map(|o| o.get_type(ctx))
124 .collect();
125
126 if expected_types != actual_types {
127 return verify_err!(self.loc(ctx), YieldOpVerifyErr::OperandTypeMismatch);
128 }
129
130 Ok(())
131 }
132}
133
134#[pliron_op(
135 name = "branch.return",
136 format = "operands(CharSpace(`,`))",
137 verifier = "succ"
138)]
139#[op_interfaces(IsTerminatorInterface, IsExitTerminator)]
140#[op_traits(CanMaterialize, NoMemoryEffect)]
141pub struct ReturnOp;
142
143impl ReturnOp {
144 pub fn new(ctx: &mut Context) -> Self {
145 let op = Operation::new(ctx, Self::get_concrete_op_info(), vec![], vec![], vec![], 0);
146 Self { op }
147 }
148
149 pub fn new_with_value(ctx: &mut Context, value: Value) -> Self {
150 let op = Operation::new(
151 ctx,
152 Self::get_concrete_op_info(),
153 vec![],
154 vec![value],
155 vec![],
156 0,
157 );
158 Self { op }
159 }
160
161 pub fn value(&self, ctx: &Context) -> Option<Value> {
162 self.get_operation().deref(ctx).results().next()
163 }
164}
165
166#[pliron_op(name = "branch.unreachable", format = "", verifier = "succ")]
167#[op_interfaces(IsTerminatorInterface, IsExitTerminator)]
168#[op_traits(CanMaterialize, NoMemoryEffect)]
169pub struct UnreachableOp;
170
171impl UnreachableOp {
172 pub fn new(ctx: &mut Context) -> Self {
173 let op = Operation::new(ctx, Self::get_concrete_op_info(), vec![], vec![], vec![], 0);
174 Self { op }
175 }
176}
177
178#[pliron_op(name = "branch.dead_region", format = "region($0)", verifier = "succ")]
182#[op_interfaces(NOpdsInterface<0>, OneResultInterface, OneRegionInterface, SingleBlockRegionInterface)]
183#[op_traits(Pure)]
184pub struct DeadRegionOp;
185
186impl DeadRegionOp {
187 pub fn new(ctx: &mut Context) -> Self {
188 let op = Operation::new(ctx, Self::get_concrete_op_info(), vec![], vec![], vec![], 1);
189
190 let region = op.deref_mut(ctx).get_region(0);
191 let body = BasicBlock::new(ctx, None, vec![]);
192 body.insert_at_front(region, ctx);
193
194 Self { op }
195 }
196
197 pub fn region(&self, ctx: &Context) -> Ptr<Region> {
198 self.get_operation().deref(ctx).get_region(0)
199 }
200}
201
202pub(super) fn block_side_effects(ctx: &Context, block: Ptr<BasicBlock>) -> bool {
203 block.deref(ctx).iter(ctx).any(|op| {
204 if op.is_op::<YieldOp>(ctx) {
207 return false;
208 }
209 match op_cast::<dyn SideEffects>(&*op.dyn_op(ctx)) {
210 Some(side_effects) => side_effects.has_side_effects(ctx),
211 None => true,
212 }
213 })
214}
215
216#[pliron_op(
217 name = "branch.if",
218 format = "$0 ` then ` region($0) ` else ` region($1)",
219 verifier = "succ"
220)]
221#[op_interfaces(NOpdsInterface<1>, NResultsInterface<0>, NRegionsInterface<2>, SingleBlockRegionInterface, OperandNOfType<0, BoolType>)]
222pub struct IfOp;
223
224impl IfOp {
225 pub fn new(ctx: &mut Context, cond: Value) -> Self {
226 let op = Operation::new(
227 ctx,
228 Self::get_concrete_op_info(),
229 vec![],
230 vec![cond],
231 vec![],
232 2,
233 );
234
235 let then_region = op.deref_mut(ctx).get_region(0);
236 let then_body = BasicBlock::new(ctx, Some("then".try_into().unwrap()), vec![]);
237 then_body.insert_at_front(then_region, ctx);
238
239 let else_region = op.deref_mut(ctx).get_region(1);
240 let else_body = BasicBlock::new(ctx, Some("else".try_into().unwrap()), vec![]);
241 else_body.insert_at_front(else_region, ctx);
242
243 Self { op }
244 }
245
246 pub fn condition(&self, ctx: &Context) -> Value {
247 self.get_operation().deref(ctx).get_operand(0)
248 }
249
250 pub fn then_region(&self, ctx: &Context) -> Ptr<Region> {
251 self.get_operation().deref(ctx).get_region(0)
252 }
253
254 pub fn then_block(&self, ctx: &Context) -> Ptr<BasicBlock> {
255 self.get_body(ctx, 0)
256 }
257
258 pub fn else_region(&self, ctx: &Context) -> Ptr<Region> {
259 self.get_operation().deref(ctx).get_region(1)
260 }
261
262 pub fn else_block(&self, ctx: &Context) -> Ptr<BasicBlock> {
263 self.get_body(ctx, 1)
264 }
265}
266
267#[op_interface_impl]
268impl ConstFoldInterface for IfOp {
269 fn check_fold(
270 &self,
271 _ctx: &Context,
272 operand_attrs: &[Option<AttrObj>],
273 ) -> Vec<Option<AttrObj>> {
274 operand_attrs.to_vec()
275 }
276
277 fn fold_in_place(
278 &self,
279 ctx: &mut Context,
280 operand_attrs: &[Option<AttrObj>],
281 rewriter: &mut dyn Rewriter,
282 ) -> IRStatus {
283 let op = self.get_operation();
284 let Some(attr) = operand_attrs[0].as_ref() else {
285 return IRStatus::Unchanged;
286 };
287 let zero = attr.downcast_ref::<ZeroAttr>().map(|_| false);
288 let bool = attr.downcast_ref::<BoolAttr>().map(|it| it.0);
289 let Some(const_cond) = zero.or(bool) else {
290 return IRStatus::Unchanged;
291 };
292 let (taken, not_taken) = match const_cond {
293 true => (self.then_block(ctx), self.else_block(ctx)),
294 false => (self.else_block(ctx), self.then_block(ctx)),
295 };
296
297 let not_taken_op = DeadRegionOp::new(ctx);
298 let dead_block = not_taken_op.get_body(ctx, 0);
299 rewriter.append_op(ctx, ¬_taken_op);
300
301 inline_block(ctx, rewriter, taken, OpInsertionPoint::BeforeOperation(op));
302 inline_block(
303 ctx,
304 rewriter,
305 not_taken,
306 OpInsertionPoint::AtBlockStart(dead_block),
307 );
308
309 IRStatus::Changed
310 }
311}
312
313fn inline_block(
314 ctx: &Context,
315 rewriter: &mut dyn Rewriter,
316 block: Ptr<BasicBlock>,
317 insertion_point: OpInsertionPoint,
318) {
319 let ops = block.deref(ctx).iter(ctx).collect::<Vec<_>>();
320 let mut insertion_pt = insertion_point;
321 for op in ops {
322 if !op.is_terminator(ctx) {
323 rewriter.move_operation(ctx, op, insertion_pt);
324 insertion_pt = OpInsertionPoint::AfterOperation(op);
325 }
326 }
327}
328
329#[op_interface_impl]
330impl SideEffects for IfOp {
331 fn has_side_effects(&self, ctx: &Context) -> bool {
332 block_side_effects(ctx, self.then_block(ctx))
333 || block_side_effects(ctx, self.else_block(ctx))
334 }
335}
336
337#[pliron_op(
338 name = "branch.switch",
339 format,
340 attributes = (branch_switch_cases: VecAttr),
341 verifier = "succ"
342)]
343#[op_interfaces(NOpdsInterface<1>, NResultsInterface<0>, SingleBlockRegionInterface)]
344pub struct SwitchOp;
345
346impl SwitchOp {
347 pub fn new(ctx: &mut Context, value: Value) -> Self {
348 let op = Operation::new(
349 ctx,
350 Self::get_concrete_op_info(),
351 vec![],
352 vec![value],
353 vec![],
354 1,
355 );
356
357 let default_region = op.deref_mut(ctx).get_region(0);
358 let default_body = BasicBlock::new(ctx, Some("default".try_into().unwrap()), vec![]);
359 default_body.insert_at_front(default_region, ctx);
360
361 Self { op }
362 }
363
364 pub fn value(&self, ctx: &Context) -> Value {
365 self.get_operation().deref(ctx).get_operand(0)
366 }
367
368 pub fn default_region(&self, ctx: &Context) -> Ptr<Region> {
369 self.get_operation().deref(ctx).get_region(0)
370 }
371
372 pub fn default_block(&self, ctx: &Context) -> Ptr<BasicBlock> {
373 self.get_body(ctx, 0)
374 }
375
376 pub fn append_case_block(&self, ctx: &mut Context) -> Ptr<BasicBlock> {
377 let region = Operation::add_region(self.get_operation(), ctx);
378 let body = BasicBlock::new(ctx, None, vec![]);
379 body.insert_at_front(region, ctx);
380 region.deref(ctx).get_head().unwrap()
381 }
382
383 pub fn cases(&self, ctx: &Context) -> Vec<(IntegerAttr, Ptr<BasicBlock>)> {
384 let cases = self.get_attr_branch_switch_cases(ctx).unwrap().clone().0;
385 let out = (0..cases.len()).map(|i| {
386 let value = cases[i].downcast_ref::<IntegerAttr>().unwrap().clone();
387 let block = self.get_body(ctx, i + 1);
388 (value, block)
389 });
390 out.collect()
391 }
392
393 pub fn get_case_destinations(&self, ctx: &Context) -> Vec<Ptr<BasicBlock>> {
394 let op = self.get_operation().deref(ctx);
395 (1..op.regions().count())
396 .map(|i| self.get_body(ctx, i))
397 .collect()
398 }
399
400 pub fn set_attr_cases(&self, ctx: &Context, cases: impl IntoIterator<Item = AttrObj>) {
401 self.set_attr_branch_switch_cases(ctx, VecAttr(cases.into_iter().collect()));
402 }
403}
404
405#[pliron_op(name = "branch.range_loop", format, verifier = "succ")]
406#[op_interfaces(NResultsInterface<0>, OneRegionInterface, SingleBlockRegionInterface, SameOperandsType)]
407pub struct RangeLoopOp;
408
409impl RangeLoopOp {
410 pub fn new(ctx: &mut Context, start: Value, end: Value, step: Value) -> Self {
411 let iter_ty = start.get_type(ctx);
412 let op = Operation::new(
413 ctx,
414 Self::get_concrete_op_info(),
415 vec![],
416 vec![start, end, step],
417 vec![],
418 1,
419 );
420
421 let body_region = op.deref_mut(ctx).get_region(0);
422 let body = BasicBlock::new(ctx, Some("body".try_into().unwrap()), vec![iter_ty]);
423 body.insert_at_front(body_region, ctx);
424
425 Self { op }
426 }
427
428 pub fn iter_var(&self, ctx: &Context) -> Value {
429 self.loop_body(ctx).deref(ctx).get_argument(0)
430 }
431
432 pub fn start(&self, ctx: &Context) -> Value {
433 self.get_operation().deref(ctx).get_operand(0)
434 }
435
436 pub fn end(&self, ctx: &Context) -> Value {
437 self.get_operation().deref(ctx).get_operand(1)
438 }
439
440 pub fn step(&self, ctx: &Context) -> Value {
441 self.get_operation().deref(ctx).get_operand(2)
442 }
443
444 pub fn loop_region(&self, ctx: &Context) -> Ptr<Region> {
445 self.get_operation().deref(ctx).get_region(0)
446 }
447
448 pub fn loop_body(&self, ctx: &Context) -> Ptr<BasicBlock> {
449 self.get_body(ctx, 0)
450 }
451}
452
453#[pliron_op(
454 name = "branch.while",
455 format = "`while ` region($0) ` do ` region($1)",
456 verifier = "succ"
457)]
458#[op_interfaces(
459 NResultsInterface<0>,
460 NRegionsInterface<2>,
461 SingleBlockRegionInterface
462)]
463pub struct WhileOp;
464
465impl WhileOp {
466 pub fn new(ctx: &mut Context) -> Self {
467 let op = Operation::new(ctx, Self::get_concrete_op_info(), vec![], vec![], vec![], 2);
468
469 let before_region = op.deref_mut(ctx).get_region(0);
470 let before = BasicBlock::new(ctx, Some("before".try_into().unwrap()), vec![]);
471 before.insert_at_front(before_region, ctx);
472
473 let after_region = op.deref_mut(ctx).get_region(1);
474 let after = BasicBlock::new(ctx, Some("after".try_into().unwrap()), vec![]);
475 after.insert_at_front(after_region, ctx);
476
477 Self { op }
478 }
479
480 pub fn before_region(&self, ctx: &Context) -> Ptr<Region> {
481 self.get_region_i(ctx, I::<0>.into())
482 }
483
484 pub fn before_block(&self, ctx: &Context) -> Ptr<BasicBlock> {
485 self.get_body(ctx, 0)
486 }
487
488 pub fn after_region(&self, ctx: &Context) -> Ptr<Region> {
489 self.get_region_i(ctx, I::<1>.into())
490 }
491
492 pub fn after_block(&self, ctx: &Context) -> Ptr<BasicBlock> {
493 self.get_body(ctx, 1)
494 }
495}