1 /*
  2  * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package jdk.incubator.code.analysis;
 27 
 28 import jdk.incubator.code.*;
 29 import jdk.incubator.code.op.CoreOp;
 30 import java.util.ArrayDeque;
 31 import java.util.ArrayList;
 32 import java.util.Deque;
 33 import java.util.HashMap;
 34 import java.util.HashSet;
 35 import java.util.LinkedHashMap;
 36 import java.util.LinkedHashSet;
 37 import java.util.List;
 38 import java.util.Map;
 39 import java.util.Set;
 40 import java.util.stream.Collectors;
 41 
 42 /**
 43  * Functionality to transform a code model into pure SSA form, replacing operations that declare variables and
 44  * access them with the use of values they depend on or additional block parameters.
 45  */
 46 public final class SSA {
 47     private SSA() {
 48     }
 49 
 50     /**
 51      * Applies an SSA transformation to an operation with bodies, replacing operations that declare variables and
 52      * access them with the use of values they depend on or additional block parameters.
 53      * <p>
 54      * The operation should first be in lowered form before applying this transformation.
 55      * <p>
 56      * Note: this implementation does not currently work correctly when a variable is stored to within an exception
 57      * region and read from outside as a result of catching an exception. In such cases a complete transformation may be
 58      * not possible and such variables will need to be retained.
 59      *
 60      * @param nestedOp the operation with bodies
 61      * @return the transformed operation
 62      * @param <T> the operation type
 63      */
 64     public static <T extends Op & Op.Nested> T transform(T nestedOp) {
 65         // @@@ property is used to test both impls
 66         if (!"cytron".equalsIgnoreCase(System.getProperty("babylon.ssa"))) {
 67             return SSAConstruction.transform(nestedOp);
 68         }
 69         Map<Block, Set<CoreOp.VarOp>> joinPoints = new HashMap<>();
 70         Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues = new HashMap<>();
 71         Map<Block.Reference, List<Object>> joinSuccessorValues = new HashMap<>();
 72 
 73         Map<Body, Boolean> visited = new HashMap<>();
 74         Map<Block, Map<CoreOp.VarOp, Block.Parameter>> joinBlockArguments = new HashMap<>();
 75         @SuppressWarnings("unchecked")
 76         T ssaOp = (T) nestedOp.transform(CopyContext.create(), (block, op) -> {
 77             // Compute join points and value mappings for body
 78             visited.computeIfAbsent(op.ancestorBody(), b -> {
 79                 findJoinPoints(b, joinPoints);
 80                 variableToValue(b, joinPoints, loadValues, joinSuccessorValues);
 81                 return true;
 82             });
 83 
 84             if (op instanceof CoreOp.VarOp || op instanceof CoreOp.VarAccessOp) {
 85                 // Drop var operations
 86                 if (op instanceof CoreOp.VarAccessOp.VarLoadOp vl) {
 87                     // Replace result of load
 88                     Object loadValue = loadValues.get(vl);
 89                     CopyContext cc = block.context();
 90                     Value v = loadValue instanceof VarOpBlockArgument vba
 91                             ? joinBlockArguments.get(vba.b()).get(vba.vop())
 92                             : cc.getValue((Value) loadValue);
 93                     cc.mapValue(op.result(), v);
 94                 }
 95             } else if (op instanceof Op.Terminating) {
 96                 for (Block.Reference s : op.successors()) {
 97                     List<Object> joinValues = joinSuccessorValues.get(s);
 98                     // Successor has join values
 99                     if (joinValues != null) {
100                         CopyContext cc = block.context();
101 
102                         // Lazily append target block arguments
103                         joinBlockArguments.computeIfAbsent(s.targetBlock(), b -> {
104                             Block.Builder bb = cc.getBlock(b);
105                             return joinPoints.get(b).stream().collect(Collectors.toMap(
106                                     varOp -> varOp,
107                                     varOp -> bb.parameter(varOp.varValueType())));
108                         });
109 
110                         // Append successor arguments
111                         List<Value> values = new ArrayList<>();
112                         for (Object o : joinValues) {
113                             Value v = o instanceof VarOpBlockArgument vba
114                                     ? joinBlockArguments.get(vba.b()).get(vba.vop())
115                                     : cc.getValue((Value) o);
116                             values.add(v);
117                         }
118 
119                         // Map successor with append arguments
120                         List<Value> toArgs = cc.getValues(s.arguments());
121                         toArgs.addAll(values);
122                         Block.Reference toS = cc.getBlock(s.targetBlock()).successor(toArgs);
123                         cc.mapSuccessor(s, toS);
124                     }
125                 }
126 
127                 block.apply(op);
128             } else {
129                 block.apply(op);
130             }
131 
132             return block;
133         });
134         return ssaOp;
135     }
136 
137     record VarOpBlockArgument(Block b, CoreOp.VarOp vop) {
138     }
139 
140     enum Uninitialized {
141         UNINITIALIZED;
142     }
143 
144     // @@@ Check for var uses in exception regions
145     //     A variable cannot be converted to SAA form if the variable is stored
146     //     to in an exception region and accessed from an associated catch region
147 
148     static void variableToValue(Body body,
149                                 Map<Block, Set<CoreOp.VarOp>> joinPoints,
150                                 Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues,
151                                 Map<Block.Reference, List<Object>> joinSuccessorValues) {
152         Map<CoreOp.VarOp, Deque<Object>> variableStack = new HashMap<>();
153         Node top = buildDomTree(body.entryBlock(), body.immediateDominators());
154         variableToValue(top, variableStack, joinPoints, loadValues, joinSuccessorValues);
155     }
156 
157     /**
158      * Replaces usages of a variable with the corresponding value, from a given block node in the dominator tree.
159      * <p>
160      * The result of a {@code VarLoadOp} for variable, {@code V} say the result of a {@code VarOp} operation,
161      * is replaced with the value passed as an operand to the immediately dominating {@code VarStoreOp} that operates
162      * on {@code V}, or a block argument representing the equivalent of a phi-value of {@code V}.
163      * After which, any related {@code VarOp}, {@code VarLoadOp}, or {@code VarStoreOp} operations are removed.
164      *
165      * @param n             the node in the dominator tree
166      * @param variableStack the variable stack
167      * @param joinPoints    the join points
168      * @implNote See "Efficiently Computing Static Single Assignment Form and the Control Dependence Graph" by Ron Cytron et. al.
169      * Section 5.2 and Figure 12.
170      */
171     static void variableToValue(Node n,
172                                 Map<CoreOp.VarOp, Deque<Object>> variableStack,
173                                 Map<Block, Set<CoreOp.VarOp>> joinPoints,
174                                 Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues,
175                                 Map<Block.Reference, List<Object>> joinSuccessorValues) {
176         int size = n.b().ops().size();
177 
178         // Check if V is associated with block argument (phi)
179         // Push argument onto V's stack
180         {
181             Set<CoreOp.VarOp> varOps = joinPoints.get(n.b());
182             if (varOps != null) {
183                 varOps.forEach(v -> {
184                     assert variableStack.containsKey(v);
185                     variableStack.get(v).push(new VarOpBlockArgument(n.b(), v));
186                 });
187             }
188         }
189 
190         {
191             for (int i = 0; i < size - 1; i++) {
192                 Op op = n.b().ops().get(i);
193 
194                 if (op instanceof CoreOp.VarOp varOp) {
195                     // Initial value assigned to variable
196                     Object current = varOp.isUninitialized()
197                             ? Uninitialized.UNINITIALIZED
198                             : op.operands().get(0);
199                     assert !variableStack.containsKey(varOp);
200                     variableStack.computeIfAbsent(varOp, _ -> new ArrayDeque<>())
201                             .push(current);
202                 } else if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) {
203                     // Value assigned to variable
204                     Value current = op.operands().get(1);
205                     variableStack.get(storeOp.varOp()).push(current);
206                 } else if (op instanceof CoreOp.VarAccessOp.VarLoadOp loadOp &&
207                            loadOp.varOp().ancestorBody() == op.ancestorBody()) {
208                     Object to = peekAtCurrentVariable(variableStack, loadOp.varOp());
209                     loadValues.put(loadOp, to);
210                 } else if (op instanceof Op.Nested) {
211                     // Traverse descendant variable loads for variables
212                     // declared in the block's parent body
213                     op.traverse(null, (_, ce) -> {
214                         if (ce instanceof CoreOp.VarAccessOp.VarLoadOp loadOp &&
215                                 loadOp.varOp().ancestorBody() == op.ancestorBody()) {
216                             Object to = peekAtCurrentVariable(variableStack, loadOp.varOp());
217                             loadValues.put(loadOp, to);
218                         }
219                         return null;
220                     });
221                 }
222             }
223 
224             // Add successor args for joint points
225             for (Block.Reference succ : n.b().successors()) {
226                 Set<CoreOp.VarOp> varOps = joinPoints.get(succ.targetBlock());
227                 if (varOps != null) {
228                     List<Object> joinValues = varOps.stream()
229                             .map(vop -> peekAtCurrentVariable(variableStack, vop)).toList();
230                     joinSuccessorValues.put(succ, joinValues);
231                 }
232             }
233 
234             // The result of a VarOp, a variable value, can only be used in VarStoreOp and VarLoadOp
235             // therefore there is no need to check existing successor arguments
236         }
237 
238         // Traverse children of dom tree
239         for (Node y : n.children()) {
240             variableToValue(y, variableStack, joinPoints, loadValues, joinSuccessorValues);
241         }
242 
243         // Pop off values for variables
244         {
245             Set<CoreOp.VarOp> varOps = joinPoints.get(n.b());
246             if (varOps != null) {
247                 varOps.forEach(v -> {
248                     variableStack.get(v).pop();
249                 });
250             }
251 
252             for (int i = 0; i < size - 1; i++) {
253                 Op op = n.b().ops().get(i);
254 
255                 if (op instanceof CoreOp.VarOp varOp) {
256                     variableStack.get(varOp).pop();
257                 } else if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) {
258                     variableStack.get(storeOp.varOp()).pop();
259                 }
260             }
261         }
262     }
263 
264     static Object peekAtCurrentVariable(Map<CoreOp.VarOp, Deque<Object>> variableStack, CoreOp.VarOp vop) {
265         Object to = variableStack.get(vop).peek();
266         return throwIfUninitialized(vop, to);
267     }
268 
269     static Object throwIfUninitialized(CoreOp.VarOp vop, Object to) {
270         if (to instanceof Uninitialized) {
271             throw new IllegalStateException("Loading from uninitialized variable: " + vop);
272         }
273         return to;
274     }
275 
276     /**
277      * Finds the join points of a body.
278      * <p>
279      * A join point is a block that is in the dominance frontier of one or more predecessors, that make one or more
280      * stores to variables (using the {@code VarStoreOp} operation on the result of a {@code VarOp} operation).
281      * The join point contains the set variables ({@code VarOp} operations) that are stored to.
282      * <p>
283      * A variable of a joint point indicates that a block argument may need to be added to the join point's block
284      * when converting variables to SSA form. Different values of a variable may occur at different control flow
285      * paths at the join point. The block argument represents the convergence of multiple values for the same
286      * variable, where a predecessor assigns to the block argument.
287      * (Block arguments are equivalent to phi-values, or phi-nodes, used in other representations.)
288      *
289      * @param body the body.
290      * @param joinPoints the returned join points.
291      * @implNote See "Efficiently Computing Static Single Assignment Form and the Control Dependence Graph" by Ron Cytron et. al.
292      * Section 5.1 and Figure 11.
293      */
294     public static void findJoinPoints(Body body, Map<Block, Set<CoreOp.VarOp>> joinPoints) {
295         Map<Block, Set<Block>> df = body.dominanceFrontier();
296         Map<CoreOp.VarOp, Set<Block>> a = findVarStores(body);
297 
298         int iterCount = 0;
299         int[] hasAlready = new int[body.blocks().size()];
300         int[] work = new int[body.blocks().size()];
301 
302         Deque<Block> w = new ArrayDeque<>();
303 
304         for (CoreOp.VarOp v : a.keySet()) {
305             iterCount++;
306 
307             for (Block x : a.get(v)) {
308                 work[x.index()] = iterCount;
309                 w.push(x);
310             }
311 
312             while (!w.isEmpty()) {
313                 Block x = w.pop();
314 
315                 for (Block y : df.getOrDefault(x, Set.of())) {
316                     if (hasAlready[y.index()] < iterCount) {
317                         // Only add to the join points if y is dominated by the var's block
318                         if (y.isDominatedBy(v.parentBlock())) {
319                             joinPoints.computeIfAbsent(y, _k -> new LinkedHashSet<>()).add(v);
320                         }
321                         hasAlready[y.index()] = iterCount;
322 
323                         if (work[y.index()] < iterCount) {
324                             work[y.index()] = iterCount;
325                             w.push(y);
326                         }
327                     }
328                 }
329             }
330         }
331     }
332 
333     // Returns map of variable to blocks that contain stores to the variables declared in the body
334     // Throws ISE if a descendant store operation is encountered
335     // @@@ Compute map for whole tree, then traverse keys with filter
336     static Map<CoreOp.VarOp, Set<Block>> findVarStores(Body r) {
337         return r.traverse(new LinkedHashMap<>(), CodeElement.opVisitor((stores, op) -> {
338             if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) {
339                 if (storeOp.varOp().ancestorBody() != storeOp.ancestorBody()) {
340                     throw new IllegalStateException("Descendant variable store operation");
341                 }
342                 if (storeOp.varOp().ancestorBody() == r) {
343                     stores.computeIfAbsent(storeOp.varOp(), _v -> new LinkedHashSet<>()).add(storeOp.parentBlock());
344                 }
345             }
346             return stores;
347         }));
348     }
349 
350     record Node(Block b, Set<Node> children) {
351     }
352 
353     static Node buildDomTree(Block entryBlock, Map<Block, Block> idoms) {
354         Map<Block, Node> tree = new HashMap<>();
355         for (Map.Entry<Block, Block> e : idoms.entrySet()) {
356             Block id = e.getValue();
357             Block b = e.getKey();
358 
359             Node parent = tree.computeIfAbsent(id, _k -> new Node(_k, new HashSet<>()));
360             if (b == entryBlock) {
361                 continue;
362             }
363 
364             Node child = tree.computeIfAbsent(b, _k -> new Node(_k, new HashSet<>()));
365             parent.children.add(child);
366         }
367         return tree.get(entryBlock);
368     }
369 }