Skip to content

Commit f73098f

Browse files
Merge pull request #58 from CompilerProgramming/ssaexit
Implement SSA Destruction using Sreedhar / Boissinot method without coalescing
2 parents 109fdc2 + e3ae2f7 commit f73098f

File tree

7 files changed

+649
-272
lines changed

7 files changed

+649
-272
lines changed

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

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ public int whichPred(BasicBlock pred) {
158158
}
159159
throw new IllegalStateException();
160160
}
161+
public BasicBlock predecessor(int i) {
162+
if (i >= predecessors.size())
163+
return null;
164+
return predecessors.get(i);
165+
}
161166
public int whichSucc(BasicBlock succ) {
162167
int i = 0;
163168
for (BasicBlock s: successors) {
@@ -167,7 +172,17 @@ public int whichSucc(BasicBlock succ) {
167172
}
168173
throw new IllegalStateException();
169174
}
170-
175+
public void replaceInstruction(Instruction instruction, List<Instruction> replacements) {
176+
int i;
177+
for (i = 0; i < instructions.size(); i++)
178+
if (instructions.get(i) == instruction)
179+
break;
180+
assert i < instructions.size();
181+
for (int j = replacements.size()-1; j >= 0; j--) {
182+
instructions.add(i+1, replacements.get(j));
183+
}
184+
instructions.remove(i);
185+
}
171186
public static StringBuilder toStr(StringBuilder sb, BasicBlock bb, BitSet visited, boolean dumpLiveness)
172187
{
173188
if (visited.get(bb.bid))

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

Lines changed: 4 additions & 271 deletions
Original file line numberDiff line numberDiff line change
@@ -4,279 +4,12 @@
44

55
/**
66
* Converts from SSA form to non-SSA form.
7-
* Implementation is based on description in
8-
* 'Practical Improvements to the Construction and Destruction
9-
* of Static Single Assignment Form' by Preston Briggs.
10-
*
11-
* The JikesRVM LeaveSSA implements a version of the
12-
* same algorithm.
137
*/
148
public class ExitSSA {
15-
16-
CompiledFunction function;
17-
NameStack[] stacks;
18-
DominatorTree tree;
19-
209
public ExitSSA(CompiledFunction function, EnumSet<Options> options) {
21-
this.function = function;
22-
if (!function.isSSA) throw new IllegalStateException();
23-
function.livenessAnalysis();
24-
if (options.contains(Options.DUMP_SSA_LIVENESS)) function.dumpIR(true, "SSA Liveness Analysis");
25-
tree = new DominatorTree(function.entry);
26-
if (options.contains(Options.DUMP_SSA_DOMTREE)) {
27-
System.out.println("Pre SSA Dominator Tree");
28-
System.out.println(tree.generateDotOutput());
29-
}
30-
initStack();
31-
insertCopies(function.entry);
32-
removePhis();
33-
function.isSSA = false;
34-
if (options.contains(Options.DUMP_POST_SSA_IR)) function.dumpIR(false, "After exiting SSA");
35-
}
36-
37-
private void removePhis() {
38-
for (BasicBlock block : tree.blocks) {
39-
block.instructions.removeIf(instruction -> instruction instanceof Instruction.Phi);
40-
}
41-
}
42-
43-
/* Algorithm for iterating through blocks to perform phi replacement */
44-
private void insertCopies(BasicBlock block) {
45-
List<Integer> pushed = new ArrayList<>();
46-
for (Instruction i: block.instructions) {
47-
// replace all uses u with stacks[i]
48-
replaceUses(i);
49-
}
50-
scheduleCopies(block, pushed);
51-
for (BasicBlock c: block.dominatedChildren) {
52-
insertCopies(c);
53-
}
54-
for (Integer name: pushed) {
55-
stacks[name].pop();
56-
}
57-
}
58-
59-
/**
60-
* replace all uses u with stacks[i]
61-
*/
62-
private void replaceUses(Instruction i) {
63-
if (i instanceof Instruction.Phi)
64-
// FIXME check this can never be valid
65-
// tests 8/9 in TestInterpreter invoke on Phi but
66-
// replacements are same as existing inputs
67-
return;
68-
var oldUses = i.uses();
69-
Register[] newUses = new Register[oldUses.size()];
70-
for (int u = 0; u < oldUses.size(); u++) {
71-
Register use = oldUses.get(u);
72-
if (!stacks[use.id].isEmpty())
73-
newUses[u] = stacks[use.id].top();
74-
else
75-
newUses[u] = use;
76-
}
77-
i.replaceUses(newUses);
78-
}
79-
80-
static class CopyItem {
81-
/** Phi input can be a register or a constant so we record the operand */
82-
final Operand src;
83-
/** The phi destination */
84-
final Register dest;
85-
/** The basic block where the phi was present */
86-
final BasicBlock destBlock;
87-
boolean removed;
88-
89-
public CopyItem(Operand src, Register dest, BasicBlock destBlock) {
90-
this.src = src;
91-
this.dest = dest;
92-
this.destBlock = destBlock;
93-
this.removed = false;
94-
}
95-
}
96-
97-
private void scheduleCopies(BasicBlock block, List<Integer> pushed) {
98-
/* Pass 1 - Initialize data structures */
99-
/* In this pass we count the number of times a name is used by other phi-nodes */
100-
List<CopyItem> copySet = new ArrayList<>();
101-
Map<Integer, Register> map = new HashMap<>();
102-
BitSet usedByAnother = new BitSet(function.registerPool.numRegisters()*2);
103-
for (BasicBlock s: block.successors) {
104-
int j = s.whichPred(block);
105-
for (Instruction.Phi phi: s.phis()) {
106-
Register dst = phi.value();
107-
Operand srcOperand = phi.input(j); // jth operand of phi node
108-
if (srcOperand instanceof Operand.RegisterOperand srcRegisterOperand) {
109-
Register src = srcRegisterOperand.reg;
110-
map.put(src.id, src);
111-
usedByAnother.set(src.id);
112-
}
113-
copySet.add(new CopyItem(srcOperand, dst, s));
114-
map.put(dst.id, dst);
115-
}
116-
}
117-
118-
/* Pass 2: setup up the worklist of initial copies */
119-
/* In this pass we build a worklist of names that are not used in other phi nodes */
120-
List<CopyItem> workList = new ArrayList<>();
121-
for (CopyItem copyItem: copySet) {
122-
if (usedByAnother.get(copyItem.dest.id) != true) {
123-
copyItem.removed = true;
124-
workList.add(copyItem);
125-
}
126-
}
127-
copySet.removeIf(copyItem -> copyItem.removed);
128-
129-
/* Pass 3: iterate over the worklist, inserting copies */
130-
/* Copy operations whose destinations are not used by other copy operations can be scheduled immediately */
131-
/* Each time we insert a copy operation we add the source of that op to the worklist */
132-
while (!workList.isEmpty() || !copySet.isEmpty()) {
133-
while (!workList.isEmpty()) {
134-
final CopyItem copyItem = workList.remove(0);
135-
final Operand src = copyItem.src;
136-
final Register dest = copyItem.dest;
137-
final BasicBlock destBlock = copyItem.destBlock;
138-
/* Engineering a Compiler: We can avoid the lost copy
139-
problem by checking the liveness of the target name
140-
for each copy that we try to insert. When we discover
141-
a copy target that is live, we must preserve the live
142-
value in a temporary name and rewrite subsequent uses to
143-
refer to the temporary name.
144-
145-
This captures the cases when the result of a phi
146-
in a control successor is live on exit of the current block.
147-
This means that it is incorrect to simply insert a copy
148-
of the destination in the current block. So we rename
149-
the destination to a new temporary, and record the renaming
150-
so that the dominator blocks get the new name. Comment adapted
151-
from JikesRVM LeaveSSA
152-
*/
153-
if (block.liveOut.get(dest.id)) {
154-
/* Insert a copy from dest to a new temp t at phi node defining dest */
155-
final Register t = addMoveToTempAfterPhi(destBlock, dest);
156-
stacks[dest.id].push(t); // record the temp name
157-
pushed.add(dest.id);
158-
}
159-
/* Insert a copy operation from map[src] to dest at end of BB */
160-
if (src instanceof Operand.RegisterOperand srcRegisterOperand) {
161-
addMoveAtBBEnd(block, map.get(srcRegisterOperand.reg.id), dest);
162-
map.put(srcRegisterOperand.reg.id, dest);
163-
/* If src is the name of a dest in copySet add item to worklist */
164-
/* see comment on phi cycles below. */
165-
CopyItem item = isCycle(copySet, srcRegisterOperand.reg);
166-
if (item != null) {
167-
workList.add(item);
168-
}
169-
}
170-
else if (src instanceof Operand.ConstantOperand srcConstantOperand) {
171-
addMoveAtBBEnd(block, srcConstantOperand, dest);
172-
}
173-
}
174-
/* Engineering a Compiler: To solve the swap problem
175-
we can detect cases where phi functions reference the
176-
targets of other phi functions in the same block. For each
177-
cycle of references, it must insert a copy to a temporary
178-
that breaks the cycle. Then we can schedule the copies to
179-
respect the dependencies implied by the phi functions.
180-
181-
An empty work list with work remaining in the copy set
182-
implies a cycle in the dependencies amongst copies. To break
183-
the cycle copy the destination of an arbitrary member of the
184-
copy set to a temporary. This destination has therefore been
185-
saved and can be safely overwritten. So then add the copy to the
186-
work list. Comment adapted from JikesRVM LeaveSSA.
187-
*/
188-
if (!copySet.isEmpty()) {
189-
CopyItem copyItem = copySet.remove(0);
190-
/* Insert a copy from dst to new temp at the end of Block */
191-
Register t = addMoveToTempAtBBEnd(block, copyItem.dest);
192-
map.put(copyItem.dest.id, t);
193-
workList.add(copyItem);
194-
}
195-
}
196-
}
197-
198-
private void insertAtEnd(BasicBlock bb, Instruction i) {
199-
assert bb.instructions.size() > 0;
200-
// Last instruction is a branch - so new instruction will
201-
// go before that
202-
int pos = bb.instructions.size()-1;
203-
bb.add(pos, i);
204-
}
205-
206-
private void insertAfterPhi(BasicBlock bb, Register phiDef, Instruction newInst) {
207-
assert bb.instructions.size() > 0;
208-
int insertionPos = -1;
209-
for (int pos = 0; pos < bb.instructions.size(); pos++) {
210-
Instruction i = bb.instructions.get(pos);
211-
if (i instanceof Instruction.Phi phi) {
212-
if (phi.value().id == phiDef.id) {
213-
insertionPos = pos+1; // After phi
214-
break;
215-
}
216-
}
217-
}
218-
if (insertionPos < 0) {
219-
throw new IllegalStateException();
220-
}
221-
bb.add(insertionPos, newInst);
222-
}
223-
224-
/* Insert a copy from dest to new temp at end of BB, and return temp */
225-
private Register addMoveToTempAtBBEnd(BasicBlock block, Register dest) {
226-
var temp = function.registerPool.newTempReg(dest.name(), dest.type);
227-
var inst = new Instruction.Move(new Operand.RegisterOperand(dest), new Operand.RegisterOperand(temp));
228-
insertAtEnd(block, inst);
229-
return temp;
230-
}
231-
232-
/* If src is the name of a dest in copySet remove the item */
233-
private CopyItem isCycle(List<CopyItem> copySet, Register src) {
234-
for (int i = 0; i < copySet.size(); i++) {
235-
CopyItem copyItem = copySet.get(i);
236-
if (copyItem.dest.id == src.id) {
237-
copySet.remove(i);
238-
return copyItem;
239-
}
240-
}
241-
return null;
242-
}
243-
244-
/* Insert a copy from src to dst at end of BB */
245-
private void addMoveAtBBEnd(BasicBlock block, Register src, Register dest) {
246-
var inst = new Instruction.Move(new Operand.RegisterOperand(src), new Operand.RegisterOperand(dest));
247-
insertAtEnd(block, inst);
248-
// If the copy instruction is followed by a cbr which uses the old var
249-
// then we need to update the cbr instruction
250-
// This is not specified in the Briggs paper but t
251-
var brInst = block.instructions.getLast();
252-
if (brInst instanceof Instruction.ConditionalBranch cbr) {
253-
cbr.replaceUse(src,dest);
254-
}
255-
}
256-
/* Insert a copy from constant src to dst at end of BB */
257-
private void addMoveAtBBEnd(BasicBlock block, Operand.ConstantOperand src, Register dest) {
258-
var inst = new Instruction.Move(src, new Operand.RegisterOperand(dest));
259-
insertAtEnd(block, inst);
260-
}
261-
/* Insert a copy dest to a new temp at phi node defining dest, return temp */
262-
private Register addMoveToTempAfterPhi(BasicBlock block, Register dest) {
263-
var temp = function.registerPool.newTempReg(dest.name(), dest.type);
264-
var inst = new Instruction.Move(new Operand.RegisterOperand(dest), new Operand.RegisterOperand(temp));
265-
insertAfterPhi(block, dest, inst);
266-
return temp;
267-
}
268-
269-
private void initStack() {
270-
stacks = new NameStack[function.registerPool.numRegisters()];
271-
for (int i = 0; i < stacks.length; i++)
272-
stacks[i] = new NameStack();
273-
}
274-
275-
static class NameStack {
276-
List<Register> stack = new ArrayList<>();
277-
void push(Register r) { stack.add(r); }
278-
Register top() { return stack.getLast(); }
279-
void pop() { stack.removeLast(); }
280-
boolean isEmpty() { return stack.isEmpty(); }
10+
if (options.contains(Options.SSA_DESTRUCTION_BOISSINOT_NOCOALESCE))
11+
new ExitSSABoissinotNoCoalesce(function,options);
12+
else
13+
new ExitSSABriggs(function,options);
28114
}
28215
}

0 commit comments

Comments
 (0)