|
4 | 4 |
|
5 | 5 | /**
|
6 | 6 | * 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. |
13 | 7 | */
|
14 | 8 | public class ExitSSA {
|
15 |
| - |
16 |
| - CompiledFunction function; |
17 |
| - NameStack[] stacks; |
18 |
| - DominatorTree tree; |
19 |
| - |
20 | 9 | 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); |
281 | 14 | }
|
282 | 15 | }
|
0 commit comments