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 java.lang.reflect.code.analysis; 27 28 import java.lang.reflect.code.*; 29 import java.lang.reflect.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 invokable operation, 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 iop the invokable operation 61 * @return the transformed operation 62 * @param <T> the invokable type 63 */ 64 public static <T extends Op & Op.Invokable> T transform(T iop) { 65 Map<Block, Set<CoreOp.VarOp>> joinPoints = new HashMap<>(); 66 Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues = new HashMap<>(); 67 Map<Block.Reference, List<Object>> joinSuccessorValues = new HashMap<>(); 68 69 Map<Body, Boolean> visited = new HashMap<>(); 70 Map<Block, Map<CoreOp.VarOp, Block.Parameter>> joinBlockArguments = new HashMap<>(); 71 @SuppressWarnings("unchecked") 72 T liop = (T) iop.transform(CopyContext.create(), (block, op) -> { 73 // Compute join points and value mappings for body 74 visited.computeIfAbsent(op.ancestorBody(), b -> { 75 findJoinPoints(b, joinPoints); 76 variableToValue(b, joinPoints, loadValues, joinSuccessorValues); 77 return true; 78 }); 79 80 if (op instanceof CoreOp.VarOp || op instanceof CoreOp.VarAccessOp) { 81 // Drop var operations 82 if (op instanceof CoreOp.VarAccessOp.VarLoadOp vl) { 83 // Replace result of load 84 Object loadValue = loadValues.get(vl); 85 CopyContext cc = block.context(); 86 Value v = loadValue instanceof VarOpBlockArgument vba 87 ? joinBlockArguments.get(vba.b()).get(vba.vop()) 88 : cc.getValue((Value) loadValue); 89 cc.mapValue(op.result(), v); 90 } 91 } else if (op instanceof Op.Terminating) { 92 for (Block.Reference s : op.successors()) { 93 List<Object> joinValues = joinSuccessorValues.get(s); 94 // Successor has join values 95 if (joinValues != null) { 96 CopyContext cc = block.context(); 97 98 // Lazily append target block arguments 99 joinBlockArguments.computeIfAbsent(s.targetBlock(), b -> { 100 Block.Builder bb = cc.getBlock(b); 101 return joinPoints.get(b).stream().collect(Collectors.toMap( 102 varOp -> varOp, 103 varOp -> bb.parameter(varOp.varType()))); 104 }); 105 106 // Append successor arguments 107 List<Value> values = new ArrayList<>(); 108 for (Object o : joinValues) { 109 Value v = o instanceof VarOpBlockArgument vba 110 ? joinBlockArguments.get(vba.b()).get(vba.vop()) 111 : cc.getValue((Value) o); 112 values.add(v); 113 } 114 115 // Map successor with append arguments 116 List<Value> toArgs = cc.getValues(s.arguments()); 117 toArgs.addAll(values); 118 Block.Reference toS = cc.getBlock(s.targetBlock()).successor(toArgs); 119 cc.mapSuccessor(s, toS); 120 } 121 } 122 123 block.apply(op); 124 } else { 125 block.apply(op); 126 } 127 128 return block; 129 }); 130 return liop; 131 } 132 133 record VarOpBlockArgument(Block b, CoreOp.VarOp vop) { 134 } 135 136 // @@@ Check for var uses in exception regions 137 // A variable cannot be converted to SAA form if the variable is stored 138 // to in an exception region and accessed from an associated catch region 139 140 static void variableToValue(Body body, 141 Map<Block, Set<CoreOp.VarOp>> joinPoints, 142 Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues, 143 Map<Block.Reference, List<Object>> joinSuccessorValues) { 144 Map<CoreOp.VarOp, Deque<Object>> variableStack = new HashMap<>(); 145 Node top = buildDomTree(body.entryBlock(), body.immediateDominators()); 146 variableToValue(top, variableStack, joinPoints, loadValues, joinSuccessorValues); 147 } 148 149 /** 150 * Replaces usages of a variable with the corresponding value, from a given block node in the dominator tree. 151 * <p> 152 * The result of a {@code VarLoadOp} for variable, {@code V} say the result of a {@code VarOp} operation, 153 * is replaced with the value passed as an operand to the immediately dominating {@code VarStoreOp} that operates 154 * on {@code V}, or a block argument representing the equivalent of a phi-value of {@code V}. 155 * After which, any related {@code VarOp}, {@code VarLoadOp}, or {@code VarStoreOp} operations are removed. 156 * 157 * @param n the node in the dominator tree 158 * @param variableStack the variable stack 159 * @param joinPoints the join points 160 * @implNote See "Efficiently Computing Static Single Assignment Form and the Control Dependence Graph" by Ron Cytron et. al. 161 * Section 5.2 and Figure 12. 162 */ 163 static void variableToValue(Node n, 164 Map<CoreOp.VarOp, Deque<Object>> variableStack, 165 Map<Block, Set<CoreOp.VarOp>> joinPoints, 166 Map<CoreOp.VarAccessOp.VarLoadOp, Object> loadValues, 167 Map<Block.Reference, List<Object>> joinSuccessorValues) { 168 int size = n.b().ops().size(); 169 170 // Check if V is associated with block argument (phi) 171 // Push argument onto V's stack 172 { 173 Set<CoreOp.VarOp> varOps = joinPoints.get(n.b()); 174 if (varOps != null) { 175 varOps.forEach(v -> { 176 assert variableStack.get(v) != null; 177 variableStack.get(v).push(new VarOpBlockArgument(n.b(), v)); 178 }); 179 } 180 } 181 182 { 183 for (int i = 0; i < size - 1; i++) { 184 Op op = n.b().ops().get(i); 185 186 if (op instanceof CoreOp.VarOp varOp) { 187 // Initial value assigned to variable 188 Value current = op.operands().get(0); 189 variableStack.computeIfAbsent(varOp, _k -> new ArrayDeque<>()) 190 .push(current); 191 } else if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) { 192 // Value assigned to variable 193 Value current = op.operands().get(1); 194 variableStack.computeIfAbsent(storeOp.varOp(), _k -> new ArrayDeque<>()) 195 .push(current); 196 } else if (op instanceof CoreOp.VarAccessOp.VarLoadOp loadOp) { 197 Object to = variableStack.get(loadOp.varOp()).peek(); 198 loadValues.put(loadOp, to); 199 } else if (op instanceof Op.Nested) { 200 // Traverse descendant variable loads for variables 201 // declared in the block's parent body 202 op.traverse(null, (o, codeElement) -> { 203 if (o instanceof CoreOp.VarAccessOp.VarLoadOp loadOp && 204 loadOp.varOp().ancestorBody() == op.ancestorBody()) { 205 Object to = variableStack.get(loadOp.varOp()).peek(); 206 loadValues.put(loadOp, to); 207 } 208 return null; 209 }); 210 } 211 } 212 213 // Add successor args for joint points 214 for (Block.Reference succ : n.b().successors()) { 215 Set<CoreOp.VarOp> varOps = joinPoints.get(succ.targetBlock()); 216 if (varOps != null) { 217 List<Object> joinValues = varOps.stream() 218 .map(vop -> variableStack.get(vop).peek()).toList(); 219 joinSuccessorValues.put(succ, joinValues); 220 } 221 } 222 223 // The result of a VarOp, a variable value, can only be used in VarStoreOp and VarLoadOp 224 // therefore there is no need to check existing successor arguments 225 } 226 227 // Traverse children of dom tree 228 for (Node y : n.children()) { 229 variableToValue(y, variableStack, joinPoints, loadValues, joinSuccessorValues); 230 } 231 232 // Pop off values for variables 233 { 234 Set<CoreOp.VarOp> varOps = joinPoints.get(n.b()); 235 if (varOps != null) { 236 varOps.forEach(v -> { 237 variableStack.get(v).pop(); 238 }); 239 } 240 241 for (int i = 0; i < size - 1; i++) { 242 Op op = n.b().ops().get(i); 243 244 if (op instanceof CoreOp.VarOp varOp) { 245 variableStack.get(varOp).pop(); 246 } else if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) { 247 variableStack.get(storeOp.varOp()).pop(); 248 } 249 } 250 } 251 } 252 253 /** 254 * Finds the join points of a body. 255 * <p> 256 * A join point is a block that is in the dominance frontier of one or more predecessors, that make one or more 257 * stores to variables (using the {@code VarStoreOp} operation on the result of a {@code VarOp} operation). 258 * The join point contains the set variables ({@code VarOp} operations) that are stored to. 259 * <p> 260 * A variable of a joint point indicates that a block argument may need to be added to the join point's block 261 * when converting variables to SSA form. Different values of a variable may occur at different control flow 262 * paths at the join point. The block argument represents the convergence of multiple values for the same 263 * variable, where a predecessor assigns to the block argument. 264 * (Block arguments are equivalent to phi-values, or phi-nodes, used in other representations.) 265 * 266 * @param body the body. 267 * @param joinPoints the returned join points. 268 * @implNote See "Efficiently Computing Static Single Assignment Form and the Control Dependence Graph" by Ron Cytron et. al. 269 * Section 5.1 and Figure 11. 270 */ 271 public static void findJoinPoints(Body body, Map<Block, Set<CoreOp.VarOp>> joinPoints) { 272 Map<Block, Set<Block>> df = body.dominanceFrontier(); 273 Map<CoreOp.VarOp, Set<Block>> a = findVarStores(body); 274 275 int iterCount = 0; 276 int[] hasAlready = new int[body.blocks().size()]; 277 int[] work = new int[body.blocks().size()]; 278 279 Deque<Block> w = new ArrayDeque<>(); 280 281 for (CoreOp.VarOp v : a.keySet()) { 282 iterCount++; 283 284 for (Block x : a.get(v)) { 285 work[x.index()] = iterCount; 286 w.push(x); 287 } 288 289 while (!w.isEmpty()) { 290 Block x = w.pop(); 291 292 for (Block y : df.getOrDefault(x, Set.of())) { 293 if (hasAlready[y.index()] < iterCount) { 294 // Only add to the join points if y is dominated by the var's block 295 if (y.isDominatedBy(v.parentBlock())) { 296 joinPoints.computeIfAbsent(y, _k -> new LinkedHashSet<>()).add(v); 297 } 298 hasAlready[y.index()] = iterCount; 299 300 if (work[y.index()] < iterCount) { 301 work[y.index()] = iterCount; 302 w.push(y); 303 } 304 } 305 } 306 } 307 } 308 } 309 310 // Returns map of variable to blocks that contain stores to the variables declared in the body 311 // Throws ISE if a descendant store operation is encountered 312 // @@@ Compute map for whole tree, then traverse keys with filter 313 static Map<CoreOp.VarOp, Set<Block>> findVarStores(Body r) { 314 return r.traverse(new LinkedHashMap<>(), CodeElement.opVisitor((stores, op) -> { 315 if (op instanceof CoreOp.VarAccessOp.VarStoreOp storeOp) { 316 if (storeOp.varOp().ancestorBody() != storeOp.ancestorBody()) { 317 throw new IllegalStateException("Descendant variable store operation"); 318 } 319 if (storeOp.varOp().ancestorBody() == r) { 320 stores.computeIfAbsent(storeOp.varOp(), _v -> new LinkedHashSet<>()).add(storeOp.parentBlock()); 321 } 322 } 323 return stores; 324 })); 325 } 326 327 record Node(Block b, Set<Node> children) { 328 } 329 330 static Node buildDomTree(Block entryBlock, Map<Block, Block> idoms) { 331 Map<Block, Node> tree = new HashMap<>(); 332 for (Map.Entry<Block, Block> e : idoms.entrySet()) { 333 Block id = e.getValue(); 334 Block b = e.getKey(); 335 336 Node parent = tree.computeIfAbsent(id, _k -> new Node(_k, new HashSet<>())); 337 if (b == entryBlock) { 338 continue; 339 } 340 341 Node child = tree.computeIfAbsent(b, _k -> new Node(_k, new HashSet<>())); 342 parent.children.add(child); 343 } 344 return tree.get(entryBlock); 345 } 346 }