diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java index 5bac8f8..7cec37a 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java @@ -7,6 +7,9 @@ * Implementation is based on description in * 'Practical Improvements to the Construction and Destruction * of Static Single Assignment Form' by Preston Briggs. + * + * The JikesRVM LeaveSSA implements a version of the + * same algorithm. */ public class ExitSSA { @@ -70,8 +73,11 @@ private void replaceUses(Instruction i) { } static class CopyItem { + /** Phi input can be a register or a constant so we record the operand */ final Operand src; + /** The phi destination */ final Register dest; + /** The basic block where the phi was present */ final BasicBlock destBlock; boolean removed; @@ -130,11 +136,19 @@ private void scheduleCopies(BasicBlock block, List pushed) { a copy target that is live, we must preserve the live value in a temporary name and rewrite subsequent uses to refer to the temporary name. + + This captures the cases when the result of a phi + in a control successor is live on exit of the current block. + This means that it is incorrect to simply insert a copy + of the destination in the current block. So we rename + the destination to a new temporary, and record the renaming + so that the dominator blocks get the new name. Comment adapted + from JikesRVM LeaveSSA */ if (block.liveOut.get(dest.id)) { /* Insert a copy from dest to a new temp t at phi node defining dest */ final Register t = addMoveToTempAfterPhi(destBlock, dest); - stacks[dest.id].push(t); + stacks[dest.id].push(t); // record the temp name pushed.add(dest.id); } /* Insert a copy operation from map[src] to dest at end of BB */ @@ -158,6 +172,13 @@ else if (src instanceof Operand.ConstantOperand srcConstantOperand) { cycle of references, it must insert a copy to a temporary that breaks the cycle. Then we can schedule the copies to respect the dependencies implied by the phi functions. + + An empty work list with work remaining in the copy set + implies a cycle in the dependencies amongst copies. To break + the cycle copy the destination of an arbitrary member of the + copy set to a temporary. This destination has therefore been + saved and can be safely overwritten. So then add the copy to the + work list. Comment adapted from JikesRVM LeaveSSA. */ if (!copySet.isEmpty()) { CopyItem copyItem = copySet.remove(0); @@ -220,6 +241,7 @@ private void addMoveAtBBEnd(BasicBlock block, Register src, Register dest) { var inst = new Instruction.Move(new Operand.RegisterOperand(src), new Operand.RegisterOperand(dest)); insertAtEnd(block, inst); } + /* Insert a copy from constant src to dst at end of BB */ private void addMoveAtBBEnd(BasicBlock block, Operand.ConstantOperand src, Register dest) { var inst = new Instruction.Move(src, new Operand.RegisterOperand(dest)); insertAtEnd(block, inst); diff --git a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java index d2b4f04..17ff310 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java @@ -1045,5 +1045,280 @@ func foo() { } + @Test + public void testParallelAssign() { + String src = """ + func foo(n: Int)->Int { + var a = 1 + var b = 2 + + while (n > 0) { + var t = a + a = b + b = t + n = n - 1 + } + return a + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA1() { + String src = """ + func foo()->Int { + var a = 5 + var b = 10 + var c = a + b + return c + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA2() { + String src = """ + func foo()->Int { + var a = 5 + a = a + 1; + return a + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA3() { + String src = """ + func foo()->Int { + var a = 5 + if (a > 3) { + a = 10 + } else { + a = 20 + } + return a + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA4() { + String src = """ + func foo()->Int { + var a = 0 + while (a < 5) { + a = a + 1; + } + return a + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA5() { + String src = """ + func foo()->Int { + var a = 0 + var b = 10 + if (b > 5) { + if (a < 5) { + a = 5 + } else { + a = 15 + } + } else { + a = 20 + } + return a + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA6() { + String src = """ + func foo()->Int { + var arr = new [Int] {1, 2}; + arr[0] = 10 + var x = arr[0] + return x + } + """; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA7() { + String src = """ + func add(x: Int, y : Int)->Int { + return x + y + } + func main()->Int { + var a = 5 + var b = 10 + var c = add(a, b) + return c + } +"""; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA8() { + String src = """ + func main()->Int { + var a = 0 + var b = 1 + while (a < 10) { + a = a + 2 + } + while (b < 20) { + b = b + 3 + } + return a + b + } +"""; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA9() { + String src = """ + func main()->Int { + var a = 0 + var b = 0 + var i = 0 + var j = 0 + while (i < 3) { + j = 0 + while (j < 2) { + a = a + 1 + i = j + 1 + } + b = b + 1; + i = i + 1 + } + return a + b + } +"""; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA10() { + String src = """ + func main()->Int { + var sum = 0 + var i = 0 + var j = 0 + while (i < 5) { + j = 0 + while (j < 5) { + if (j % 2 == 0) + sum = sum + j + j = j + 1 + } + i = i + 1 + } + return sum + } +"""; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA11() { + String src = """ + func main()->Int { + var a = 0 + var i = 0 + var j = 0 + while (i < 3) { + j = 0 + while (j < 3) { + if (i == j) + a = a + i + j + else if (i > j) + a = a - 1 + j = j + 1 + } + i = i + 1 + } + return a + } +"""; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA12() { + String src = """ + func main()->Int { + var count = 0 + var i = 0 + var j = 0 + while (i < 5) { + j = 0 + while (j < 5) { + if (i + j > 5) + break + if (i == j) { + j = j + 1 + continue + } + count = count + 1 + j = j + 1 + } + i = i + 1 + } + return count + } +"""; + String result = compileSrc(src); + System.out.println(result); + } + + @Test + public void testSSA13() { + String src = """ + func main()->Int { + var outerSum = 0 + var innerSum = 0 + var i = 0 + var j = 0 + while (i < 4) { + j = 0 + while (j < 4) { + if ((i + j) % 2 == 0) + innerSum = innerSum + j + j = j + 1 + } + outerSum = outerSum + innerSum + i = i + 1 + } + return outerSum + } +"""; + String result = compileSrc(src); + System.out.println(result); + } } \ No newline at end of file diff --git a/parser/src/main/java/com/compilerprogramming/ezlang/parser/Parser.java b/parser/src/main/java/com/compilerprogramming/ezlang/parser/Parser.java index de65e19..2249d4d 100644 --- a/parser/src/main/java/com/compilerprogramming/ezlang/parser/Parser.java +++ b/parser/src/main/java/com/compilerprogramming/ezlang/parser/Parser.java @@ -348,7 +348,6 @@ private AST.Expr parsePrimary(Lexer lexer) { case PUNCT -> { /* Nested expression */ matchPunctuation(lexer, "("); - nextToken(lexer); var x = parseBool(lexer); matchPunctuation(lexer, ")"); return x;