Skip to content

Commit 7e320f7

Browse files
New pass that detects == operations against constant value in conditional branches - and uses the knowledge to propagate the constant value in the true branch.
1 parent 111a8fb commit 7e320f7

File tree

4 files changed

+174
-2
lines changed

4 files changed

+174
-2
lines changed
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
package com.compilerprogramming.ezlang.compiler;
2+
3+
import java.util.EnumSet;
4+
import java.util.Iterator;
5+
import java.util.Map;
6+
7+
/**
8+
* The goal of this pass is to detect conditional branching based
9+
* on comparison with a constant. Example
10+
*
11+
* <pre>
12+
* if (i == 5)
13+
* {
14+
* // some use of i
15+
* }
16+
* </pre>
17+
*
18+
* SCCP generates a lattice value for i, and if i changes outside the if block, then
19+
* it is not classed as a constant. This means that inside the if block, we cannot exploit
20+
* the knowledge that i is a constant locally.
21+
*
22+
* To enable this, we need to detect such comparisons and then insert a temp
23+
* variable which is set to the constant value. The new temp variable is inserted
24+
* inside the if block only and does not affect the meaning of i outside the if block.
25+
* After this transformation we can run SCCP again to take advantage of the local
26+
* knowledge.
27+
*
28+
* This technique can be extended to null checks too, but we do not do that yet.
29+
*/
30+
public class ConstantComparisonPropagation {
31+
32+
private final CompiledFunction function;
33+
private DominatorTree domTree;
34+
private Map<Register, SSAEdges.SSADef> ssaDefUse;
35+
private boolean updated = false;
36+
37+
public ConstantComparisonPropagation(CompiledFunction function) {
38+
this.function = function;
39+
}
40+
41+
public boolean apply(EnumSet<Options> options) {
42+
if (options.contains(Options.CCP)) {
43+
updated = false;
44+
domTree = new DominatorTree(function.entry);
45+
ssaDefUse = SSAEdges.buildDefUseChains(function);
46+
walkBlocks();
47+
}
48+
return updated;
49+
}
50+
51+
private void walkBlocks() {
52+
walkBlock(function.entry);
53+
}
54+
55+
private void walkBlock(BasicBlock block) {
56+
propagateConstantsInComparisons(block);
57+
for (BasicBlock c : block.dominatedChildren) {
58+
walkBlock(c);
59+
}
60+
}
61+
62+
private void propagateConstantsInComparisons(BasicBlock block) {
63+
if (block == function.exit) return;
64+
// Get terminal instruction
65+
Instruction instruction = block.instructions.getLast();
66+
if (instruction instanceof Instruction.ConditionalBranch cbr) {
67+
if (cbr.condition() instanceof Operand.RegisterOperand conditionVar) {
68+
SSAEdges.SSADef defUse = ssaDefUse.get(conditionVar.reg);
69+
// If the condition var was result of == with constant value
70+
if (defUse.instruction.block.bid == block.bid
71+
&& defUse.instruction instanceof Instruction.Binary binary
72+
&& binary.binOp.equals("==")) {
73+
// Get the constant and the register operands
74+
// from the binary
75+
Operand.ConstantOperand constantOp = null;
76+
Operand.RegisterOperand registerOp = null;
77+
if (binary.left() instanceof Operand.ConstantOperand leftConstant
78+
&& binary.right() instanceof Operand.RegisterOperand rightReg) {
79+
constantOp = leftConstant;
80+
registerOp = rightReg;
81+
} else if (binary.right() instanceof Operand.ConstantOperand rightConstant
82+
&& binary.left() instanceof Operand.RegisterOperand leftReg) {
83+
constantOp = rightConstant;
84+
registerOp = leftReg;
85+
}
86+
if (constantOp != null) {
87+
// Since reg is constant in true branch
88+
// We can replace all uses of reg with the constant
89+
// in blocks dominated by the true block
90+
BasicBlock trueBlock = cbr.trueBlock;
91+
// I am not sure if this scenario can occur but
92+
// for safety we check that the register we will
93+
// replace is not immediately used inside the true block
94+
// in a phi, because we intend to add the definition of
95+
// the replacement after any phis
96+
if (!checkUsedInPhi(trueBlock, registerOp.reg)) {
97+
// Create a temp and move constant to it.
98+
// We insert the new instruction at the top of the True Block,
99+
// where it should dominate all uses of it
100+
// We could just replace with a constant here instead of creating
101+
// a temp and a move instruction but that would be less general a solution as it
102+
// would not work for other scenarios such as null status which we will
103+
// be adding in future
104+
var replacementRegister = function.registerPool.newTempReg(registerOp.reg.type);
105+
var defInst = new Instruction.Move(constantOp, new Operand.TempRegisterOperand(replacementRegister));
106+
insertAtBeginning(trueBlock, defInst);
107+
var replacementRegisterUses = SSAEdges.addDef(ssaDefUse, replacementRegister, defInst); // Update SSA Def Use chains, add def for new reg
108+
Iterator<Instruction> useIter = ssaDefUse.get(registerOp.reg).useList.iterator();
109+
while (useIter.hasNext()) {
110+
Instruction use = useIter.next();
111+
if (trueBlock.dominates(use.block)) {
112+
use.replaceUse(registerOp.reg, replacementRegister);
113+
// Update SSA Def use chains
114+
useIter.remove(); // No longer a use of old register
115+
replacementRegisterUses.addUse(use); // Use of new temp register
116+
}
117+
}
118+
}
119+
}
120+
}
121+
}
122+
}
123+
}
124+
125+
/* Check if the register is used in a phi instruction within the block */
126+
private static boolean checkUsedInPhi(BasicBlock block, Register register) {
127+
for (Instruction instruction : block.instructions) {
128+
if (instruction instanceof Instruction.Phi phi) {
129+
for (int i = 0; i < phi.numInputs(); i++) {
130+
if (phi.isRegisterInput(i)) {
131+
Register phiUse = phi.inputAsRegister(i);
132+
if (phiUse.equals(register)) return true;
133+
}
134+
}
135+
}
136+
else break;
137+
}
138+
return false;
139+
}
140+
141+
/* Insert instruction at the start of BB after any phis */
142+
private static void insertAtBeginning(BasicBlock block, Instruction instruction) {
143+
int pos = 0; // insertion point
144+
for (; pos < block.instructions.size(); pos++) {
145+
if (!(block.instructions.get(pos) instanceof Instruction.Phi))
146+
break;
147+
}
148+
if (pos == block.instructions.size())
149+
throw new IllegalStateException();
150+
block.add(pos, instruction);
151+
}
152+
}

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@ public class Optimizer {
77
public void optimize(CompiledFunction function, EnumSet<Options> options) {
88
if (options.contains(Options.OPTIMIZE)) {
99
new EnterSSA(function, options);
10-
if (options.contains(Options.SCCP))
10+
if (options.contains(Options.SCCP)) {
1111
new SparseConditionalConstantPropagation().constantPropagation(function).apply(options);
12+
if (new ConstantComparisonPropagation(function).apply(options)) {
13+
// Run SCCP again
14+
// We could repeat this until no further changes occur
15+
new SparseConditionalConstantPropagation().constantPropagation(function).apply(options);
16+
}
17+
}
1218
new ExitSSA(function, options);
1319
}
1420
if (options.contains(Options.REGALLOC))

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
public enum Options {
66
OPTIMIZE,
77
SCCP,
8+
CCP, // constant comparison propagation
89
REGALLOC,
910
DUMP_INITIAL_IR,
1011
DUMP_PRE_SSA_DOMTREE,
1112
DUMP_SSA_IR,
1213
DUMP_SCCP_PREAPPLY,
1314
DUMP_SCCP_POSTAPPLY,
15+
DUMP_CCP_POSTAPPLY,
1416
DUMP_SSA_LIVENESS,
1517
DUMP_SSA_DOMTREE,
1618
DUMP_POST_SSA_IR,
@@ -19,6 +21,6 @@ public enum Options {
1921
DUMP_POST_CHAITIN_IR;
2022

2123
public static final EnumSet<Options> NONE = EnumSet.noneOf(Options.class);
22-
public static final EnumSet<Options> OPT = EnumSet.of(Options.OPTIMIZE,Options.SCCP,Options.REGALLOC);
24+
public static final EnumSet<Options> OPT = EnumSet.of(Options.OPTIMIZE,Options.SCCP,Options.CCP,Options.REGALLOC);
2325
public static final EnumSet<Options> OPT_VERBOSE = EnumSet.allOf(Options.class);
2426
}

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ public SSADef(Instruction instruction) {
2828
this.instruction = instruction;
2929
this.useList = new ArrayList<>();
3030
}
31+
32+
public void addUse(Instruction instruction) {
33+
useList.add(instruction);
34+
}
3135
}
3236

3337
public static Map<Register, SSADef> buildDefUseChains(CompiledFunction function) {
@@ -41,6 +45,14 @@ public static Map<Register, SSADef> buildDefUseChains(CompiledFunction function)
4145
return defUseChains;
4246
}
4347

48+
public static SSADef addDef(Map<Register, SSADef> defUseChains, Register register, Instruction instruction) {
49+
if (defUseChains.get(register) != null)
50+
throw new CompilerException("Duplicate definition for register " + register);
51+
var ssaDef = new SSADef(instruction);
52+
defUseChains.put(register, ssaDef);
53+
return ssaDef;
54+
}
55+
4456
private static void recordDefs(CompiledFunction function, Map<Register, SSADef> defUseChains) {
4557
for (BasicBlock block : function.getBlocks()) {
4658
for (Instruction instruction : block.instructions) {

0 commit comments

Comments
 (0)