1 /* 2 * Copyright (c) 2011, 2017, 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 package org.graalvm.compiler.virtual.phases.ea; 24 25 import static org.graalvm.compiler.core.common.GraalOptions.ReadEliminationMaxLoopVisits; 26 import static org.graalvm.word.LocationIdentity.any; 27 28 import java.util.Iterator; 29 import java.util.List; 30 31 import org.graalvm.compiler.core.common.cfg.Loop; 32 import org.graalvm.compiler.core.common.type.Stamp; 33 import org.graalvm.compiler.graph.Node; 34 import org.graalvm.compiler.nodes.FieldLocationIdentity; 35 import org.graalvm.compiler.nodes.FixedWithNextNode; 36 import org.graalvm.compiler.nodes.LoopExitNode; 37 import org.graalvm.compiler.nodes.PhiNode; 38 import org.graalvm.compiler.nodes.ProxyNode; 39 import org.graalvm.compiler.nodes.ValueNode; 40 import org.graalvm.compiler.nodes.ValuePhiNode; 41 import org.graalvm.compiler.nodes.ValueProxyNode; 42 import org.graalvm.compiler.nodes.cfg.Block; 43 import org.graalvm.compiler.nodes.cfg.ControlFlowGraph; 44 import org.graalvm.compiler.nodes.extended.GuardedNode; 45 import org.graalvm.compiler.nodes.extended.GuardingNode; 46 import org.graalvm.compiler.nodes.extended.RawLoadNode; 47 import org.graalvm.compiler.nodes.extended.RawStoreNode; 48 import org.graalvm.compiler.nodes.extended.UnsafeAccessNode; 49 import org.graalvm.compiler.nodes.java.AccessFieldNode; 50 import org.graalvm.compiler.nodes.java.LoadFieldNode; 51 import org.graalvm.compiler.nodes.java.StoreFieldNode; 52 import org.graalvm.compiler.nodes.memory.MemoryCheckpoint; 53 import org.graalvm.compiler.nodes.memory.ReadNode; 54 import org.graalvm.compiler.nodes.memory.WriteNode; 55 import org.graalvm.compiler.nodes.util.GraphUtil; 56 import org.graalvm.compiler.options.OptionValues; 57 import org.graalvm.compiler.virtual.phases.ea.ReadEliminationBlockState.CacheEntry; 58 import org.graalvm.compiler.virtual.phases.ea.ReadEliminationBlockState.LoadCacheEntry; 59 import org.graalvm.compiler.virtual.phases.ea.ReadEliminationBlockState.UnsafeLoadCacheEntry; 60 import org.graalvm.util.EconomicMap; 61 import org.graalvm.util.EconomicSet; 62 import org.graalvm.util.Equivalence; 63 import org.graalvm.util.MapCursor; 64 import org.graalvm.word.LocationIdentity; 65 66 import jdk.vm.ci.meta.JavaKind; 67 68 /** 69 * This closure initially handled a set of nodes that is disjunct from 70 * {@link PEReadEliminationClosure}, but over time both have evolved so that there's a significant 71 * overlap. 72 */ 73 public final class ReadEliminationClosure extends EffectsClosure<ReadEliminationBlockState> { 74 private final boolean considerGuards; 75 76 public ReadEliminationClosure(ControlFlowGraph cfg, boolean considerGuards) { 77 super(null, cfg); 78 this.considerGuards = considerGuards; 79 } 80 81 @Override 82 protected ReadEliminationBlockState getInitialState() { 83 return new ReadEliminationBlockState(); 84 } 85 86 @Override 87 protected boolean processNode(Node node, ReadEliminationBlockState state, GraphEffectList effects, FixedWithNextNode lastFixedNode) { 88 boolean deleted = false; 89 if (node instanceof AccessFieldNode) { 90 AccessFieldNode access = (AccessFieldNode) node; 91 if (access.isVolatile()) { 92 processIdentity(state, any()); 93 } else { 94 ValueNode object = GraphUtil.unproxify(access.object()); 95 LoadCacheEntry identifier = new LoadCacheEntry(object, new FieldLocationIdentity(access.field())); 96 ValueNode cachedValue = state.getCacheEntry(identifier); 97 if (node instanceof LoadFieldNode) { 98 if (cachedValue != null && access.stamp().isCompatible(cachedValue.stamp())) { 99 effects.replaceAtUsages(access, cachedValue, access); 100 addScalarAlias(access, cachedValue); 101 deleted = true; 102 } else { 103 state.addCacheEntry(identifier, access); 104 } 105 } else { 106 assert node instanceof StoreFieldNode; 107 StoreFieldNode store = (StoreFieldNode) node; 108 ValueNode value = getScalarAlias(store.value()); 109 if (GraphUtil.unproxify(value) == GraphUtil.unproxify(cachedValue)) { 110 effects.deleteNode(store); 111 deleted = true; 112 } 113 state.killReadCache(identifier.identity); 114 state.addCacheEntry(identifier, value); 115 } 116 } 117 } else if (node instanceof ReadNode) { 118 ReadNode read = (ReadNode) node; 119 if (read.getLocationIdentity().isSingle()) { 120 ValueNode object = GraphUtil.unproxify(read.getAddress()); 121 LoadCacheEntry identifier = new LoadCacheEntry(object, read.getLocationIdentity()); 122 ValueNode cachedValue = state.getCacheEntry(identifier); 123 if (cachedValue != null && areValuesReplaceable(read, cachedValue, considerGuards)) { 124 effects.replaceAtUsages(read, cachedValue, read); 125 addScalarAlias(read, cachedValue); 126 deleted = true; 127 } else { 128 state.addCacheEntry(identifier, read); 129 } 130 } 131 } else if (node instanceof WriteNode) { 132 WriteNode write = (WriteNode) node; 133 if (write.getLocationIdentity().isSingle()) { 134 ValueNode object = GraphUtil.unproxify(write.getAddress()); 135 LoadCacheEntry identifier = new LoadCacheEntry(object, write.getLocationIdentity()); 136 ValueNode cachedValue = state.getCacheEntry(identifier); 137 138 ValueNode value = getScalarAlias(write.value()); 139 if (GraphUtil.unproxify(value) == GraphUtil.unproxify(cachedValue)) { 140 effects.deleteNode(write); 141 deleted = true; 142 } 143 processIdentity(state, write.getLocationIdentity()); 144 state.addCacheEntry(identifier, value); 145 } else { 146 processIdentity(state, write.getLocationIdentity()); 147 } 148 } else if (node instanceof UnsafeAccessNode) { 149 if (node instanceof RawLoadNode) { 150 RawLoadNode load = (RawLoadNode) node; 151 if (load.getLocationIdentity().isSingle()) { 152 ValueNode object = GraphUtil.unproxify(load.object()); 153 UnsafeLoadCacheEntry identifier = new UnsafeLoadCacheEntry(object, load.offset(), load.getLocationIdentity()); 154 ValueNode cachedValue = state.getCacheEntry(identifier); 155 if (cachedValue != null && areValuesReplaceable(load, cachedValue, considerGuards)) { 156 effects.replaceAtUsages(load, cachedValue, load); 157 addScalarAlias(load, cachedValue); 158 deleted = true; 159 } else { 160 state.addCacheEntry(identifier, load); 161 } 162 } 163 } else { 164 assert node instanceof RawStoreNode; 165 RawStoreNode write = (RawStoreNode) node; 166 if (write.getLocationIdentity().isSingle()) { 167 ValueNode object = GraphUtil.unproxify(write.object()); 168 UnsafeLoadCacheEntry identifier = new UnsafeLoadCacheEntry(object, write.offset(), write.getLocationIdentity()); 169 ValueNode cachedValue = state.getCacheEntry(identifier); 170 171 ValueNode value = getScalarAlias(write.value()); 172 if (GraphUtil.unproxify(value) == GraphUtil.unproxify(cachedValue)) { 173 effects.deleteNode(write); 174 deleted = true; 175 } 176 processIdentity(state, write.getLocationIdentity()); 177 state.addCacheEntry(identifier, value); 178 } else { 179 processIdentity(state, write.getLocationIdentity()); 180 } 181 } 182 } else if (node instanceof MemoryCheckpoint.Single) { 183 LocationIdentity identity = ((MemoryCheckpoint.Single) node).getLocationIdentity(); 184 processIdentity(state, identity); 185 } else if (node instanceof MemoryCheckpoint.Multi) { 186 for (LocationIdentity identity : ((MemoryCheckpoint.Multi) node).getLocationIdentities()) { 187 processIdentity(state, identity); 188 } 189 } 190 return deleted; 191 } 192 193 private static boolean areValuesReplaceable(ValueNode originalValue, ValueNode replacementValue, boolean considerGuards) { 194 return originalValue.stamp().isCompatible(replacementValue.stamp()) && 195 (!considerGuards || (getGuard(originalValue) == null || getGuard(originalValue) == getGuard(replacementValue))); 196 } 197 198 private static GuardingNode getGuard(ValueNode node) { 199 if (node instanceof GuardedNode) { 200 GuardedNode guardedNode = (GuardedNode) node; 201 return guardedNode.getGuard(); 202 } 203 return null; 204 } 205 206 private static void processIdentity(ReadEliminationBlockState state, LocationIdentity identity) { 207 if (identity.isAny()) { 208 state.killReadCache(); 209 return; 210 } 211 state.killReadCache(identity); 212 } 213 214 @Override 215 protected void processLoopExit(LoopExitNode exitNode, ReadEliminationBlockState initialState, ReadEliminationBlockState exitState, GraphEffectList effects) { 216 if (exitNode.graph().hasValueProxies()) { 217 MapCursor<CacheEntry<?>, ValueNode> entry = exitState.getReadCache().getEntries(); 218 while (entry.advance()) { 219 if (initialState.getReadCache().get(entry.getKey()) != entry.getValue()) { 220 ProxyNode proxy = new ValueProxyNode(exitState.getCacheEntry(entry.getKey()), exitNode); 221 effects.addFloatingNode(proxy, "readCacheProxy"); 222 exitState.getReadCache().put(entry.getKey(), proxy); 223 } 224 } 225 } 226 } 227 228 @Override 229 protected ReadEliminationBlockState cloneState(ReadEliminationBlockState other) { 230 return new ReadEliminationBlockState(other); 231 } 232 233 @Override 234 protected MergeProcessor createMergeProcessor(Block merge) { 235 return new ReadEliminationMergeProcessor(merge); 236 } 237 238 private class ReadEliminationMergeProcessor extends EffectsClosure<ReadEliminationBlockState>.MergeProcessor { 239 240 private final EconomicMap<Object, ValuePhiNode> materializedPhis = EconomicMap.create(Equivalence.DEFAULT); 241 242 ReadEliminationMergeProcessor(Block mergeBlock) { 243 super(mergeBlock); 244 } 245 246 protected ValuePhiNode getCachedPhi(CacheEntry<?> virtual, Stamp stamp) { 247 ValuePhiNode result = materializedPhis.get(virtual); 248 if (result == null) { 249 result = createValuePhi(stamp); 250 materializedPhis.put(virtual, result); 251 } 252 return result; 253 } 254 255 @Override 256 protected void merge(List<ReadEliminationBlockState> states) { 257 MapCursor<CacheEntry<?>, ValueNode> cursor = states.get(0).readCache.getEntries(); 258 while (cursor.advance()) { 259 CacheEntry<?> key = cursor.getKey(); 260 ValueNode value = cursor.getValue(); 261 boolean phi = false; 262 for (int i = 1; i < states.size(); i++) { 263 ValueNode otherValue = states.get(i).readCache.get(key); 264 // E.g. unsafe loads / stores with different access kinds have different stamps 265 // although location, object and offset are the same. In this case we cannot 266 // create a phi nor can we set a common value. 267 if (otherValue == null || !value.stamp().isCompatible(otherValue.stamp())) { 268 value = null; 269 phi = false; 270 break; 271 } 272 if (!phi && otherValue != value) { 273 phi = true; 274 } 275 } 276 if (phi) { 277 PhiNode phiNode = getCachedPhi(key, value.stamp().unrestricted()); 278 mergeEffects.addFloatingNode(phiNode, "mergeReadCache"); 279 for (int i = 0; i < states.size(); i++) { 280 ValueNode v = states.get(i).getCacheEntry(key); 281 assert phiNode.stamp().isCompatible(v.stamp()) : "Cannot create read elimination phi for inputs with incompatible stamps."; 282 setPhiInput(phiNode, i, v); 283 } 284 newState.addCacheEntry(key, phiNode); 285 } else if (value != null) { 286 // case that there is the same value on all branches 287 newState.addCacheEntry(key, value); 288 } 289 } 290 /* 291 * For object phis, see if there are known reads on all predecessors, for which we could 292 * create new phis. 293 */ 294 for (PhiNode phi : getPhis()) { 295 if (phi.getStackKind() == JavaKind.Object) { 296 for (CacheEntry<?> entry : states.get(0).readCache.getKeys()) { 297 if (entry.object == getPhiValueAt(phi, 0)) { 298 mergeReadCachePhi(phi, entry, states); 299 } 300 } 301 } 302 } 303 } 304 305 private void mergeReadCachePhi(PhiNode phi, CacheEntry<?> identifier, List<ReadEliminationBlockState> states) { 306 ValueNode[] values = new ValueNode[states.size()]; 307 values[0] = states.get(0).getCacheEntry(identifier.duplicateWithObject(getPhiValueAt(phi, 0))); 308 if (values[0] != null) { 309 for (int i = 1; i < states.size(); i++) { 310 ValueNode value = states.get(i).getCacheEntry(identifier.duplicateWithObject(getPhiValueAt(phi, i))); 311 // e.g. unsafe loads / stores with same identity and different access kinds see 312 // mergeReadCache(states) 313 if (value == null || !values[i - 1].stamp().isCompatible(value.stamp())) { 314 return; 315 } 316 values[i] = value; 317 } 318 319 CacheEntry<?> newIdentifier = identifier.duplicateWithObject(phi); 320 PhiNode phiNode = getCachedPhi(newIdentifier, values[0].stamp().unrestricted()); 321 mergeEffects.addFloatingNode(phiNode, "mergeReadCachePhi"); 322 for (int i = 0; i < values.length; i++) { 323 setPhiInput(phiNode, i, values[i]); 324 } 325 newState.addCacheEntry(newIdentifier, phiNode); 326 } 327 } 328 } 329 330 @Override 331 protected void processKilledLoopLocations(Loop<Block> loop, ReadEliminationBlockState initialState, ReadEliminationBlockState mergedStates) { 332 assert initialState != null; 333 assert mergedStates != null; 334 if (initialState.readCache.size() > 0) { 335 LoopKillCache loopKilledLocations = loopLocationKillCache.get(loop); 336 // we have fully processed this loop the first time, remember to cache it the next time 337 // it is visited 338 if (loopKilledLocations == null) { 339 loopKilledLocations = new LoopKillCache(1/* 1.visit */); 340 loopLocationKillCache.put(loop, loopKilledLocations); 341 } else { 342 OptionValues options = loop.getHeader().getBeginNode().getOptions(); 343 if (loopKilledLocations.visits() > ReadEliminationMaxLoopVisits.getValue(options)) { 344 // we have processed the loop too many times, kill all locations so the inner 345 // loop will never be processed more than once again on visit 346 loopKilledLocations.setKillsAll(); 347 } else { 348 // we have fully processed this loop >1 times, update the killed locations 349 EconomicSet<LocationIdentity> forwardEndLiveLocations = EconomicSet.create(Equivalence.DEFAULT); 350 for (CacheEntry<?> entry : initialState.readCache.getKeys()) { 351 forwardEndLiveLocations.add(entry.getIdentity()); 352 } 353 for (CacheEntry<?> entry : mergedStates.readCache.getKeys()) { 354 forwardEndLiveLocations.remove(entry.getIdentity()); 355 } 356 // every location that is alive before the loop but not after is killed by the 357 // loop 358 for (LocationIdentity location : forwardEndLiveLocations) { 359 loopKilledLocations.rememberLoopKilledLocation(location); 360 } 361 if (debug.isLogEnabled() && loopKilledLocations != null) { 362 debug.log("[Early Read Elimination] Setting loop killed locations of loop at node %s with %s", 363 loop.getHeader().getBeginNode(), forwardEndLiveLocations); 364 } 365 } 366 // remember the loop visit 367 loopKilledLocations.visited(); 368 } 369 } 370 } 371 372 @Override 373 protected ReadEliminationBlockState stripKilledLoopLocations(Loop<Block> loop, ReadEliminationBlockState originalInitialState) { 374 ReadEliminationBlockState initialState = super.stripKilledLoopLocations(loop, originalInitialState); 375 LoopKillCache loopKilledLocations = loopLocationKillCache.get(loop); 376 if (loopKilledLocations != null && loopKilledLocations.loopKillsLocations()) { 377 Iterator<CacheEntry<?>> it = initialState.readCache.getKeys().iterator(); 378 while (it.hasNext()) { 379 CacheEntry<?> entry = it.next(); 380 if (loopKilledLocations.containsLocation(entry.getIdentity())) { 381 it.remove(); 382 } 383 } 384 } 385 return initialState; 386 } 387 }