Compilation 2014 Basic Blocks Aslan Askarov aslan@cs.au.dk Revised from slides by E. Ernst
IR = Intermediate … • IR does not match either end perfectly • Source: • Translation non-trivial, e.g., using the static link • Target: • ESEQ & CALL enable side-e ff ects in expressions • CJUMP has 2 targets, machine instructions fall through • CALL puts returned value in specific register (RV) • Why not just drop them? • CALL in expression: needed for function • ESEQ: very convenient • Strategy: Remove ESEQ, move CALL
Idea: Require ‘well-formed’ IR Trees Type Correct MOVE(TEMP t, ESEQ…) ESEQ(MOVE…, TEMP t) Satisfies Constraints MOVE(MEM…,CALL…) SEQ(MEM…,SEQ(…,SEQ…)) MOVE(BINOP…, TEMP s) Only a subset of type correct terms used
Canonical IR Trees • Just ordinary IR trees, but well-formed • Requirements: • SEQ only topmost (will be removed using exp list) • ESEQ never used • Parent of CALL is EXP(…) or MOVE(TEMP t, …) • Created in module ‘Canon’ signature CANON = sig val linearize: stm -> stm list val basicBlocks: stm list -> (stm list list * Temp.label) val traceSchedule: stm list list Temp.label -> stm list end structure Canon: CANON = struct ... end
Technique: Rewriting • Goal of linearize achieved by repeated rewrite • Rewriting rules: • Specify a ‘from’ pattern, to be matched • Specify a ‘to’ pattern, to construct from match • Correctness requirement: • Every possible rewrite preserves the semantics one replaced IR tree by another will be IR tree
Rewriting 1 • Purpose: Eliminate one ESEQ node • Matching: For given IR tree matching concrete nodes, bind subtrees to metavariables • Construction: replace metavariables by their values ESEQ ESEQ s1 ESEQ SEQ e s2 e s1 s2
Rewriting 2 • Purpose: Move ESEQ up BINOP ESEQ op ESEQ e2 s BINOP s e1 op e1 e2 MEM(ESEQ(s,e1)) ESEQ(s,MEM(e1)) JUMP(ESEQ(s,e1)) ESEQ(s,JUMP(e1)) CJUMP(op,ESEQ(s,e1) SEQ(s,CJUMP(op,e1,e2 ,e2,l1,l2) ,l1,l2))
Rewriting 3 • Purpose: Pull ESEQ over operand ESEQ MOVE ESEQ BINOP TEMP e1 s BINOP op e1 ESEQ e2 t op TEMP s e2 t SEQ(MOVE(TEMP t,e1) CJUMP(op,e1 ,SEQ(s ,ESEQ(s,e2) ,CJUMP(op,TEMP t ,l1,l2) ,e2,l1,l2)
Rewriting 4 • Purpose: Pull ESEQ over operand for free! ESEQ BINOP s,e1 commute s BINOP op e1 ESEQ op e2 e1 s e2 s,e1 commute CJUMP(op,e1 SEQ(s,CJUMP(op,e1,e2 ,ESEQ(s,e2) ,l1,l2)) ,l1,l2)
Algorithm Doing the Rewriting • Functions do… handle deconstruct/reconstruct • Functions reorder… perform subtree transforms val reorderStm: exp list * (exp list -> stm) -> stm val reorderExp: exp list * (exp list -> exp) -> (stm*exp) fun doStm (T.JUMP(e,labs)) = reorderStm ([e], fn [e] => T.JUMP(e,labs)) | doStm (T.CJUMP(p,a,b,t,f)) = reorderStm ([a,b], fn [a,b] => T.CJUMP(p,a,b,t,f)) | doStm (T.MOVE(T.TEMP t, b)) = reorderStm ([b], fn [b] => T.MOVE(T.TEMP t, b)) ... and doExp (T.BINOP(p,a,b)) = reorderExp ([a,b], fn [a,b] => T.BINOP(p,a,b)) | doExp (T.MEM(a)) = reorderExp ([a], fn [a] => T.MEM(a)) ...
On CALL • Problem: A CALL returns result in register RV • Why does CALL(f, CALL(…),CALL(…)) not work? • (unless we are careful) ESEQ(MOVE(TEMP t CALL(f,args)) ,CALL(f,args)) ,TEMP t) • Why does this solve the problem?
After Rewriting 1-4 Stabilizes • Eliminate SEQ: • First rewrite SEQ to enforce list structure SEQ(SEQ(a,b),c)) SEQ(a,SEQ(b,c)) • Then replace SEQ by list constructor SEQ(a,SEQ(b,c)) a::(b::c) fun linearize (stm0: stm): stm list = let definitions of reorderExp, reorderStm, doExp, … fun linear (T.SEQ(a,b),l) = linear(a,linear(b,l)) | linear (s,l) = s::l in linear(doStm stm0, nil) end
Basic Blocks • Control flow: Studying program behavior with no regard to values, just “movement” (*JUMP , step) • Basic block: Sequence of instructions w/o JUMP • First statement: LABEL • Last statement: [C]JUMP • No other LABELs or [C]JUMPs • Simple algorithm: • at [C]JUMP: end current block; at LABEL: start new block • fixup: add ‘blocks’ label at very beginning, JUMP to ‘done’ label at very end • Result: basic blocks can be freely reordered
Traces • Trace: instruction sequence that could be executed consecutively (choice: CJUMP) • We reorder such that CJUMP is followed by its ‘false’ label, thus enabling fall through • Pseudo-code algorithm: Put all blocks of the program into a list Q. while Q is not empty Start a new (empty) trace, call it T Remove the head element b from Q. while b is not marked Mark b; append b to the end of the current trace T. Examine the successors of b; if there is any unmarked successor c b := c end current trace T.
Traces May be Optimized • All these are correct tracings for the same function prologue statements prologue statements prologue statements JUMP (NAME test) JUMP (NAME test) JUMP (NAME test) LABEL test LABEL test LABEL body CJUMP (>,i,N,done,body) CJUMP(<=,i,N,done,body) loop body statements LABEL body LABEL done JUMP(NAME test) loop body statements epilogue statements LABEL test JUMP(NAME test) LABEL body CJUMP (>,i,N,done,body) LABEL done loop body statements LABEL done epilogue statements JUMP(NAME test) epilogue statements • Count instructions for the loop! • Optimal traces: not this compiler
Implementation • File ‘canon.sml’ available, fully implemented • Has signature CANON (including linearize, basicBlocks, traceSchedule) • Note warnings during compilation: canon.sml:81.30-81.54 Warning: match nonexhaustive e :: nil => ... canon.sml:83.32-83.62 Warning: match nonexhaustive a :: b :: nil => ... • Not a problem! ;-) Caused by using well-formed subset of type correct trees, carefully..
Summary • IR trees really intermediate: Not a perfect fit for source, nor for target • For target: Eliminate ESEQ & SEQ, move CALL, ensure parent EXP(…) or MOVE(TEMP t, …) • Transformations: Move ESEQ up, eliminate an ESEQ, pull ESEQ over expression, ditto for free • Tricky algorithm: note deconstruct/reconstruct • Protect register RV: Transform CALL • Move CALL up to EXP/MOVE • Basic blocks: find, then reorder into traces
Recommend
More recommend