1 /* 2 * Copyright (c) 2014, 2019, 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. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 package org.graalvm.compiler.core.match; 26 27 import static org.graalvm.compiler.core.common.cfg.AbstractControlFlowGraph.dominates; 28 import static org.graalvm.compiler.core.common.cfg.AbstractControlFlowGraph.strictlyDominates; 29 import static org.graalvm.compiler.debug.DebugOptions.LogVerbose; 30 31 import java.util.ArrayList; 32 import java.util.Arrays; 33 import java.util.Iterator; 34 import java.util.List; 35 36 import jdk.internal.vm.compiler.collections.EconomicMap; 37 import jdk.internal.vm.compiler.collections.Equivalence; 38 import org.graalvm.compiler.core.common.cfg.BlockMap; 39 import org.graalvm.compiler.core.gen.NodeLIRBuilder; 40 import org.graalvm.compiler.core.match.MatchPattern.Result; 41 import org.graalvm.compiler.debug.CounterKey; 42 import org.graalvm.compiler.debug.DebugContext; 43 import org.graalvm.compiler.debug.GraalError; 44 import org.graalvm.compiler.graph.Node; 45 import org.graalvm.compiler.graph.NodeMap; 46 import org.graalvm.compiler.nodes.PhiNode; 47 import org.graalvm.compiler.nodes.StructuredGraph; 48 import org.graalvm.compiler.nodes.calc.FloatingNode; 49 import org.graalvm.compiler.nodes.cfg.Block; 50 import org.graalvm.compiler.nodes.virtual.VirtualObjectNode; 51 52 /** 53 * Container for state captured during a match. 54 */ 55 public class MatchContext { 56 private static final CounterKey MatchContextSuccessDifferentBlocks = DebugContext.counter("MatchContextSuccessDifferentBlocks"); 57 58 private final Node root; 59 60 private final MatchStatement rule; 61 private final StructuredGraph.ScheduleResult schedule; 62 63 private EconomicMap<String, NamedNode> namedNodes; 64 65 /** 66 * A node consumed by a match. Keeps track of whether side effects can be ignored. 67 */ 68 static final class ConsumedNode { 69 final Node node; 70 final boolean ignoresSideEffects; 71 72 ConsumedNode(Node node, boolean ignoresSideEffects) { 73 this.node = node; 74 this.ignoresSideEffects = ignoresSideEffects; 75 } 76 } 77 78 /** 79 * The collection of nodes consumed by this match. 80 */ 81 static final class ConsumedNodes implements Iterable<ConsumedNode> { 82 private ArrayList<ConsumedNode> nodes; 83 84 ConsumedNodes() { 85 this.nodes = null; 86 } 87 88 public void add(Node node, boolean ignoresSideEffects) { 89 if (nodes == null) { 90 nodes = new ArrayList<>(2); 91 } 92 nodes.add(new ConsumedNode(node, ignoresSideEffects)); 93 } 94 95 public boolean contains(Node node) { 96 for (ConsumedNode c : nodes) { 97 if (c.node == node) { 98 return true; 99 } 100 } 101 return false; 102 } 103 104 public ConsumedNode find(Node node) { 105 for (ConsumedNode c : nodes) { 106 if (c.node == node) { 107 return c; 108 } 109 } 110 return null; 111 } 112 113 @Override 114 public String toString() { 115 Node[] arr = new Node[nodes.size()]; 116 int i = 0; 117 for (ConsumedNode c : nodes) { 118 arr[i++] = c.node; 119 } 120 return Arrays.toString(arr); 121 } 122 123 @Override 124 public Iterator<ConsumedNode> iterator() { 125 return nodes.iterator(); 126 } 127 } 128 129 private ConsumedNodes consumed = new ConsumedNodes(); 130 131 private Block rootBlock; 132 private int rootIndex; 133 134 /** 135 * Block and index in the block at which the match should be emitted. Differs from 136 * rootBlock/rootIndex for (ZeroExtend Read=access) for instance: match should be emitted where 137 * the Read is. 138 */ 139 private int emitIndex; 140 private Block emitBlock; 141 142 private final NodeLIRBuilder builder; 143 144 private static class NamedNode { 145 final Class<? extends Node> type; 146 final Node value; 147 148 NamedNode(Class<? extends Node> type, Node value) { 149 this.type = type; 150 this.value = value; 151 } 152 } 153 154 public MatchContext(NodeLIRBuilder builder, MatchStatement rule, int index, Node node, Block rootBlock, StructuredGraph.ScheduleResult schedule) { 155 this.builder = builder; 156 this.rule = rule; 157 this.root = node; 158 assert index == schedule.getBlockToNodesMap().get(rootBlock).indexOf(node); 159 this.schedule = schedule; 160 // The root should be the last index since all the inputs must be scheduled before it. 161 this.rootBlock = rootBlock; 162 rootIndex = index; 163 } 164 165 public Node getRoot() { 166 return root; 167 } 168 169 public Result captureNamedValue(String name, Class<? extends Node> type, Node value) { 170 if (namedNodes == null) { 171 namedNodes = EconomicMap.create(Equivalence.DEFAULT); 172 } 173 NamedNode current = namedNodes.get(name); 174 if (current == null) { 175 current = new NamedNode(type, value); 176 namedNodes.put(name, current); 177 return Result.OK; 178 } else { 179 if (current.value != value || current.type != type) { 180 return Result.namedValueMismatch(value, rule.getPattern()); 181 } 182 return Result.OK; 183 } 184 } 185 186 public Result validate() { 187 Result result = findEarlyPosition(); 188 if (result != Result.OK) { 189 return result; 190 } 191 findLatePosition(); 192 assert emitIndex == rootIndex || consumed.find(root).ignoresSideEffects; 193 return verifyInputs(); 194 } 195 196 private Result findEarlyPosition() { 197 int startIndexSideEffect = -1; 198 int endIndexSideEffect = -1; 199 final NodeMap<Block> nodeToBlockMap = schedule.getNodeToBlockMap(); 200 final BlockMap<List<Node>> blockToNodesMap = schedule.getBlockToNodesMap(); 201 202 // Nodes affected by side effects must be in the same block 203 for (ConsumedNode cn : consumed) { 204 if (!cn.ignoresSideEffects) { 205 Block b = nodeToBlockMap.get(cn.node); 206 if (emitBlock == null) { 207 emitBlock = b; 208 startIndexSideEffect = endIndexSideEffect = blockToNodesMap.get(b).indexOf(cn.node); 209 } else if (emitBlock == b) { 210 int index = blockToNodesMap.get(b).indexOf(cn.node); 211 startIndexSideEffect = Math.min(startIndexSideEffect, index); 212 endIndexSideEffect = Math.max(endIndexSideEffect, index); 213 } else { 214 logFailedMatch("nodes affected by side effects in different blocks %s", cn.node); 215 return Result.notInBlock(cn.node, rule.getPattern()); 216 } 217 } 218 } 219 if (emitBlock != null) { 220 // There must be no side effects between nodes that are affected by side effects 221 assert startIndexSideEffect != -1 && endIndexSideEffect != -1; 222 final List<Node> nodes = blockToNodesMap.get(emitBlock); 223 for (int i = startIndexSideEffect; i <= endIndexSideEffect; i++) { 224 Node node = nodes.get(i); 225 if (!sideEffectFree(node) && !consumed.contains(node)) { 226 logFailedMatch("unexpected side effect %s", node); 227 return Result.notSafe(node, rule.getPattern()); 228 } 229 } 230 // early position is at the node affected by side effects the closest to the root 231 emitIndex = endIndexSideEffect; 232 } else { 233 // Nodes not affected by side effect: early position is at the root 234 emitBlock = nodeToBlockMap.get(root); 235 emitIndex = rootIndex; 236 } 237 return Result.OK; 238 } 239 240 private static boolean sideEffectFree(Node node) { 241 // The order of evaluation of these nodes controlled by data dependence so they 242 // don't interfere with this match. 243 return node instanceof VirtualObjectNode || node instanceof FloatingNode; 244 } 245 246 private void findLatePosition() { 247 // If emit position is at a node affected by side effects that's followed by side effect 248 // free nodes, the node can be emitted later. This helps when the match has inputs that are 249 // late in the block. 250 int index = rootIndex; 251 if (emitBlock != rootBlock) { 252 index = schedule.getBlockToNodesMap().get(emitBlock).size() - 1; 253 } 254 final List<Node> emitBlockNodes = schedule.getBlockToNodesMap().get(emitBlock); 255 for (int i = emitIndex + 1; i <= index; i++) { 256 Node node = emitBlockNodes.get(i); 257 ConsumedNode cn = consumed.find(node); 258 if (cn == null) { 259 if (!sideEffectFree(node)) { 260 return; 261 } 262 } else { 263 assert cn.ignoresSideEffects; 264 emitIndex = i; 265 } 266 } 267 } 268 269 private Result verifyInputs() { 270 DebugContext debug = root.getDebug(); 271 if (emitBlock != rootBlock) { 272 assert consumed.find(root).ignoresSideEffects; 273 Result result = verifyInputsDifferentBlock(root); 274 if (result == Result.OK) { 275 MatchContextSuccessDifferentBlocks.increment(debug); 276 } 277 return result; 278 } 279 // We are going to emit the match at emitIndex. We need to make sure nodes of the match 280 // between emitIndex and rootIndex don't have inputs after position emitIndex that would 281 // make emitIndex an illegal position. 282 final List<Node> nodes = schedule.getBlockToNodesMap().get(rootBlock); 283 for (int i = emitIndex + 1; i <= rootIndex; i++) { 284 Node node = nodes.get(i); 285 ConsumedNode cn = consumed.find(node); 286 if (cn != null) { 287 assert cn.ignoresSideEffects; 288 for (Node in : node.inputs()) { 289 if (!consumed.contains(in)) { 290 for (int j = emitIndex + 1; j < i; j++) { 291 if (nodes.get(j) == in) { 292 logFailedMatch("Earliest position in block is too late %s", in); 293 assert consumed.find(root).ignoresSideEffects; 294 assert verifyInputsDifferentBlock(root) != Result.OK; 295 return Result.tooLate(node, rule.getPattern()); 296 } 297 } 298 } 299 } 300 } 301 } 302 assert verifyInputsDifferentBlock(root) == Result.OK; 303 return Result.OK; 304 } 305 306 private Result verifyInputsDifferentBlock(Node node) { 307 // Is there an input that's not part of the match that's after the emit position? 308 for (Node in : node.inputs()) { 309 if (in instanceof PhiNode) { 310 Block b = schedule.getNodeToBlockMap().get(((PhiNode) in).merge()); 311 if (dominates(b, emitBlock)) { 312 continue; 313 } 314 } else { 315 Block b = schedule.getNodeToBlockMap().get(in); 316 if (strictlyDominates(b, emitBlock) || (b == emitBlock && schedule.getBlockToNodesMap().get(emitBlock).indexOf(in) <= emitIndex)) { 317 continue; 318 } 319 } 320 ConsumedNode cn = consumed.find(in); 321 if (cn == null) { 322 logFailedMatch("Earliest position in block is too late %s", in); 323 return Result.tooLate(node, rule.getPattern()); 324 } 325 assert cn.ignoresSideEffects; 326 Result res = verifyInputsDifferentBlock(in); 327 if (res != Result.OK) { 328 return res; 329 } 330 } 331 return Result.OK; 332 } 333 334 private void logFailedMatch(String s, Node node) { 335 if (LogVerbose.getValue(root.getOptions())) { 336 DebugContext debug = root.getDebug(); 337 debug.log(s, node); 338 int startIndex = emitIndex; 339 if (emitBlock != rootBlock) { 340 int endIndex = schedule.getBlockToNodesMap().get(emitBlock).size() - 1; 341 final List<Node> emitBlockNodes = schedule.getBlockToNodesMap().get(emitBlock); 342 debug.log("%s:", emitBlock); 343 for (int j = startIndex; j <= endIndex; j++) { 344 Node theNode = emitBlockNodes.get(j); 345 debug.log("%s(%s) %1s", consumed.contains(theNode) ? "*" : " ", theNode.getUsageCount(), theNode); 346 } 347 startIndex = 0; 348 } 349 debug.log("%s:", rootBlock); 350 final List<Node> nodes = schedule.getBlockToNodesMap().get(rootBlock); 351 for (int j = startIndex; j <= rootIndex; j++) { 352 Node theNode = nodes.get(j); 353 debug.log("%s(%s) %1s", consumed.contains(theNode) ? "*" : " ", theNode.getUsageCount(), theNode); 354 } 355 } 356 } 357 358 /** 359 * Mark the interior nodes with INTERIOR_MATCH and set the Value of the root to be the result. 360 * During final LIR generation it will be evaluated to produce the actual LIR value. 361 * 362 * @param result 363 */ 364 public void setResult(ComplexMatchResult result) { 365 ComplexMatchValue value = new ComplexMatchValue(result); 366 Node emitNode = schedule.getBlockToNodesMap().get(emitBlock).get(emitIndex); 367 DebugContext debug = root.getDebug(); 368 if (debug.isLogEnabled()) { 369 debug.log("matched %s %s%s", rule.getName(), rule.getPattern(), emitIndex != rootIndex ? " skipping side effects" : ""); 370 debug.log("with nodes %s", rule.formatMatch(root)); 371 } 372 for (ConsumedNode cn : consumed) { 373 if (cn.node == root || cn.node == emitNode) { 374 continue; 375 } 376 // All the interior nodes should be skipped during the normal doRoot calls in 377 // NodeLIRBuilder so mark them as interior matches. The root of the match will get a 378 // closure which will be evaluated to produce the final LIR. 379 builder.setMatchResult(cn.node, ComplexMatchValue.INTERIOR_MATCH); 380 } 381 builder.setMatchResult(emitNode, value); 382 if (root != emitNode) { 383 // Match is not emitted at the position of root in the block but the uses of root needs 384 // the result of the match so add a ComplexMatchValue that will simply return the result 385 // of the actual match above. 386 builder.setMatchResult(root, new ComplexMatchValue(gen -> gen.operand(emitNode))); 387 } 388 } 389 390 /** 391 * Mark a node as consumed by the match. Consumed nodes will never be evaluated. 392 * 393 * @return Result.OK if the node can be safely consumed. 394 */ 395 public Result consume(Node node, boolean ignoresSideEffects, boolean atRoot) { 396 if (atRoot) { 397 consumed.add(node, ignoresSideEffects); 398 return Result.OK; 399 } 400 assert MatchPattern.isSingleValueUser(node) : "should have already been checked"; 401 402 if (builder.hasOperand(node)) { 403 return Result.alreadyUsed(node, rule.getPattern()); 404 } 405 406 consumed.add(node, ignoresSideEffects); 407 return Result.OK; 408 } 409 410 /** 411 * Return the named node. It's an error if the 412 * 413 * @param name the name of a node in the match rule 414 * @return the matched node 415 * @throws GraalError is the named node doesn't exist. 416 */ 417 public Node namedNode(String name) { 418 if (namedNodes != null) { 419 NamedNode value = namedNodes.get(name); 420 if (value != null) { 421 return value.value; 422 } 423 } 424 throw new GraalError("missing node %s", name); 425 } 426 427 @Override 428 public String toString() { 429 return String.format("%s %s (%s/%d, %s/%d) consumed %s", rule, root, rootBlock, rootIndex, emitBlock, emitIndex, consumed); 430 } 431 }