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
2 changes: 2 additions & 0 deletions optvm/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Instruction> useList;

public SSADef(Register register, Instruction instruction) {
this.register = register;
public SSADef(Instruction instruction) {
this.instruction = instruction;
this.useList = new ArrayList<>();
}
}

public static Map<Integer, SSADef> buildDefUseChains(CompiledFunction function) {
public static Map<Register, SSADef> buildDefUseChains(CompiledFunction function) {

if (!function.isSSA) throw new CompilerException("Function must be in SSA form");

Map<Integer, SSADef> defUseChains = new HashMap<>();
Map<Register, SSADef> defUseChains = new HashMap<>();
recordDefs(function, defUseChains);
recordUses(function, defUseChains);

return defUseChains;
}

private static void recordDefs(CompiledFunction function, Map<Integer, SSADef> defUseChains) {
private static void recordDefs(CompiledFunction function, Map<Register, SSADef> defUseChains) {
for (BasicBlock block : function.getBlocks()) {
for (Instruction instruction : block.instructions) {
if (instruction instanceof Instruction.Phi phi) {
Expand All @@ -50,7 +54,7 @@ else if (instruction.definesVar()) {
}
}

private static void recordUses(CompiledFunction function, Map<Integer, SSADef> defUseChains) {
private static void recordUses(CompiledFunction function, Map<Register, SSADef> defUseChains) {
for (BasicBlock block : function.getBlocks()) {
for (Instruction instruction : block.instructions) {
if (instruction instanceof Instruction.Phi phi) {
Expand All @@ -65,16 +69,16 @@ private static void recordUses(CompiledFunction function, Map<Integer, SSADef> d
}
}

private static void recordUses(Map<Integer, SSADef> defUseChains, Register[] inputs, BasicBlock block, Instruction instruction) {
private static void recordUses(Map<Register, SSADef> 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<Integer, SSADef> defUseChains, Register value, Instruction instruction) {
if (defUseChains.containsKey(value.id))
private static void recordDef(Map<Register, SSADef> 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));
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.compilerprogramming.ezlang.compiler;

import com.compilerprogramming.ezlang.exceptions.CompilerException;
import com.compilerprogramming.ezlang.types.Type;

import java.util.*;
Expand All @@ -10,30 +11,43 @@
*
* <ol>
* <li>Constant Propagation with Conditional Branches. Wegman and Zadeck.</li>
* <li>Modern Compiler Implementation in C</li>
* <li></li>
* <li>Modern Compiler Implementation in C, Andrew Appel, section 19.3</li>
* <li>Building an Optimizing Compiler, Bob Morgan, section 8.3</li>
* </ol>
*
*
*/
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<FlowEdge, Boolean> flowEdges;
/**
* Worklist of ssaedges (the term used by SCCP paper)
*/
WorkList<Instruction> instructionWorkList;
/**
* As edges between basic blocks become executable, we
* add them the impacted blocks to the worklist for processing.
*/
WorkList<BasicBlock> 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<Integer, SSAEdges.SSADef> ssaEdges;
Map<Register, SSAEdges.SSADef> ssaEdges;
CompiledFunction function;

public SparseConditionalConstantPropagation constantPropagation(CompiledFunction function) {
Expand Down Expand Up @@ -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();
Expand All @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand All @@ -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 "*":
Expand All @@ -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<Integer, LatticeElement> valueLattice = new HashMap<>();

private final Map<Register, LatticeElement> 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<Register> getRegisters() {
return valueLattice.keySet();
}
}

static final class WorkList<E> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);

}

}
Loading