Skip to main content

sway_ir/
block.rs

1//! Represents a 'basic block' of [`Instruction`]s in a control flow graph.
2//!
3//! [`Block`]s contain zero or more _non-terminating_ instructions and at most one _terminating_
4//! instruction or _terminator_.  Terminators are either branches or a return instruction and are
5//! the last instruction in the block.
6//!
7//! Blocks also contain a single 'phi' instruction at its start.  In
8//! [SSA](https://en.wikipedia.org/wiki/Static_single_assignment_form) form 'phi' instructions are
9//! used to merge values from preceding blocks.
10//!
11//! Every [`Function`] has at least one block, the first of which is usually labeled `entry`.
12
13use rustc_hash::{FxHashMap, FxHashSet};
14
15use crate::{
16    context::Context,
17    error::IrError,
18    function::Function,
19    instruction::{FuelVmInstruction, InstOp},
20    value::{Value, ValueDatum},
21    BranchToWithArgs, DebugWithContext, Instruction, InstructionInserter, InstructionIterator,
22    Module, Type,
23};
24
25/// A wrapper around an [ECS](https://github.com/orlp/slotmap) handle into the
26/// [`Context`].
27#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, DebugWithContext)]
28pub struct Block(pub slotmap::DefaultKey);
29
30#[doc(hidden)]
31pub struct BlockContent {
32    /// Block label, useful for printing.
33    pub label: Label,
34    /// The function containing this block.
35    pub function: Function,
36    /// List of instructions in the block.
37    pub(crate) instructions: Vec<Value>,
38    /// Block arguments: Another form of SSA PHIs.
39    pub args: Vec<Value>,
40    /// CFG predecessors
41    pub preds: FxHashSet<Block>,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, DebugWithContext)]
45pub struct BlockArgument {
46    /// The block of which this is an argument.
47    pub block: Block,
48    /// idx'th argument of the block.
49    pub idx: usize,
50    pub ty: Type,
51    /// Is this known to be immutable?
52    pub is_immutable: bool,
53}
54
55impl BlockArgument {
56    /// Get the actual parameter passed to this block argument from `from_block`.
57    pub fn get_val_coming_from(&self, context: &Context, from_block: &Block) -> Option<Value> {
58        for BranchToWithArgs {
59            block: succ_block,
60            args,
61        } in from_block.successors(context)
62        {
63            if succ_block == self.block {
64                return Some(args[self.idx]);
65            }
66        }
67        None
68    }
69
70    /// Get the actual parameters passed to this block argument from any of its block predecessors.
71    ///
72    /// Returns the predecessor block the parameter is coming from and the parameter.
73    pub fn get_vals_coming_from<'a>(
74        &'a self,
75        context: &'a Context,
76    ) -> impl Iterator<Item = (Block, Value)> + 'a {
77        self.block.pred_iter(context).flat_map(move |pred| {
78            self.get_val_coming_from(context, pred)
79                .map(|value| (*pred, value))
80        })
81    }
82
83    /// Get the [Value] that this argument represents.
84    pub fn as_value(&self, context: &Context) -> Value {
85        self.block.get_arg(context, self.idx).unwrap()
86    }
87}
88
89/// Each block may be explicitly named.  A [`Label`] is a simple `String` synonym.
90pub type Label = String;
91
92impl Block {
93    /// Return a new block handle.
94    ///
95    /// Creates a new Block belonging to `function` in the context and returns its handle.  `label`
96    /// is optional and is used only when printing the IR.
97    pub fn new(context: &mut Context, function: Function, label: Option<String>) -> Block {
98        let label = function.get_unique_label(context, label);
99        let content = BlockContent {
100            label,
101            function,
102            instructions: vec![],
103            args: vec![],
104            preds: FxHashSet::default(),
105        };
106        Block(context.blocks.insert(content))
107    }
108
109    /// Get the parent module for this block.
110    pub fn get_module(&self, context: &Context) -> Module {
111        let f = context.blocks[self.0].function;
112        context.functions[f.0].module
113    }
114
115    /// Get the parent function for this block.
116    pub fn get_function(&self, context: &Context) -> Function {
117        context.blocks[self.0].function
118    }
119
120    /// Create a new [`InstructionInserter`] to more easily append instructions to this block.
121    pub fn append<'a, 'eng>(
122        &self,
123        context: &'a mut Context<'eng>,
124    ) -> InstructionInserter<'a, 'eng> {
125        InstructionInserter::new(context, *self, crate::InsertionPosition::End)
126    }
127
128    /// Get the label of this block.  If it wasn't given one upon creation it will be a generated
129    /// label.
130    pub fn get_label(&self, context: &Context) -> String {
131        context.blocks[self.0].label.clone()
132    }
133
134    /// Set the label of this block.  If the label isn't unique it will be made so.
135    pub fn set_label(&self, context: &mut Context, new_label: Option<Label>) {
136        let unique_label = self
137            .get_function(context)
138            .get_unique_label(context, new_label);
139        context.blocks[self.0].label = unique_label;
140    }
141
142    /// Get the number of instructions in this block.
143    pub fn num_instructions(&self, context: &Context) -> usize {
144        context.blocks[self.0].instructions.len()
145    }
146
147    /// Get the i'th block arg.
148    pub fn get_arg(&self, context: &Context, index: usize) -> Option<Value> {
149        context.blocks[self.0].args.get(index).cloned()
150    }
151
152    /// Get the number of predecessor blocks, i.e., blocks which branch to this one.
153    pub fn num_predecessors(&self, context: &Context) -> usize {
154        context.blocks[self.0].preds.len()
155    }
156
157    /// Add a new block argument of type `ty`. Returns its index.
158    pub fn new_arg(&self, context: &mut Context, ty: Type) -> usize {
159        let idx = context.blocks[self.0].args.len();
160        let arg_val = Value::new_argument(
161            context,
162            BlockArgument {
163                block: *self,
164                idx,
165                ty,
166                is_immutable: false,
167            },
168        );
169        context.blocks[self.0].args.push(arg_val);
170        idx
171    }
172
173    pub fn set_arg(&self, context: &mut Context, arg: Value) {
174        match context.values[arg.0].value {
175            ValueDatum::Argument(BlockArgument {
176                block,
177                idx,
178                ty: _,
179                is_immutable: _,
180            }) if block == *self && idx < context.blocks[self.0].args.len() => {
181                context.blocks[self.0].args[idx] = arg;
182            }
183            _ => panic!("Inconsistent block argument being set"),
184        }
185    }
186
187    /// Add a block argument, asserts that `arg` is suitable here.
188    pub fn add_arg(&self, context: &mut Context, arg: Value) {
189        match context.values[arg.0].value {
190            ValueDatum::Argument(BlockArgument {
191                block,
192                idx,
193                ty: _,
194                is_immutable: _,
195            }) if block == *self && idx == context.blocks[self.0].args.len() => {
196                context.blocks[self.0].args.push(arg);
197            }
198            _ => panic!("Inconsistent block argument being added"),
199        }
200    }
201
202    /// Get an iterator over this block's args.
203    pub fn arg_iter<'a>(&'a self, context: &'a Context) -> impl Iterator<Item = &'a Value> {
204        context.blocks[self.0].args.iter()
205    }
206
207    /// How many args does this block have?
208    pub fn num_args(&self, context: &Context) -> usize {
209        context.blocks[self.0].args.len()
210    }
211
212    /// Get an iterator over this block's predecessor blocks.
213    pub fn pred_iter<'a>(&'a self, context: &'a Context) -> impl Iterator<Item = &'a Block> {
214        context.blocks[self.0].preds.iter()
215    }
216
217    /// Add `from_block` to the set of predecessors of this block.
218    pub fn add_pred(&self, context: &mut Context, from_block: &Block) {
219        context.blocks[self.0].preds.insert(*from_block);
220    }
221
222    /// Remove `from_block` from the set of predecessors of this block.
223    pub fn remove_pred(&self, context: &mut Context, from_block: &Block) {
224        context.blocks[self.0].preds.remove(from_block);
225    }
226
227    /// Replace a `old_source` with `new_source` as a predecessor.
228    pub fn replace_pred(&self, context: &mut Context, old_source: &Block, new_source: &Block) {
229        self.remove_pred(context, old_source);
230        self.add_pred(context, new_source);
231    }
232
233    /// Get instruction at position `pos`.
234    ///
235    /// Returns `None` if block is empty or if `pos` is out of bounds.
236    pub fn get_instruction_at(&self, context: &Context, pos: usize) -> Option<Value> {
237        context.blocks[self.0].instructions.get(pos).cloned()
238    }
239
240    /// Get a reference to the final instruction in the block, provided it is a terminator.
241    ///
242    /// Returns `None` if the final instruction is not a terminator. This can only happen during IR
243    /// generation when the block is still being populated.
244    pub fn get_terminator<'a>(&self, context: &'a Context) -> Option<&'a Instruction> {
245        context.blocks[self.0].instructions.last().and_then(|val| {
246            // It's guaranteed to be an instruction value.
247            if let ValueDatum::Instruction(term_inst) = &context.values[val.0].value {
248                if term_inst.op.is_terminator() {
249                    Some(term_inst)
250                } else {
251                    None
252                }
253            } else {
254                None
255            }
256        })
257    }
258
259    /// Get a mutable reference to the final instruction in the block, provided it is a terminator.
260    ///
261    /// Returns `None` if the final instruction is not a terminator. This can only happen during IR
262    /// generation when the block is still being populated.
263    pub fn get_terminator_mut<'a>(&self, context: &'a mut Context) -> Option<&'a mut Instruction> {
264        context.blocks[self.0].instructions.last().and_then(|val| {
265            // It's guaranteed to be an instruction value.
266            if let ValueDatum::Instruction(term_inst) = &mut context.values[val.0].value {
267                if term_inst.op.is_terminator() {
268                    Some(term_inst)
269                } else {
270                    None
271                }
272            } else {
273                None
274            }
275        })
276    }
277
278    /// Get the CFG successors (and the parameters passed to them) of this block.
279    pub(super) fn successors<'a>(&'a self, context: &'a Context) -> Vec<BranchToWithArgs> {
280        match self.get_terminator(context) {
281            Some(Instruction {
282                op:
283                    InstOp::ConditionalBranch {
284                        true_block,
285                        false_block,
286                        ..
287                    },
288                ..
289            }) => vec![true_block.clone(), false_block.clone()],
290
291            Some(Instruction {
292                op: InstOp::Branch(block),
293                ..
294            }) => vec![block.clone()],
295
296            _otherwise => Vec::new(),
297        }
298    }
299
300    /// For a particular successor (if it indeed is one), get the arguments passed.
301    pub fn get_succ_params(&self, context: &Context, succ: &Block) -> Vec<Value> {
302        self.successors(context)
303            .iter()
304            .find(|branch| &branch.block == succ)
305            .map_or(vec![], |branch| branch.args.clone())
306    }
307
308    /// For a particular successor (if it indeed is one), get a mut ref to parameters passed.
309    pub fn get_succ_params_mut<'a>(
310        &'a self,
311        context: &'a mut Context,
312        succ: &Block,
313    ) -> Option<&'a mut Vec<Value>> {
314        match self.get_terminator_mut(context) {
315            Some(Instruction {
316                op:
317                    InstOp::ConditionalBranch {
318                        true_block,
319                        false_block,
320                        ..
321                    },
322                ..
323            }) => {
324                if true_block.block == *succ {
325                    Some(&mut true_block.args)
326                } else if false_block.block == *succ {
327                    Some(&mut false_block.args)
328                } else {
329                    None
330                }
331            }
332            Some(Instruction {
333                op: InstOp::Branch(block),
334                ..
335            }) if block.block == *succ => Some(&mut block.args),
336            _ => None,
337        }
338    }
339
340    /// Replace successor `old_succ` with `new_succ`.
341    /// Updates `preds` of both `old_succ` and `new_succ`.
342    pub(super) fn replace_successor(
343        &self,
344        context: &mut Context,
345        old_succ: Block,
346        new_succ: Block,
347        new_params: Vec<Value>,
348    ) {
349        let mut modified = false;
350        if let Some(term) = self.get_terminator_mut(context) {
351            match term {
352                Instruction {
353                    op:
354                        InstOp::ConditionalBranch {
355                            true_block:
356                                BranchToWithArgs {
357                                    block: true_block,
358                                    args: true_opds,
359                                },
360                            false_block:
361                                BranchToWithArgs {
362                                    block: false_block,
363                                    args: false_opds,
364                                },
365                            cond_value: _,
366                        },
367                    ..
368                } => {
369                    if old_succ == *true_block {
370                        modified = true;
371                        *true_block = new_succ;
372                        true_opds.clone_from(&new_params);
373                    }
374                    if old_succ == *false_block {
375                        modified = true;
376                        *false_block = new_succ;
377                        *false_opds = new_params
378                    }
379                }
380
381                Instruction {
382                    op: InstOp::Branch(BranchToWithArgs { block, args }),
383                    ..
384                } if *block == old_succ => {
385                    *block = new_succ;
386                    *args = new_params;
387                    modified = true;
388                }
389                _ => (),
390            }
391        }
392        if modified {
393            old_succ.remove_pred(context, self);
394            new_succ.add_pred(context, self);
395        }
396    }
397
398    /// Return whether this block is already terminated by non-branching instructions,
399    /// means with instructions that cause either revert, or local or context returns.
400    /// Those instructions are: [InstOp::Ret], [FuelVmInstruction::Retd],
401    /// [FuelVmInstruction::JmpMem], and [FuelVmInstruction::Revert]).
402    pub fn is_terminated_by_return_or_revert(&self, context: &Context) -> bool {
403        self.get_terminator(context).is_some_and(|i| {
404            matches!(
405                i,
406                Instruction {
407                    op: InstOp::Ret(..)
408                        | InstOp::FuelVm(
409                            FuelVmInstruction::Revert(..)
410                                | FuelVmInstruction::JmpMem
411                                | FuelVmInstruction::Retd { .. }
412                        ),
413                    ..
414                }
415            )
416        })
417    }
418
419    /// Replace a value within this block.
420    ///
421    /// For every instruction within the block, any reference to `old_val` is replaced with
422    /// `new_val`.
423    pub fn replace_values(&self, context: &mut Context, replace_map: &FxHashMap<Value, Value>) {
424        for ins_idx in 0..context.blocks[self.0].instructions.len() {
425            let ins = context.blocks[self.0].instructions[ins_idx];
426            ins.replace_instruction_values(context, replace_map);
427        }
428    }
429
430    /// Remove an instruction from this block.
431    ///
432    /// **NOTE:** We must be very careful!  We mustn't remove the phi or the terminator.  Some
433    /// extra checks should probably be performed here to avoid corruption! Ideally we use get a
434    /// user/uses system implemented.  Using `Vec::remove()` is also O(n) which we may want to
435    /// avoid someday.
436    pub fn remove_instruction(&self, context: &mut Context, instr_val: Value) {
437        let ins = &mut context.blocks[self.0].instructions;
438        if let Some(pos) = ins.iter().position(|iv| *iv == instr_val) {
439            ins.remove(pos);
440        }
441    }
442
443    /// Remove an instruction at position `pos` from this block.
444    ///
445    /// **NOTE:** We must be very careful!  We mustn't remove the phi or the terminator.  Some
446    /// extra checks should probably be performed here to avoid corruption! Ideally we use get a
447    /// user/uses system implemented.  Using `Vec::remove()` is also O(n) which we may want to
448    /// avoid someday.
449    pub fn remove_instruction_at(&self, context: &mut Context, pos: usize) {
450        context.blocks[self.0].instructions.remove(pos);
451    }
452
453    /// Remove the last instruction in the block.
454    ///
455    /// **NOTE:** The caller must be very careful if removing the terminator.
456    pub fn remove_last_instruction(&self, context: &mut Context) {
457        context.blocks[self.0].instructions.pop();
458    }
459
460    /// Remove instructions from block that satisfy a given predicate.
461    pub fn remove_instructions<T: Fn(Value) -> bool>(&self, context: &mut Context, pred: T) {
462        let ins = &mut context.blocks[self.0].instructions;
463        ins.retain(|value| !pred(*value));
464    }
465
466    /// Clear the current instruction list and take the one provided.
467    pub fn take_body(&self, context: &mut Context, new_insts: Vec<Value>) {
468        let _ = std::mem::replace(&mut (context.blocks[self.0].instructions), new_insts);
469        for inst in &context.blocks[self.0].instructions {
470            let ValueDatum::Instruction(inst) = &mut context.values[inst.0].value else {
471                continue;
472            };
473            inst.parent = *self;
474        }
475    }
476
477    /// Insert instruction(s) at the beginning of the block.
478    pub fn prepend_instructions(&self, context: &mut Context, mut insts: Vec<Value>) {
479        let block_ins = &mut context.blocks[self.0].instructions;
480        insts.append(block_ins);
481        context.blocks[self.0].instructions = insts;
482    }
483
484    /// Replace an instruction in this block with another.  Will return a ValueNotFound on error.
485    /// Any use of the old instruction value will also be replaced by the new value throughout the
486    /// owning function if `replace_uses` is set.
487    pub fn replace_instruction(
488        &self,
489        context: &mut Context,
490        old_instr_val: Value,
491        new_instr_val: Value,
492        replace_uses: bool,
493    ) -> Result<(), IrError> {
494        match context.blocks[self.0]
495            .instructions
496            .iter_mut()
497            .find(|instr_val| *instr_val == &old_instr_val)
498        {
499            None => Err(IrError::ValueNotFound(
500                "Attempting to replace instruction.".to_owned(),
501            )),
502            Some(instr_val) => {
503                *instr_val = new_instr_val;
504                if replace_uses {
505                    self.get_function(context).replace_value(
506                        context,
507                        old_instr_val,
508                        new_instr_val,
509                        Some(*self),
510                    );
511                }
512                Ok(())
513            }
514        }
515    }
516
517    /// Split the block into two.
518    ///
519    /// This will create a new block and move the instructions at and following `split_idx` to it.
520    /// Returns both blocks.
521    pub fn split_at(&self, context: &mut Context, split_idx: usize) -> (Block, Block) {
522        let function = context.blocks[self.0].function;
523        if split_idx == 0 {
524            // We can just create a new empty block and put it before this one.  We know that it
525            // will succeed because self is definitely in the function, so we can unwrap().
526            //
527            // If self is the entry block then for now we need to rename it from 'entry' and call
528            // our new block 'entry'.
529            let new_block_name = (*self == self.get_function(context).get_entry_block(context))
530                .then(|| {
531                    self.set_label(context, None);
532                    "entry".to_owned()
533                });
534            let new_block = function
535                .create_block_before(context, self, new_block_name)
536                .unwrap();
537
538            // Move the block arguments to the new block. We collect because we want to mutate next.
539            #[allow(clippy::needless_collect)]
540            let args: Vec<_> = self.arg_iter(context).copied().collect();
541            for arg in args.into_iter() {
542                match &mut context.values[arg.0].value {
543                    ValueDatum::Argument(BlockArgument {
544                        block,
545                        idx: _,
546                        ty: _,
547                        is_immutable: _,
548                    }) => {
549                        // We modify the Value in place to be a BlockArgument for the new block.
550                        *block = new_block;
551                    }
552                    _ => unreachable!("Block arg value inconsistent"),
553                }
554                new_block.add_arg(context, arg);
555            }
556            context.blocks[self.0].args.clear();
557
558            (new_block, *self)
559        } else {
560            // Again, we know that it will succeed because self is definitely in the function, and
561            // so we can unwrap().
562            let new_block = function.create_block_after(context, self, None).unwrap();
563
564            // Split the instructions at the index and append them to the new block.
565            let mut tail_instructions = context.blocks[self.0].instructions.split_off(split_idx);
566            // Update the parent of tail_instructions.
567            for instr in &tail_instructions {
568                instr.get_instruction_mut(context).unwrap().parent = new_block;
569            }
570            context.blocks[new_block.0]
571                .instructions
572                .append(&mut tail_instructions);
573
574            // If the terminator of the old block (now the new block) was a branch then we need to
575            // update the destination block's preds.
576            //
577            // Copying the candidate blocks and putting them in a vector to avoid borrowing context
578            // as immutable and then mutable in the loop body.
579            for to_block in match new_block.get_terminator(context) {
580                Some(Instruction {
581                    op: InstOp::Branch(to_block),
582                    ..
583                }) => {
584                    vec![to_block.block]
585                }
586                Some(Instruction {
587                    op:
588                        InstOp::ConditionalBranch {
589                            true_block,
590                            false_block,
591                            ..
592                        },
593                    ..
594                }) => {
595                    vec![true_block.block, false_block.block]
596                }
597
598                _ => Vec::new(),
599            } {
600                to_block.replace_pred(context, self, &new_block);
601            }
602
603            (*self, new_block)
604        }
605    }
606
607    /// Return an instruction iterator for each instruction in this block.
608    pub fn instruction_iter(&self, context: &Context) -> InstructionIterator {
609        InstructionIterator::new(context, self)
610    }
611}
612
613/// An iterator over each block in a [`Function`].
614pub struct BlockIterator {
615    blocks: Vec<slotmap::DefaultKey>,
616    next: usize,
617}
618
619impl BlockIterator {
620    /// Return a new iterator for each block in `function`.
621    pub fn new(context: &Context, function: &Function) -> Self {
622        // Copy all the current block indices, so they may be modified in the context during
623        // iteration.
624        BlockIterator {
625            blocks: context.functions[function.0]
626                .blocks
627                .iter()
628                .map(|block| block.0)
629                .collect(),
630            next: 0,
631        }
632    }
633}
634
635impl Iterator for BlockIterator {
636    type Item = Block;
637
638    fn next(&mut self) -> Option<Block> {
639        if self.next < self.blocks.len() {
640            let idx = self.next;
641            self.next += 1;
642            Some(Block(self.blocks[idx]))
643        } else {
644            None
645        }
646    }
647}