IR basic block. Contains a sequence of Ops and ends with a ControlOp (Goto, Branch, Return or Unreachable). Only the last op can be a ControlOp. All generated Ops live in basic blocks. Basic blocks determine the order of evaluation and control flow within a function. A basic
| 69 | |
| 70 | @final |
| 71 | class BasicBlock: |
| 72 | """IR basic block. |
| 73 | |
| 74 | Contains a sequence of Ops and ends with a ControlOp (Goto, |
| 75 | Branch, Return or Unreachable). Only the last op can be a |
| 76 | ControlOp. |
| 77 | |
| 78 | All generated Ops live in basic blocks. Basic blocks determine the |
| 79 | order of evaluation and control flow within a function. A basic |
| 80 | block is always associated with a single function/method (FuncIR). |
| 81 | |
| 82 | When building the IR, ops that raise exceptions can be included in |
| 83 | the middle of a basic block, but the exceptions aren't checked. |
| 84 | Afterwards we perform a transform that inserts explicit checks for |
| 85 | all error conditions and splits basic blocks accordingly to preserve |
| 86 | the invariant that a jump, branch or return can only ever appear |
| 87 | as the final op in a block. Manually inserting error checking ops |
| 88 | would be boring and error-prone. |
| 89 | |
| 90 | BasicBlocks have an error_handler attribute that determines where |
| 91 | to jump if an error occurs. If none is specified, an error will |
| 92 | propagate up out of the function. This is compiled away by the |
| 93 | `exceptions` module. |
| 94 | |
| 95 | Block labels are used for pretty printing and emitting C code, and |
| 96 | get filled in by those passes. |
| 97 | |
| 98 | Ops that may terminate the program aren't treated as exits. |
| 99 | """ |
| 100 | |
| 101 | def __init__(self, label: int = -1) -> None: |
| 102 | self.label = label |
| 103 | self.ops: list[Op] = [] |
| 104 | self.error_handler: BasicBlock | None = None |
| 105 | self.referenced = False |
| 106 | |
| 107 | @property |
| 108 | def terminated(self) -> bool: |
| 109 | """Does the block end with a jump, branch or return? |
| 110 | |
| 111 | This should always be true after the basic block has been fully built, but |
| 112 | this is false during construction. |
| 113 | """ |
| 114 | return bool(self.ops) and isinstance(self.ops[-1], ControlOp) |
| 115 | |
| 116 | @property |
| 117 | def terminator(self) -> ControlOp: |
| 118 | """The terminator operation of the block.""" |
| 119 | assert bool(self.ops) and isinstance(self.ops[-1], ControlOp) |
| 120 | return self.ops[-1] |
| 121 | |
| 122 | |
| 123 | # Never generates an exception |
no outgoing calls
searching dependent graphs…