instructor: Prof. Dr. Peter Thiemann date: 2024-05-14 title: Chapter 5

Conditionals

Changes to the transformation pipeline

Shrink

  • remove and and or in favor of conditional expression
if not cond: sethen else: seelse
--->
if cond: seelse else: sethen

Transformation to monadic form

output grammar ast_ir_mon

type ExprAtom = EConst | EVar 
type Expr = ExprAtom | EOp1 | EOp2 | EInput | ECompare
type Stmt = SPrint | SAssign | SCond
type Program = IList[Stmt]
  • expression conditional eliminated
  • arguments to EOp1, EOp2, ECompare, and SPrint are atomic
  • expression arguments to SAssign, SCond are not atomic
  • condition of if should always be a comparison

Explication of control (5.7)

input grammar ast_ir_mon output grammar ast_ir_mon_exp

type ExprAtom = EConst | EVar 
type Expr = ExprAtom | EOp1 | EOp2 | EInput | ECompare
type Stmt = SPrint | SAssign | SCond | SGoto
type Block = IList[Stmt]
type Program = dict[Label, Block]

class SCond:
    test: ECompare
    body: Label
    orelse: Label

class SGoto:
    target: Label
  • statement lists in conditional replaced by jump labels (here: Label)
  • New goto statement

Write two functions

def explicate(p: src.Program) -> tuple[tgt.Program, Label, Label]:
def explicate_block(out: tgt.Program, Lentry: Label, Lexit: Label, p: src.Program):
  • explicate creates a program dictionary and entry and exit labels
  • explicate_block
    • traverses a block for a conditional
    • introduces labels for the true branch, the false branch and the continuation (the code following the conditional)
    • recursively invokes explicate_block to translate the true branch and the false branch
    • emits the new SCond and inserts the proper SGotos
    • SCond finishes a block, so we have to start a new block using the continuation label

Roughly:

SCond( test, body, orelse )
... more code ...
-->

SCond( test, Ltrue, Lfalse )

Ltrue:
   code for body
   SGoto Lcont
   
Lfalse:
   code for orelse
   SGoto LCont

Lcont:
   explicate ... more code ...

Instruction Selection (5.8)

  • raise from single block to dict of blocks
  • all conditional statements with comparison have the shape like this if a1 == a2: goto ltrue; else goto lfalse with atoms a1 and a2 so we can generate a conditional branch instruction
  • for reifying the result of a comparison in, say, a boolean variable check the instructions slt and sltu

Register Allocation (5.9)

  • need to update liveness analysis
  • works on control flow graph

Control Flow Graph (CFG)

A directed graph with

  • nodes = basic blocks, i.e., a sequence of instructions starting with a label and ending with a (conditional) jump.

  • edges = from block ending with a jump to L to block starting with label L

  • Each block has either one or two outgoing edges.

  • Each block has one or more incoming edges.

  • One node is labeled entry, the label exit points to the conclusion of the code.

Liveness Analysis

  • currently, the CFG is a DAG

  • for each node labeled L in the CFG, we maintain sets of variables live_after(L) and live_before(L)

  • initially all sets are empty

  • process all nodes in reverse topological order, i.e., the nodes pointing to exit first

  • liveness analysis for node L:

    • set live_after(L) to live_before(L1) U … U live_before(Ln) where L1Ln are the immediate successors of L in the CFG
    • calculate live_before(L) by transforming live_after(L) according to the gen/kill rules of liveness analysis
  • may calculate the interference right away without storing live_after for each individual instruction

Optimize Blocks / Remove Jumps

  • the material of 5.12.1 is not obviously applicable to our approach
  • but the same effect can be achieved using the approach of 5.12.2 or by partially evaluating conditionals earlier in the pipeline

While

control flow graph for summation from 0-5

mainstart
    |
   block5
/    ^     \
|    |      |
block7     block8
            |
		   exit
  • it’s cyclic
  • no traversal in topological order possible
  • set up liveness information as before
  • compute live_before from live_after until no live_before changes.
  • called fixed point iteration

For instance, suppose sum is live in block8. -> sum in live_after(block5) -> sum in live_before(block5) -> live_after(block7) = live_before(block5) which contains sum -> sum in live_before(block7)

  • it’s also correct to compute the interference graph during the fixed point iteration

Suppose LA(ins) is some life after set. Consider instruction ins and LB(ins) the corresponding life before set.

If LA(ins) gets bigger, then LB(ins) stays the same or gets bigger. Because LB(ins) = (LA(ins) \ W(ins)) U R(ins) where W(ins) and R(ins) are constant sets (because the instruction ins is fixed). So technically, this computes a monotone function.