Skip to content

Commit 8844725

Browse files
Merge pull request #21 from CompilerProgramming/sccp
SCCP Updates
2 parents 52a83d8 + e839091 commit 8844725

File tree

4 files changed

+93
-28
lines changed

4 files changed

+93
-28
lines changed

optvm/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ a physical machine. Therefore, all our optimization passes will work on the inst
3535
liveness data per basic block - mainly live-out. Note that the interference graph builder starts here and computes instruction level liveness as necessary.
3636
* [EnterSSA](src/main/java/com/compilerprogramming/ezlang/compiler/EnterSSA.java) - Transforms into SSA, using algorithm by Preston Briggs.
3737
* [ExitSSA](src/main/java/com/compilerprogramming/ezlang/compiler/ExitSSA.java) - Exits SSA form, using algorithm by Preston Briggs.
38+
* [SparseConditionalConstantPropagation](src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java) - Conditional Constant Propagation on SSA form (SCCP)
39+
* [SSAEdges](src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java) - SSAEdges are def-use chains used by SCCP algorithm
3840
* [LoopFinder](src/main/java/com/compilerprogramming/ezlang/compiler/LoopFinder.java) - Discovers loops. (Not used yet)
3941
* [LoopNest](src/main/java/com/compilerprogramming/ezlang/compiler/LoopNest.java) - Representation of loop nesting. (Not used yet)
4042
* [InterferenceGraph](src/main/java/com/compilerprogramming/ezlang/compiler/InterferenceGraph.java) - Representation of an Interference Graph

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SSAEdges.java

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,29 +15,33 @@
1515
public class SSAEdges {
1616

1717
public static final class SSADef {
18-
public final Register register;
18+
/**
19+
* Instruction where the definition occurs
20+
*/
1921
public final Instruction instruction;
22+
/**
23+
* Instructions that use the definition
24+
*/
2025
public final List<Instruction> useList;
2126

22-
public SSADef(Register register, Instruction instruction) {
23-
this.register = register;
27+
public SSADef(Instruction instruction) {
2428
this.instruction = instruction;
2529
this.useList = new ArrayList<>();
2630
}
2731
}
2832

29-
public static Map<Integer, SSADef> buildDefUseChains(CompiledFunction function) {
33+
public static Map<Register, SSADef> buildDefUseChains(CompiledFunction function) {
3034

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

33-
Map<Integer, SSADef> defUseChains = new HashMap<>();
37+
Map<Register, SSADef> defUseChains = new HashMap<>();
3438
recordDefs(function, defUseChains);
3539
recordUses(function, defUseChains);
3640

3741
return defUseChains;
3842
}
3943

40-
private static void recordDefs(CompiledFunction function, Map<Integer, SSADef> defUseChains) {
44+
private static void recordDefs(CompiledFunction function, Map<Register, SSADef> defUseChains) {
4145
for (BasicBlock block : function.getBlocks()) {
4246
for (Instruction instruction : block.instructions) {
4347
if (instruction instanceof Instruction.Phi phi) {
@@ -50,7 +54,7 @@ else if (instruction.definesVar()) {
5054
}
5155
}
5256

53-
private static void recordUses(CompiledFunction function, Map<Integer, SSADef> defUseChains) {
57+
private static void recordUses(CompiledFunction function, Map<Register, SSADef> defUseChains) {
5458
for (BasicBlock block : function.getBlocks()) {
5559
for (Instruction instruction : block.instructions) {
5660
if (instruction instanceof Instruction.Phi phi) {
@@ -65,16 +69,16 @@ private static void recordUses(CompiledFunction function, Map<Integer, SSADef> d
6569
}
6670
}
6771

68-
private static void recordUses(Map<Integer, SSADef> defUseChains, Register[] inputs, BasicBlock block, Instruction instruction) {
72+
private static void recordUses(Map<Register, SSADef> defUseChains, Register[] inputs, BasicBlock block, Instruction instruction) {
6973
for (Register register : inputs) {
70-
SSADef def = defUseChains.get(register.id);
74+
SSADef def = defUseChains.get(register);
7175
def.useList.add(instruction);
7276
}
7377
}
7478

75-
private static void recordDef(Map<Integer, SSADef> defUseChains, Register value, Instruction instruction) {
76-
if (defUseChains.containsKey(value.id))
79+
private static void recordDef(Map<Register, SSADef> defUseChains, Register value, Instruction instruction) {
80+
if (defUseChains.containsKey(value))
7781
throw new CompilerException("Register already defined, invalid multiple definition in SSA");
78-
defUseChains.put(value.id, new SSADef(value, instruction));
82+
defUseChains.put(value, new SSADef(instruction));
7983
}
8084
}

optvm/src/main/java/com/compilerprogramming/ezlang/compiler/SparseConditionalConstantPropagation.java

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.compilerprogramming.ezlang.compiler;
22

3+
import com.compilerprogramming.ezlang.exceptions.CompilerException;
34
import com.compilerprogramming.ezlang.types.Type;
45

56
import java.util.*;
@@ -10,30 +11,43 @@
1011
*
1112
* <ol>
1213
* <li>Constant Propagation with Conditional Branches. Wegman and Zadeck.</li>
13-
* <li>Modern Compiler Implementation in C</li>
14-
* <li></li>
14+
* <li>Modern Compiler Implementation in C, Andrew Appel, section 19.3</li>
15+
* <li>Building an Optimizing Compiler, Bob Morgan, section 8.3</li>
1516
* </ol>
1617
*
1718
*
1819
*/
1920
public class SparseConditionalConstantPropagation {
2021

2122
/**
22-
* Contains a lattice for all possible definitions
23+
* Contains a lattice for each SSA definition
2324
*/
2425
ValueLattice valueLattice;
2526
/**
26-
* Executable status for each flow edge
27+
* Executable status for each flow edge, initially all edges are
28+
* marked non-executable except the start block
2729
*/
2830
Map<FlowEdge, Boolean> flowEdges;
31+
/**
32+
* Worklist of ssaedges (the term used by SCCP paper)
33+
*/
2934
WorkList<Instruction> instructionWorkList;
35+
/**
36+
* As edges between basic blocks become executable, we
37+
* add them the impacted blocks to the worklist for processing.
38+
*/
3039
WorkList<BasicBlock> flowWorklist;
40+
/**
41+
* We don't evaluate a block more than once (except for Phi instructions
42+
* in the block). So we have to track which blocks have already been
43+
* evaluated.
44+
*/
3145
BitSet visited = new BitSet();
3246
/**
3347
* Def use chains for each register
3448
* Called SSAEdge in the original paper.
3549
*/
36-
Map<Integer, SSAEdges.SSADef> ssaEdges;
50+
Map<Register, SSAEdges.SSADef> ssaEdges;
3751
CompiledFunction function;
3852

3953
public SparseConditionalConstantPropagation constantPropagation(CompiledFunction function) {
@@ -61,8 +75,7 @@ public String toString() {
6175
}
6276
}
6377
sb.append("Lattices:\n");
64-
for (var id: valueLattice.valueLattice.keySet()) {
65-
var register = function.registerPool.getReg(id);
78+
for (var register: valueLattice.getRegisters()) {
6679
sb.append(register.name()).append("=").append(valueLattice.get(register)).append("\n");
6780
}
6881
return sb.toString();
@@ -86,14 +99,14 @@ private void visitInstruction(Instruction instruction) {
8699
if (instruction instanceof Instruction.ConditionalBranch || instruction instanceof Instruction.Jump) {
87100
for (BasicBlock s : block.successors) {
88101
if (isEdgeExecutable(block, s)) {
89-
flowWorklist.push(block); // Is this correct ?
102+
flowWorklist.push(block); // Push both this block and successor to worklist?
90103
flowWorklist.push(s);
91104
}
92105
}
93106
} else if (instruction.definesVar() || instruction instanceof Instruction.Phi) {
94107
var def = instruction instanceof Instruction.Phi phi ? phi.value() : instruction.def();
95108
// Push all uses (instructions) of the def into the worklist
96-
SSAEdges.SSADef ssaDef = ssaEdges.get(def.id);
109+
SSAEdges.SSADef ssaDef = ssaEdges.get(def);
97110
if (ssaDef != null) {
98111
for (Instruction use : ssaDef.useList) {
99112
instructionWorkList.push(use);
@@ -209,6 +222,14 @@ public String toString() {
209222
}
210223
}
211224

225+
/**
226+
* This is based on the description of CP_Evaluate(I) in
227+
* Building an Optimizing Compiler. It evaluates an instruction and
228+
* if the instruction defines an SSA variable, then it updates the lattice
229+
* value of that variable. If the lattice changes then this returns true,
230+
* else false. For branches the change in executable status of an edge is
231+
* used instead of the lattice value change.
232+
*/
212233
private boolean evalInstruction(Instruction instruction) {
213234
BasicBlock block = instruction.block;
214235
boolean changed = false;
@@ -252,8 +273,6 @@ private boolean evalInstruction(Instruction instruction) {
252273
} else throw new IllegalStateException();
253274
}
254275
case Instruction.Call callInst -> {
255-
// Copy args to new frame
256-
// Copy return value in expected location
257276
if (!(callInst.callee.returnType instanceof Type.TypeVoid)) {
258277
var cell = valueLattice.get(callInst.returnOperand().reg);
259278
changed = cell.setKind(V_VARYING);
@@ -341,6 +360,7 @@ private boolean visitPhi(BasicBlock block, Instruction.Phi phiInst) {
341360
LatticeElement newValue = new LatticeElement(V_UNDEFINED, 0);
342361
for (int j = 0; j < block.predecessors.size(); j++) {
343362
BasicBlock pred = block.predecessors.get(j);
363+
// We ignore non-executable edges
344364
if (isEdgeExecutable(pred, block)) {
345365
LatticeElement varValue = valueLattice.get(phiInst.input(j));
346366
newValue.meet(varValue);
@@ -357,6 +377,7 @@ private boolean markEdgeExecutable(BasicBlock source, BasicBlock target) {
357377
var oldValue = flowEdges.get(edge);
358378
assert oldValue != null;
359379
if (!oldValue) {
380+
// Mark edge as executable
360381
flowEdges.put(edge, true);
361382
return true;
362383
}
@@ -393,6 +414,8 @@ private static boolean evalLogical(LatticeElement cell, LatticeElement left, Lat
393414
}
394415
changed = cell.meet(result);
395416
} else if (left.kind == V_VARYING || right.kind == V_VARYING) {
417+
// We could constrain the result here to the set [0-1]
418+
// but we don't track ranges or sets of values
396419
changed = cell.setKind(V_VARYING);
397420
}
398421
return changed;
@@ -412,6 +435,7 @@ private static boolean evalArith(LatticeElement cell, LatticeElement left, Latti
412435
result = leftValue - rightValue;
413436
break;
414437
case "/":
438+
if (rightValue == 0) throw new CompilerException("Division by zero");
415439
result = leftValue / rightValue;
416440
break;
417441
case "*":
@@ -425,25 +449,34 @@ private static boolean evalArith(LatticeElement cell, LatticeElement left, Latti
425449
}
426450
changed = cell.meet(result);
427451
} else if (binOp.equals("*") && ((left.kind == V_CONSTANT && left.value == 0) || (right.kind == V_CONSTANT && right.value == 0))) {
428-
var result = new LatticeElement(V_CONSTANT, 0);
429-
changed = cell.meet(result);
452+
// multiplication with 0 yields 0
453+
changed = cell.meet(0);
430454
} else if (left.kind == V_VARYING || right.kind == V_VARYING) {
431455
changed = cell.setKind(V_VARYING);
432456
}
433457
return changed;
434458
}
435459

460+
/**
461+
* Maintains a Lattice for each SSA variable - i.e register
462+
* Initial value of lattice is TOP/Undefined
463+
*/
436464
static final class ValueLattice {
437-
Map<Integer, LatticeElement> valueLattice = new HashMap<>();
465+
466+
private final Map<Register, LatticeElement> valueLattice = new HashMap<>();
438467

439468
LatticeElement get(Register reg) {
440-
var cell = valueLattice.get(reg.id);
469+
var cell = valueLattice.get(reg);
441470
if (cell == null) {
471+
// Initial value is UNDEFINED/TOP
442472
cell = new LatticeElement(V_UNDEFINED, 0);
443-
valueLattice.put(reg.id, cell);
473+
valueLattice.put(reg, cell);
444474
}
445475
return cell;
446476
}
477+
Set<Register> getRegisters() {
478+
return valueLattice.keySet();
479+
}
447480
}
448481

449482
static final class WorkList<E> {

optvm/src/test/java/com/compilerprogramming/ezlang/compiler/TestSSATransform.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,4 +814,30 @@ func foo(x: Int) {
814814
System.out.println(result);
815815
}
816816

817+
// http://users.csc.calpoly.edu/~akeen/courses/csc431/handouts/references/ssa_example.pdf
818+
@Test
819+
public void testSSAExample() {
820+
// TODO
821+
String src = """
822+
func foo(x: Int, y: Int)->Int {
823+
var sum: Int
824+
825+
if (x >= y)
826+
return 0
827+
828+
sum = 0;
829+
while (x < y) {
830+
if (x / 2 * 2 == x) {
831+
sum = sum + 1
832+
}
833+
x = x + 1
834+
}
835+
return sum
836+
}
837+
""";
838+
String result = compileSrc(src);
839+
System.out.println(result);
840+
841+
}
842+
817843
}

0 commit comments

Comments
 (0)