Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -48,25 +52,29 @@ private void initBlocks(RegisterPool regPool, List<BasicBlock> blocks) {

private void init(List<BasicBlock> 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))
Expand All @@ -76,11 +84,24 @@ private void init(List<BasicBlock> 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);
}
}
}
}
}
Expand All @@ -89,6 +110,7 @@ private void computeLiveness(List<BasicBlock> blocks) {
boolean changed = true;
while (changed) {
changed = false;
// TODO we should process in RPO order
for (BasicBlock block : blocks) {
if (recomputeLiveOut(block))
changed = true;
Expand All @@ -97,8 +119,17 @@ private void computeLiveness(List<BasicBlock> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}