diff --git a/optvm/README.md b/optvm/README.md index bae0927..218b4f7 100644 --- a/optvm/README.md +++ b/optvm/README.md @@ -35,6 +35,8 @@ a physical machine. Therefore, all our optimization passes will work on the inst liveness data per basic block - mainly live-out. Note that the interference graph builder starts here and computes instruction level liveness as necessary. * [EnterSSA](src/main/java/com/compilerprogramming/ezlang/compiler/EnterSSA.java) - Transforms into SSA, using algorithm by Preston Briggs. * [ExitSSA](src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java) - Exits SSA form, using algorithm by Preston Briggs. +* [SparseConditionalConstantPropagation](src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java) - Conditional Constant Propagation on SSA form (SCCP) +* [SSAEdges](src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java) - SSAEdges are def-use chains used by SCCP algorithm * [LoopFinder](src/main/java/com/compilerprogramming/ezlang/compiler/LoopFinder.java) - Discovers loops. (Not used yet) * [LoopNest](src/main/java/com/compilerprogramming/ezlang/compiler/LoopNest.java) - Representation of loop nesting. (Not used yet) * [InterferenceGraph](src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraph.java) - Representation of an Interference Graph diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java index 6026f8a..262a3ba 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java @@ -15,29 +15,33 @@ public class SSAEdges { public static final class SSADef { - public final Register register; + /** + * Instruction where the definition occurs + */ public final Instruction instruction; + /** + * Instructions that use the definition + */ public final List useList; - public SSADef(Register register, Instruction instruction) { - this.register = register; + public SSADef(Instruction instruction) { this.instruction = instruction; this.useList = new ArrayList<>(); } } - public static Map buildDefUseChains(CompiledFunction function) { + public static Map buildDefUseChains(CompiledFunction function) { if (!function.isSSA) throw new CompilerException("Function must be in SSA form"); - Map defUseChains = new HashMap<>(); + Map defUseChains = new HashMap<>(); recordDefs(function, defUseChains); recordUses(function, defUseChains); return defUseChains; } - private static void recordDefs(CompiledFunction function, Map defUseChains) { + private static void recordDefs(CompiledFunction function, Map defUseChains) { for (BasicBlock block : function.getBlocks()) { for (Instruction instruction : block.instructions) { if (instruction instanceof Instruction.Phi phi) { @@ -50,7 +54,7 @@ else if (instruction.definesVar()) { } } - private static void recordUses(CompiledFunction function, Map defUseChains) { + private static void recordUses(CompiledFunction function, Map defUseChains) { for (BasicBlock block : function.getBlocks()) { for (Instruction instruction : block.instructions) { if (instruction instanceof Instruction.Phi phi) { @@ -65,16 +69,16 @@ private static void recordUses(CompiledFunction function, Map d } } - private static void recordUses(Map defUseChains, Register[] inputs, BasicBlock block, Instruction instruction) { + private static void recordUses(Map defUseChains, Register[] inputs, BasicBlock block, Instruction instruction) { for (Register register : inputs) { - SSADef def = defUseChains.get(register.id); + SSADef def = defUseChains.get(register); def.useList.add(instruction); } } - private static void recordDef(Map defUseChains, Register value, Instruction instruction) { - if (defUseChains.containsKey(value.id)) + private static void recordDef(Map defUseChains, Register value, Instruction instruction) { + if (defUseChains.containsKey(value)) throw new CompilerException("Register already defined, invalid multiple definition in SSA"); - defUseChains.put(value.id, new SSADef(value, instruction)); + defUseChains.put(value, new SSADef(instruction)); } } diff --git a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java index f56154e..47a838b 100644 --- a/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java +++ b/optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java @@ -1,5 +1,6 @@ package com.compilerprogramming.ezlang.compiler; +import com.compilerprogramming.ezlang.exceptions.CompilerException; import com.compilerprogramming.ezlang.types.Type; import java.util.*; @@ -10,8 +11,8 @@ * *
    *
  1. Constant Propagation with Conditional Branches. Wegman and Zadeck.
  2. - *
  3. Modern Compiler Implementation in C
  4. - *
  5. + *
  6. Modern Compiler Implementation in C, Andrew Appel, section 19.3
  7. + *
  8. Building an Optimizing Compiler, Bob Morgan, section 8.3
  9. *
* * @@ -19,21 +20,34 @@ public class SparseConditionalConstantPropagation { /** - * Contains a lattice for all possible definitions + * Contains a lattice for each SSA definition */ ValueLattice valueLattice; /** - * Executable status for each flow edge + * Executable status for each flow edge, initially all edges are + * marked non-executable except the start block */ Map flowEdges; + /** + * Worklist of ssaedges (the term used by SCCP paper) + */ WorkList instructionWorkList; + /** + * As edges between basic blocks become executable, we + * add them the impacted blocks to the worklist for processing. + */ WorkList flowWorklist; + /** + * We don't evaluate a block more than once (except for Phi instructions + * in the block). So we have to track which blocks have already been + * evaluated. + */ BitSet visited = new BitSet(); /** * Def use chains for each register * Called SSAEdge in the original paper. */ - Map ssaEdges; + Map ssaEdges; CompiledFunction function; public SparseConditionalConstantPropagation constantPropagation(CompiledFunction function) { @@ -61,8 +75,7 @@ public String toString() { } } sb.append("Lattices:\n"); - for (var id: valueLattice.valueLattice.keySet()) { - var register = function.registerPool.getReg(id); + for (var register: valueLattice.getRegisters()) { sb.append(register.name()).append("=").append(valueLattice.get(register)).append("\n"); } return sb.toString(); @@ -86,14 +99,14 @@ private void visitInstruction(Instruction instruction) { if (instruction instanceof Instruction.ConditionalBranch || instruction instanceof Instruction.Jump) { for (BasicBlock s : block.successors) { if (isEdgeExecutable(block, s)) { - flowWorklist.push(block); // Is this correct ? + flowWorklist.push(block); // Push both this block and successor to worklist? flowWorklist.push(s); } } } else if (instruction.definesVar() || instruction instanceof Instruction.Phi) { var def = instruction instanceof Instruction.Phi phi ? phi.value() : instruction.def(); // Push all uses (instructions) of the def into the worklist - SSAEdges.SSADef ssaDef = ssaEdges.get(def.id); + SSAEdges.SSADef ssaDef = ssaEdges.get(def); if (ssaDef != null) { for (Instruction use : ssaDef.useList) { instructionWorkList.push(use); @@ -209,6 +222,14 @@ public String toString() { } } + /** + * This is based on the description of CP_Evaluate(I) in + * Building an Optimizing Compiler. It evaluates an instruction and + * if the instruction defines an SSA variable, then it updates the lattice + * value of that variable. If the lattice changes then this returns true, + * else false. For branches the change in executable status of an edge is + * used instead of the lattice value change. + */ private boolean evalInstruction(Instruction instruction) { BasicBlock block = instruction.block; boolean changed = false; @@ -252,8 +273,6 @@ private boolean evalInstruction(Instruction instruction) { } else throw new IllegalStateException(); } case Instruction.Call callInst -> { - // Copy args to new frame - // Copy return value in expected location if (!(callInst.callee.returnType instanceof Type.TypeVoid)) { var cell = valueLattice.get(callInst.returnOperand().reg); changed = cell.setKind(V_VARYING); @@ -341,6 +360,7 @@ private boolean visitPhi(BasicBlock block, Instruction.Phi phiInst) { LatticeElement newValue = new LatticeElement(V_UNDEFINED, 0); for (int j = 0; j < block.predecessors.size(); j++) { BasicBlock pred = block.predecessors.get(j); + // We ignore non-executable edges if (isEdgeExecutable(pred, block)) { LatticeElement varValue = valueLattice.get(phiInst.input(j)); newValue.meet(varValue); @@ -357,6 +377,7 @@ private boolean markEdgeExecutable(BasicBlock source, BasicBlock target) { var oldValue = flowEdges.get(edge); assert oldValue != null; if (!oldValue) { + // Mark edge as executable flowEdges.put(edge, true); return true; } @@ -393,6 +414,8 @@ private static boolean evalLogical(LatticeElement cell, LatticeElement left, Lat } changed = cell.meet(result); } else if (left.kind == V_VARYING || right.kind == V_VARYING) { + // We could constrain the result here to the set [0-1] + // but we don't track ranges or sets of values changed = cell.setKind(V_VARYING); } return changed; @@ -412,6 +435,7 @@ private static boolean evalArith(LatticeElement cell, LatticeElement left, Latti result = leftValue - rightValue; break; case "/": + if (rightValue == 0) throw new CompilerException("Division by zero"); result = leftValue / rightValue; break; case "*": @@ -425,25 +449,34 @@ private static boolean evalArith(LatticeElement cell, LatticeElement left, Latti } changed = cell.meet(result); } else if (binOp.equals("*") && ((left.kind == V_CONSTANT && left.value == 0) || (right.kind == V_CONSTANT && right.value == 0))) { - var result = new LatticeElement(V_CONSTANT, 0); - changed = cell.meet(result); + // multiplication with 0 yields 0 + changed = cell.meet(0); } else if (left.kind == V_VARYING || right.kind == V_VARYING) { changed = cell.setKind(V_VARYING); } return changed; } + /** + * Maintains a Lattice for each SSA variable - i.e register + * Initial value of lattice is TOP/Undefined + */ static final class ValueLattice { - Map valueLattice = new HashMap<>(); + + private final Map valueLattice = new HashMap<>(); LatticeElement get(Register reg) { - var cell = valueLattice.get(reg.id); + var cell = valueLattice.get(reg); if (cell == null) { + // Initial value is UNDEFINED/TOP cell = new LatticeElement(V_UNDEFINED, 0); - valueLattice.put(reg.id, cell); + valueLattice.put(reg, cell); } return cell; } + Set getRegisters() { + return valueLattice.keySet(); + } } static final class WorkList { 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 52dffb6..8b46834 100644 --- a/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java +++ b/optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java @@ -814,4 +814,30 @@ func foo(x: Int) { System.out.println(result); } + // http://users.csc.calpoly.edu/~akeen/courses/csc431/handouts/references/ssa_example.pdf + @Test + public void testSSAExample() { + // TODO + String src = """ +func foo(x: Int, y: Int)->Int { + var sum: Int + + if (x >= y) + return 0 + + sum = 0; + while (x < y) { + if (x / 2 * 2 == x) { + sum = sum + 1 + } + x = x + 1 + } + return sum +} + """; + String result = compileSrc(src); + System.out.println(result); + + } + } \ No newline at end of file