From edc687bfa8e1b6989267b35045112065ed7e68d7 Mon Sep 17 00:00:00 2001 From: dibyendumajumdar Date: Fri, 21 Feb 2025 15:22:10 +0000 Subject: [PATCH] Add test for simple SSA liveness example, also add comments and reorg the liveness calculation a bit for clarity --- .../ezlang/compiler/BasicBlock.java | 5 +- .../ezlang/compiler/Liveness.java | 59 ++++++--- .../ezlang/compiler/TestLiveness.java | 116 ++++++++++++++++++ 3 files changed, 164 insertions(+), 16 deletions(-) diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java index 80c8193..a57fc2b 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/BasicBlock.java @@ -67,11 +67,12 @@ public class BasicBlock { LiveSet liveOut; /** - * Inputs to successor block's phi function + * phiUses(B) is the set of variables used in a phi-operation at entry of a block successor of the block B + * That is, inputs to successor block's phi functions. */ LiveSet phiUses; /** - * Phi definitions in this block + * phiDefs(B) the variables defined by phi-operations at entry of the block B */ LiveSet phiDefs; /** diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Liveness.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Liveness.java index f5b2802..4eb56a3 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Liveness.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/Liveness.java @@ -9,11 +9,15 @@ * pages 446-447. * * It turned out that this dataflow implementation cannot correctly handle - * phis, because with phis, the inputs are live at the predecessor blocks. - * We have to look at alternative approaches when input is SSA form. - * Surprisingly even with this approach, the lost copy and swap problems + * phis. Therefore, we have to look at alternative approaches when input is SSA form. + * Surprisingly even with the flawed approach, the lost copy and swap problems * appeared to work correctly. * + * Phis need special considerations: + * + * Phi inputs are live out at the predecessor blocks, but not live in for phi block. + * Phi def is live in at phi block but not in live out at predecessor blocks. + * * The new approach is based on formula described in * Computing Liveness Sets for SSA-Form Programs * Florian Brandner, Benoit Boissinot, Alain Darte, BenoƮt Dupont de Dinechin, Fabrice Rastello @@ -48,25 +52,29 @@ private void initBlocks(RegisterPool regPool, List blocks) { private void init(List blocks) { for (BasicBlock block : blocks) { - // We st up phiDefs first because when we + // Any vars created by phi instructions are added to phiDefs + // for this block; these vars will be live on entry to block + // but not live out from predecessor blocks. + // + // We set up phiDefs first because when we // look at phi uses we need to refer back here // see comments on phi cycles below for (Instruction instruction : block.instructions) { if (instruction instanceof Instruction.Phi phi) { block.phiDefs.add(phi.value()); } + // There is a scenario where other instructions can appear + // between phi instructions - this happens during the SSA deconstruction + // using Brigg's method. But we don't calculate liveness + // in the middle of that process, so assuming all phis are together + // at the top of the block is okay else break; } for (Instruction instruction : block.instructions) { - for (Register use : instruction.uses()) { - if (!block.varKill.contains(use)) - block.UEVar.add(use); - } - if (instruction.definesVar() && !(instruction instanceof Instruction.Phi)) { - Register def = instruction.def(); - block.varKill.add(def); - } if (instruction instanceof Instruction.Phi phi) { + // Any uses in a Phi are added to the phiUses of predecessor + // block. These uses will be in live out of predecessor block but + // not live in for current block. for (int i = 0; i < block.predecessors.size(); i++) { BasicBlock pred = block.predecessors.get(i); if (!phi.isRegisterInput(i)) @@ -76,11 +84,24 @@ private void init(List blocks) { // if there is loop back and there are cycles // such as e.g. the swap copy problem if (pred == block && - block.phiDefs.contains(use)) + block.phiDefs.contains(use)) continue; pred.phiUses.add(use); } } + else { + // Non phi instructions follow regular + // logic. Any var that is used before being defined + // is added to upward expose set. + for (Register use : instruction.uses()) { + if (!block.varKill.contains(use)) + block.UEVar.add(use); + } + if (instruction.definesVar()) { + Register def = instruction.def(); + block.varKill.add(def); + } + } } } } @@ -89,6 +110,7 @@ private void computeLiveness(List blocks) { boolean changed = true; while (changed) { changed = false; + // TODO we should process in RPO order for (BasicBlock block : blocks) { if (recomputeLiveOut(block)) changed = true; @@ -97,8 +119,17 @@ private void computeLiveness(List blocks) { } // See 'Computing Liveness Sets for SSA-Form Programs' + // // LiveIn(B) = PhiDefs(B) U UpwardExposed(B) U (LiveOut(B) \ Defs(B)) - // LiveOut(B) = U all S (LiveIn(S) \ PhiDefs(S)) U PhiUses(B) + // LiveOut(B) = U all S (LiveIn(S) \ PhiDefs(S)) U PhiUses + // + // For a phi-function a_0 = phi(a_1, ..., a_n ) in block B_0, where a_i comes from block B_i, then: + // * a_0 is considered to be live-in for B_0, but, with respect to this phi-function, it is + // not live-out for B_i, i > 0. + // * a_i, i > 0, is considered to be live-out of B_i , but, with respect to this phi-function, + // it is not live-in for B_0. + // This corresponds to placing a copy of a_i to a_0 on each edge from B_i to B_0. + // private boolean recomputeLiveOut(BasicBlock block) { LiveSet oldLiveOut = block.liveOut.dup(); LiveSet t = block.liveOut.dup().subtract(block.varKill); diff --git a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java index c9c2d27..8e632d7 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestLiveness.java @@ -444,4 +444,120 @@ func foo(p: Int)->Int """, actual); } + @Test + public void testSimpleCase() { + String src = """ + func foo()->Int + { + return 1 && 2 + } + """; + var typeDict = compileSrc(src); + var funcSymbol = typeDict.lookup("foo"); + CompiledFunction func = (CompiledFunction) ((Symbol.FunctionTypeSymbol) funcSymbol).code(); + func.livenessAnalysis(); + StringBuilder result = new StringBuilder(); + result.append("Pre-SSA\n"); + func.toStr(result, true); + new EnterSSA(func, Options.NONE); + func.livenessAnalysis(); + result.append("Post-SSA\n"); + func.toStr(result, true); + Assert.assertEquals(""" +Pre-SSA +func foo()->Int +Reg #0 %t0 +L0: + if 1 goto L2 else goto L3 + #PHIDEFS = {} + #PHIUSES = {} + #UEVAR = {} + #VARKILL = {} + #LIVEIN = {} + #LIVEOUT = {} +L2: + %t0 = 2 + goto L4 + #PHIDEFS = {} + #PHIUSES = {} + #UEVAR = {} + #VARKILL = {0} + #LIVEIN = {} + #LIVEOUT = {0} +L4: + ret %t0 + goto L1 + #PHIDEFS = {} + #PHIUSES = {} + #UEVAR = {0} + #VARKILL = {} + #LIVEIN = {0} + #LIVEOUT = {} +L1: + #PHIDEFS = {} + #PHIUSES = {} + #UEVAR = {} + #VARKILL = {} + #LIVEIN = {} + #LIVEOUT = {} +L3: + %t0 = 0 + goto L4 + #PHIDEFS = {} + #PHIUSES = {} + #UEVAR = {} + #VARKILL = {0} + #LIVEIN = {} + #LIVEOUT = {0} +Post-SSA +func foo()->Int +Reg #0 %t0 +Reg #1 %t0_0 +Reg #2 %t0_1 +Reg #3 %t0_2 +L0: + if 1 goto L2 else goto L3 + #PHIDEFS = {} + #PHIUSES = {} + #UEVAR = {} + #VARKILL = {} + #LIVEIN = {} + #LIVEOUT = {} +L2: + %t0_1 = 2 + goto L4 + #PHIDEFS = {} + #PHIUSES = {2} + #UEVAR = {} + #VARKILL = {2} + #LIVEIN = {} + #LIVEOUT = {2} +L4: + %t0_2 = phi(%t0_1, %t0_0) + ret %t0_2 + goto L1 + #PHIDEFS = {3} + #PHIUSES = {} + #UEVAR = {3} + #VARKILL = {} + #LIVEIN = {3} + #LIVEOUT = {} +L1: + #PHIDEFS = {} + #PHIUSES = {} + #UEVAR = {} + #VARKILL = {} + #LIVEIN = {} + #LIVEOUT = {} +L3: + %t0_0 = 0 + goto L4 + #PHIDEFS = {} + #PHIUSES = {1} + #UEVAR = {} + #VARKILL = {1} + #LIVEIN = {} + #LIVEOUT = {1} +""", result.toString()); + } }