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 
  24 
  25 package org.graalvm.compiler.virtual.phases.ea;
  26 
  27 import java.util.ArrayList;
  28 import java.util.BitSet;
  29 import java.util.Iterator;
  30 import java.util.List;
  31 import java.util.function.IntUnaryOperator;
  32 
  33 import jdk.internal.vm.compiler.collections.EconomicMap;
  34 import jdk.internal.vm.compiler.collections.EconomicSet;
  35 import jdk.internal.vm.compiler.collections.Equivalence;
  36 import org.graalvm.compiler.core.common.GraalOptions;
  37 import org.graalvm.compiler.core.common.cfg.Loop;
  38 import org.graalvm.compiler.core.common.spi.ConstantFieldProvider;
  39 import org.graalvm.compiler.core.common.type.Stamp;
  40 import org.graalvm.compiler.core.common.type.StampFactory;
  41 import org.graalvm.compiler.debug.CounterKey;
  42 import org.graalvm.compiler.debug.DebugContext;
  43 import org.graalvm.compiler.graph.Node;
  44 import org.graalvm.compiler.graph.NodeBitMap;
  45 import org.graalvm.compiler.graph.Position;
  46 import org.graalvm.compiler.graph.spi.Canonicalizable;
  47 import org.graalvm.compiler.nodes.AbstractEndNode;
  48 import org.graalvm.compiler.nodes.CallTargetNode;
  49 import org.graalvm.compiler.nodes.ConstantNode;
  50 import org.graalvm.compiler.nodes.ControlSinkNode;
  51 import org.graalvm.compiler.nodes.FixedNode;
  52 import org.graalvm.compiler.nodes.FixedWithNextNode;
  53 import org.graalvm.compiler.nodes.FrameState;
  54 import org.graalvm.compiler.nodes.Invoke;
  55 import org.graalvm.compiler.nodes.LoopBeginNode;
  56 import org.graalvm.compiler.nodes.LoopExitNode;
  57 import org.graalvm.compiler.nodes.NodeView;
  58 import org.graalvm.compiler.nodes.PhiNode;
  59 import org.graalvm.compiler.nodes.ProxyNode;
  60 import org.graalvm.compiler.nodes.StructuredGraph;
  61 import org.graalvm.compiler.nodes.StructuredGraph.ScheduleResult;
  62 import org.graalvm.compiler.nodes.ValueNode;
  63 import org.graalvm.compiler.nodes.ValuePhiNode;
  64 import org.graalvm.compiler.nodes.ValueProxyNode;
  65 import org.graalvm.compiler.nodes.VirtualState;
  66 import org.graalvm.compiler.nodes.VirtualState.NodeClosure;
  67 import org.graalvm.compiler.nodes.cfg.Block;
  68 import org.graalvm.compiler.nodes.spi.LoweringProvider;
  69 import org.graalvm.compiler.nodes.spi.NodeWithState;
  70 import org.graalvm.compiler.nodes.spi.Virtualizable;
  71 import org.graalvm.compiler.nodes.spi.VirtualizableAllocation;
  72 import org.graalvm.compiler.nodes.spi.VirtualizerTool;
  73 import org.graalvm.compiler.nodes.virtual.AllocatedObjectNode;
  74 import org.graalvm.compiler.nodes.virtual.VirtualObjectNode;
  75 import org.graalvm.compiler.virtual.nodes.VirtualObjectState;
  76 
  77 import jdk.vm.ci.meta.ConstantReflectionProvider;
  78 import jdk.vm.ci.meta.JavaConstant;
  79 import jdk.vm.ci.meta.JavaKind;
  80 import jdk.vm.ci.meta.MetaAccessProvider;
  81 
  82 public abstract class PartialEscapeClosure<BlockT extends PartialEscapeBlockState<BlockT>> extends EffectsClosure<BlockT> {
  83 
  84     public static final CounterKey COUNTER_MATERIALIZATIONS = DebugContext.counter("Materializations");
  85     public static final CounterKey COUNTER_MATERIALIZATIONS_PHI = DebugContext.counter("MaterializationsPhi");
  86     public static final CounterKey COUNTER_MATERIALIZATIONS_MERGE = DebugContext.counter("MaterializationsMerge");
  87     public static final CounterKey COUNTER_MATERIALIZATIONS_UNHANDLED = DebugContext.counter("MaterializationsUnhandled");
  88     public static final CounterKey COUNTER_MATERIALIZATIONS_LOOP_REITERATION = DebugContext.counter("MaterializationsLoopReiteration");
  89     public static final CounterKey COUNTER_MATERIALIZATIONS_LOOP_END = DebugContext.counter("MaterializationsLoopEnd");
  90     public static final CounterKey COUNTER_ALLOCATION_REMOVED = DebugContext.counter("AllocationsRemoved");
  91     public static final CounterKey COUNTER_MEMORYCHECKPOINT = DebugContext.counter("MemoryCheckpoint");
  92 
  93     /**
  94      * Nodes with inputs that were modified during analysis are marked in this bitset - this way
  95      * nodes that are not influenced at all by analysis can be rejected quickly.
  96      */
  97     private final NodeBitMap hasVirtualInputs;
  98 
  99     /**
 100      * This is handed out to implementers of {@link Virtualizable}.
 101      */
 102     protected final VirtualizerToolImpl tool;
 103 
 104     /**
 105      * The indexes into this array correspond to {@link VirtualObjectNode#getObjectId()}.
 106      */
 107     public final ArrayList<VirtualObjectNode> virtualObjects = new ArrayList<>();
 108     public final DebugContext debug;
 109 
 110     @Override
 111     public boolean needsApplyEffects() {
 112         if (hasChanged()) {
 113             return true;
 114         }
 115         /*
 116          * If there is a mismatch between the number of materializations and the number of
 117          * virtualizations, we need to apply effects, even if there were no other significant
 118          * changes to the graph. This applies to each block, since moving from one block to the
 119          * other can also be important (if the probabilities of the block differ).
 120          */
 121         for (Block block : cfg.getBlocks()) {
 122             GraphEffectList effects = blockEffects.get(block);
 123             if (effects != null) {
 124                 if (effects.getVirtualizationDelta() != 0) {
 125                     return true;
 126                 }
 127             }
 128         }
 129         return false;
 130     }
 131 
 132     private final class CollectVirtualObjectsClosure extends NodeClosure<ValueNode> {
 133         private final EconomicSet<VirtualObjectNode> virtual;
 134         private final GraphEffectList effects;
 135         private final BlockT state;
 136 
 137         private CollectVirtualObjectsClosure(EconomicSet<VirtualObjectNode> virtual, GraphEffectList effects, BlockT state) {
 138             this.virtual = virtual;
 139             this.effects = effects;
 140             this.state = state;
 141         }
 142 
 143         @Override
 144         public void apply(Node usage, ValueNode value) {
 145             if (value instanceof VirtualObjectNode) {
 146                 VirtualObjectNode object = (VirtualObjectNode) value;
 147                 if (object.getObjectId() != -1 && state.getObjectStateOptional(object) != null) {
 148                     virtual.add(object);
 149                 }
 150             } else {
 151                 ValueNode alias = getAlias(value);
 152                 if (alias instanceof VirtualObjectNode) {
 153                     VirtualObjectNode object = (VirtualObjectNode) alias;
 154                     virtual.add(object);
 155                     effects.replaceFirstInput(usage, value, object);
 156                 }
 157             }
 158         }
 159     }
 160 
 161     /**
 162      * Final subclass of PartialEscapeClosure, for performance and to make everything behave nicely
 163      * with generics.
 164      */
 165     public static final class Final extends PartialEscapeClosure<PartialEscapeBlockState.Final> {
 166 
 167         public Final(ScheduleResult schedule, MetaAccessProvider metaAccess, ConstantReflectionProvider constantReflection, ConstantFieldProvider constantFieldProvider,
 168                         LoweringProvider loweringProvider) {
 169             super(schedule, metaAccess, constantReflection, constantFieldProvider, loweringProvider);
 170         }
 171 
 172         @Override
 173         protected PartialEscapeBlockState.Final getInitialState() {
 174             return new PartialEscapeBlockState.Final(tool.getOptions(), tool.getDebug());
 175         }
 176 
 177         @Override
 178         protected PartialEscapeBlockState.Final cloneState(PartialEscapeBlockState.Final oldState) {
 179             return new PartialEscapeBlockState.Final(oldState);
 180         }
 181     }
 182 
 183     public PartialEscapeClosure(ScheduleResult schedule, MetaAccessProvider metaAccess, ConstantReflectionProvider constantReflection, ConstantFieldProvider constantFieldProvider) {
 184         this(schedule, metaAccess, constantReflection, constantFieldProvider, null);
 185     }
 186 
 187     public PartialEscapeClosure(ScheduleResult schedule, MetaAccessProvider metaAccess, ConstantReflectionProvider constantReflection, ConstantFieldProvider constantFieldProvider,
 188                     LoweringProvider loweringProvider) {
 189         super(schedule, schedule.getCFG());
 190         StructuredGraph graph = schedule.getCFG().graph;
 191         this.hasVirtualInputs = graph.createNodeBitMap();
 192         this.debug = graph.getDebug();
 193         this.tool = new VirtualizerToolImpl(metaAccess, constantReflection, constantFieldProvider, this, graph.getAssumptions(), graph.getOptions(), debug, loweringProvider);
 194     }
 195 
 196     /**
 197      * @return true if the node was deleted, false otherwise
 198      */
 199     @Override
 200     protected boolean processNode(Node node, BlockT state, GraphEffectList effects, FixedWithNextNode lastFixedNode) {
 201         /*
 202          * These checks make up for the fact that an earliest schedule moves CallTargetNodes upwards
 203          * and thus materializes virtual objects needlessly. Also, FrameStates and ConstantNodes are
 204          * scheduled, but can safely be ignored.
 205          */
 206         if (node instanceof CallTargetNode || node instanceof FrameState || node instanceof ConstantNode) {
 207             return false;
 208         } else if (node instanceof Invoke) {
 209             processNodeInternal(((Invoke) node).callTarget(), state, effects, lastFixedNode);
 210         }
 211         return processNodeInternal(node, state, effects, lastFixedNode);
 212     }
 213 
 214     private boolean processNodeInternal(Node node, BlockT state, GraphEffectList effects, FixedWithNextNode lastFixedNode) {
 215         FixedNode nextFixedNode = lastFixedNode == null ? null : lastFixedNode.next();
 216         VirtualUtil.trace(node.getOptions(), debug, "%s", node);
 217 
 218         if (requiresProcessing(node)) {
 219             if (processVirtualizable((ValueNode) node, nextFixedNode, state, effects) == false) {
 220                 return false;
 221             }
 222             if (tool.isDeleted()) {
 223                 VirtualUtil.trace(node.getOptions(), debug, "deleted virtualizable allocation %s", node);
 224                 return true;
 225             }
 226         }
 227         if (hasVirtualInputs.isMarked(node) && node instanceof ValueNode) {
 228             if (node instanceof Virtualizable) {
 229                 if (processVirtualizable((ValueNode) node, nextFixedNode, state, effects) == false) {
 230                     return false;
 231                 }
 232                 if (tool.isDeleted()) {
 233                     VirtualUtil.trace(node.getOptions(), debug, "deleted virtualizable node %s", node);
 234                     return true;
 235                 }
 236             }
 237             processNodeInputs((ValueNode) node, nextFixedNode, state, effects);
 238         }
 239 
 240         if (hasScalarReplacedInputs(node) && node instanceof ValueNode) {
 241             if (processNodeWithScalarReplacedInputs((ValueNode) node, nextFixedNode, state, effects)) {
 242                 return true;
 243             }
 244         }
 245         return false;
 246     }
 247 
 248     protected boolean requiresProcessing(Node node) {
 249         return node instanceof VirtualizableAllocation;
 250     }
 251 
 252     private boolean processVirtualizable(ValueNode node, FixedNode insertBefore, BlockT state, GraphEffectList effects) {
 253         tool.reset(state, node, insertBefore, effects);
 254         return virtualize(node, tool);
 255     }
 256 
 257     protected boolean virtualize(ValueNode node, VirtualizerTool vt) {
 258         ((Virtualizable) node).virtualize(vt);
 259         return true; // request further processing
 260     }
 261 
 262     /**
 263      * This tries to canonicalize the node based on improved (replaced) inputs.
 264      */
 265     @SuppressWarnings("unchecked")
 266     private boolean processNodeWithScalarReplacedInputs(ValueNode node, FixedNode insertBefore, BlockT state, GraphEffectList effects) {
 267         ValueNode canonicalizedValue = node;
 268         if (node instanceof Canonicalizable.Unary<?>) {
 269             Canonicalizable.Unary<ValueNode> canonicalizable = (Canonicalizable.Unary<ValueNode>) node;
 270             ObjectState valueObj = getObjectState(state, canonicalizable.getValue());
 271             ValueNode valueAlias = valueObj != null ? valueObj.getMaterializedValue() : getScalarAlias(canonicalizable.getValue());
 272             if (valueAlias != canonicalizable.getValue()) {
 273                 canonicalizedValue = (ValueNode) canonicalizable.canonical(tool, valueAlias);
 274             }
 275         } else if (node instanceof Canonicalizable.Binary<?>) {
 276             Canonicalizable.Binary<ValueNode> canonicalizable = (Canonicalizable.Binary<ValueNode>) node;
 277             ObjectState xObj = getObjectState(state, canonicalizable.getX());
 278             ValueNode xAlias = xObj != null ? xObj.getMaterializedValue() : getScalarAlias(canonicalizable.getX());
 279             ObjectState yObj = getObjectState(state, canonicalizable.getY());
 280             ValueNode yAlias = yObj != null ? yObj.getMaterializedValue() : getScalarAlias(canonicalizable.getY());
 281             if (xAlias != canonicalizable.getX() || yAlias != canonicalizable.getY()) {
 282                 canonicalizedValue = (ValueNode) canonicalizable.canonical(tool, xAlias, yAlias);
 283             }
 284         } else {
 285             return false;
 286         }
 287         if (canonicalizedValue != node && canonicalizedValue != null) {
 288             if (canonicalizedValue.isAlive()) {
 289                 ValueNode alias = getAliasAndResolve(state, canonicalizedValue);
 290                 if (alias instanceof VirtualObjectNode) {
 291                     addVirtualAlias((VirtualObjectNode) alias, node);
 292                     effects.deleteNode(node);
 293                 } else {
 294                     effects.replaceAtUsages(node, alias, insertBefore);
 295                     addScalarAlias(node, alias);
 296                 }
 297             } else {
 298                 if (!prepareCanonicalNode(canonicalizedValue, state, effects)) {
 299                     VirtualUtil.trace(node.getOptions(), debug, "replacement via canonicalization too complex: %s -> %s", node, canonicalizedValue);
 300                     return false;
 301                 }
 302                 if (canonicalizedValue instanceof ControlSinkNode) {
 303                     effects.replaceWithSink((FixedWithNextNode) node, (ControlSinkNode) canonicalizedValue);
 304                     state.markAsDead();
 305                 } else {
 306                     effects.replaceAtUsages(node, canonicalizedValue, insertBefore);
 307                     addScalarAlias(node, canonicalizedValue);
 308                 }
 309             }
 310             VirtualUtil.trace(node.getOptions(), debug, "replaced via canonicalization: %s -> %s", node, canonicalizedValue);
 311             return true;
 312         }
 313         return false;
 314     }
 315 
 316     /**
 317      * Nodes created during canonicalizations need to be scanned for values that were replaced.
 318      */
 319     private boolean prepareCanonicalNode(ValueNode node, BlockT state, GraphEffectList effects) {
 320         assert !node.isAlive();
 321         for (Position pos : node.inputPositions()) {
 322             Node input = pos.get(node);
 323             if (input instanceof ValueNode) {
 324                 if (input.isAlive()) {
 325                     if (!(input instanceof VirtualObjectNode)) {
 326                         ObjectState obj = getObjectState(state, (ValueNode) input);
 327                         if (obj != null) {
 328                             if (obj.isVirtual()) {
 329                                 return false;
 330                             } else {
 331                                 pos.initialize(node, obj.getMaterializedValue());
 332                             }
 333                         } else {
 334                             pos.initialize(node, getScalarAlias((ValueNode) input));
 335                         }
 336                     }
 337                 } else {
 338                     if (!prepareCanonicalNode((ValueNode) input, state, effects)) {
 339                         return false;
 340                     }
 341                 }
 342             }
 343         }
 344         return true;
 345     }
 346 
 347     /**
 348      * This replaces all inputs that point to virtual or materialized values with the actual value,
 349      * materializing if necessary. Also takes care of frame states, adding the necessary
 350      * {@link VirtualObjectState}.
 351      */
 352     private void processNodeInputs(ValueNode node, FixedNode insertBefore, BlockT state, GraphEffectList effects) {
 353         VirtualUtil.trace(node.getOptions(), debug, "processing nodewithstate: %s", node);
 354         for (Node input : node.inputs()) {
 355             if (input instanceof ValueNode) {
 356                 ValueNode alias = getAlias((ValueNode) input);
 357                 if (alias instanceof VirtualObjectNode) {
 358                     int id = ((VirtualObjectNode) alias).getObjectId();
 359                     ensureMaterialized(state, id, insertBefore, effects, COUNTER_MATERIALIZATIONS_UNHANDLED);
 360                     effects.replaceFirstInput(node, input, state.getObjectState(id).getMaterializedValue());
 361                     VirtualUtil.trace(node.getOptions(), debug, "replacing input %s at %s", input, node);
 362                 }
 363             }
 364         }
 365         if (node instanceof NodeWithState) {
 366             processNodeWithState((NodeWithState) node, state, effects);
 367         }
 368     }
 369 
 370     private void processNodeWithState(NodeWithState nodeWithState, BlockT state, GraphEffectList effects) {
 371         for (FrameState fs : nodeWithState.states()) {
 372             FrameState frameState = getUniqueFramestate(nodeWithState, fs);
 373             EconomicSet<VirtualObjectNode> virtual = EconomicSet.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE);
 374             frameState.applyToNonVirtual(new CollectVirtualObjectsClosure(virtual, effects, state));
 375             collectLockedVirtualObjects(state, virtual);
 376             collectReferencedVirtualObjects(state, virtual);
 377             addVirtualMappings(frameState, virtual, state, effects);
 378         }
 379     }
 380 
 381     private static FrameState getUniqueFramestate(NodeWithState nodeWithState, FrameState frameState) {
 382         if (frameState.hasMoreThanOneUsage()) {
 383             // Can happen for example from inlined snippets with multiple state split nodes.
 384             FrameState copy = (FrameState) frameState.copyWithInputs();
 385             nodeWithState.asNode().replaceFirstInput(frameState, copy);
 386             return copy;
 387         }
 388         return frameState;
 389     }
 390 
 391     private void addVirtualMappings(FrameState frameState, EconomicSet<VirtualObjectNode> virtual, BlockT state, GraphEffectList effects) {
 392         for (VirtualObjectNode obj : virtual) {
 393             effects.addVirtualMapping(frameState, state.getObjectState(obj).createEscapeObjectState(debug, obj));
 394         }
 395     }
 396 
 397     private void collectReferencedVirtualObjects(BlockT state, EconomicSet<VirtualObjectNode> virtual) {
 398         Iterator<VirtualObjectNode> iterator = virtual.iterator();
 399         while (iterator.hasNext()) {
 400             VirtualObjectNode object = iterator.next();
 401             int id = object.getObjectId();
 402             if (id != -1) {
 403                 ObjectState objState = state.getObjectStateOptional(id);
 404                 if (objState != null && objState.isVirtual()) {
 405                     for (ValueNode entry : objState.getEntries()) {
 406                         if (entry instanceof VirtualObjectNode) {
 407                             VirtualObjectNode entryVirtual = (VirtualObjectNode) entry;
 408                             if (!virtual.contains(entryVirtual)) {
 409                                 virtual.add(entryVirtual);
 410                             }
 411                         }
 412                     }
 413                 }
 414             }
 415         }
 416     }
 417 
 418     private void collectLockedVirtualObjects(BlockT state, EconomicSet<VirtualObjectNode> virtual) {
 419         for (int i = 0; i < state.getStateCount(); i++) {
 420             ObjectState objState = state.getObjectStateOptional(i);
 421             if (objState != null && objState.isVirtual() && objState.hasLocks()) {
 422                 virtual.add(virtualObjects.get(i));
 423             }
 424         }
 425     }
 426 
 427     /**
 428      * @return true if materialization happened, false if not.
 429      */
 430     protected boolean ensureMaterialized(PartialEscapeBlockState<?> state, int object, FixedNode materializeBefore, GraphEffectList effects, CounterKey counter) {
 431         if (state.getObjectState(object).isVirtual()) {
 432             counter.increment(debug);
 433             VirtualObjectNode virtual = virtualObjects.get(object);
 434             state.materializeBefore(materializeBefore, virtual, effects);
 435             assert !updateStatesForMaterialized(state, virtual, state.getObjectState(object).getMaterializedValue()) : "method must already have been called before";
 436             return true;
 437         } else {
 438             return false;
 439         }
 440     }
 441 
 442     public static boolean updateStatesForMaterialized(PartialEscapeBlockState<?> state, VirtualObjectNode virtual, ValueNode materializedValue) {
 443         // update all existing states with the newly materialized object
 444         boolean change = false;
 445         for (int i = 0; i < state.getStateCount(); i++) {
 446             ObjectState objState = state.getObjectStateOptional(i);
 447             if (objState != null && objState.isVirtual()) {
 448                 ValueNode[] entries = objState.getEntries();
 449                 for (int i2 = 0; i2 < entries.length; i2++) {
 450                     if (entries[i2] == virtual) {
 451                         state.setEntry(i, i2, materializedValue);
 452                         change = true;
 453                     }
 454                 }
 455             }
 456         }
 457         return change;
 458     }
 459 
 460     @Override
 461     protected BlockT stripKilledLoopLocations(Loop<Block> loop, BlockT originalInitialState) {
 462         BlockT initialState = super.stripKilledLoopLocations(loop, originalInitialState);
 463         if (loop.getDepth() > GraalOptions.EscapeAnalysisLoopCutoff.getValue(cfg.graph.getOptions())) {
 464             /*
 465              * After we've reached the maximum loop nesting, we'll simply materialize everything we
 466              * can to make sure that the loops only need to be iterated one time. Care is taken here
 467              * to not materialize virtual objects that have the "ensureVirtualized" flag set.
 468              */
 469             LoopBeginNode loopBegin = (LoopBeginNode) loop.getHeader().getBeginNode();
 470             AbstractEndNode end = loopBegin.forwardEnd();
 471             Block loopPredecessor = loop.getHeader().getFirstPredecessor();
 472             assert loopPredecessor.getEndNode() == end;
 473             int length = initialState.getStateCount();
 474 
 475             boolean change;
 476             BitSet ensureVirtualized = new BitSet(length);
 477             for (int i = 0; i < length; i++) {
 478                 ObjectState state = initialState.getObjectStateOptional(i);
 479                 if (state != null && state.isVirtual() && state.getEnsureVirtualized()) {
 480                     ensureVirtualized.set(i);
 481                 }
 482             }
 483             do {
 484                 // propagate "ensureVirtualized" flag
 485                 change = false;
 486                 for (int i = 0; i < length; i++) {
 487                     if (!ensureVirtualized.get(i)) {
 488                         ObjectState state = initialState.getObjectStateOptional(i);
 489                         if (state != null && state.isVirtual()) {
 490                             for (ValueNode entry : state.getEntries()) {
 491                                 if (entry instanceof VirtualObjectNode) {
 492                                     if (ensureVirtualized.get(((VirtualObjectNode) entry).getObjectId())) {
 493                                         change = true;
 494                                         ensureVirtualized.set(i);
 495                                         break;
 496                                     }
 497                                 }
 498                             }
 499                         }
 500                     }
 501                 }
 502             } while (change);
 503 
 504             for (int i = 0; i < length; i++) {
 505                 ObjectState state = initialState.getObjectStateOptional(i);
 506                 if (state != null && state.isVirtual() && !ensureVirtualized.get(i)) {
 507                     initialState.materializeBefore(end, virtualObjects.get(i), blockEffects.get(loopPredecessor));
 508                 }
 509             }
 510         }
 511         return initialState;
 512     }
 513 
 514     @Override
 515     protected void processInitialLoopState(Loop<Block> loop, BlockT initialState) {
 516         for (PhiNode phi : ((LoopBeginNode) loop.getHeader().getBeginNode()).phis()) {
 517             if (phi.valueAt(0) != null) {
 518                 ValueNode alias = getAliasAndResolve(initialState, phi.valueAt(0));
 519                 if (alias instanceof VirtualObjectNode) {
 520                     VirtualObjectNode virtual = (VirtualObjectNode) alias;
 521                     addVirtualAlias(virtual, phi);
 522                 } else {
 523                     aliases.set(phi, null);
 524                 }
 525             }
 526         }
 527     }
 528 
 529     @Override
 530     protected void processLoopExit(LoopExitNode exitNode, BlockT initialState, BlockT exitState, GraphEffectList effects) {
 531         if (exitNode.graph().hasValueProxies()) {
 532             EconomicMap<Integer, ProxyNode> proxies = EconomicMap.create(Equivalence.DEFAULT);
 533             for (ProxyNode proxy : exitNode.proxies()) {
 534                 ValueNode alias = getAlias(proxy.value());
 535                 if (alias instanceof VirtualObjectNode) {
 536                     VirtualObjectNode virtual = (VirtualObjectNode) alias;
 537                     proxies.put(virtual.getObjectId(), proxy);
 538                 }
 539             }
 540             for (int i = 0; i < exitState.getStateCount(); i++) {
 541                 ObjectState exitObjState = exitState.getObjectStateOptional(i);
 542                 if (exitObjState != null) {
 543                     ObjectState initialObjState = initialState.getObjectStateOptional(i);
 544 
 545                     if (exitObjState.isVirtual()) {
 546                         processVirtualAtLoopExit(exitNode, effects, i, exitObjState, initialObjState, exitState);
 547                     } else {
 548                         processMaterializedAtLoopExit(exitNode, effects, proxies, i, exitObjState, initialObjState, exitState);
 549                     }
 550                 }
 551             }
 552         }
 553     }
 554 
 555     private static void processMaterializedAtLoopExit(LoopExitNode exitNode, GraphEffectList effects, EconomicMap<Integer, ProxyNode> proxies, int object, ObjectState exitObjState,
 556                     ObjectState initialObjState, PartialEscapeBlockState<?> exitState) {
 557         if (initialObjState == null || initialObjState.isVirtual()) {
 558             ProxyNode proxy = proxies.get(object);
 559             if (proxy == null) {
 560                 proxy = new ValueProxyNode(exitObjState.getMaterializedValue(), exitNode);
 561                 effects.addFloatingNode(proxy, "proxy");
 562             } else {
 563                 effects.replaceFirstInput(proxy, proxy.value(), exitObjState.getMaterializedValue());
 564                 // nothing to do - will be handled in processNode
 565             }
 566             exitState.updateMaterializedValue(object, proxy);
 567         } else {
 568             if (initialObjState.getMaterializedValue() != exitObjState.getMaterializedValue()) {
 569                 exitNode.getDebug().log("materialized value changes within loop: %s vs. %s at %s", initialObjState.getMaterializedValue(), exitObjState.getMaterializedValue(), exitNode);
 570             }
 571         }
 572     }
 573 
 574     private static void processVirtualAtLoopExit(LoopExitNode exitNode, GraphEffectList effects, int object, ObjectState exitObjState, ObjectState initialObjState,
 575                     PartialEscapeBlockState<?> exitState) {
 576         for (int i = 0; i < exitObjState.getEntries().length; i++) {
 577             ValueNode value = exitState.getObjectState(object).getEntry(i);
 578             if (!(value instanceof VirtualObjectNode || value.isConstant())) {
 579                 if (exitNode.loopBegin().isPhiAtMerge(value) || initialObjState == null || !initialObjState.isVirtual() || initialObjState.getEntry(i) != value) {
 580                     ProxyNode proxy = new ValueProxyNode(value, exitNode);
 581                     exitState.setEntry(object, i, proxy);
 582                     effects.addFloatingNode(proxy, "virtualProxy");
 583                 }
 584             }
 585         }
 586     }
 587 
 588     @Override
 589     protected MergeProcessor createMergeProcessor(Block merge) {
 590         return new MergeProcessor(merge);
 591     }
 592 
 593     protected class MergeProcessor extends EffectsClosure<BlockT>.MergeProcessor {
 594 
 595         private EconomicMap<Object, ValuePhiNode> materializedPhis;
 596         private EconomicMap<ValueNode, ValuePhiNode[]> valuePhis;
 597         private EconomicMap<ValuePhiNode, VirtualObjectNode> valueObjectVirtuals;
 598         private final boolean needsCaching;
 599 
 600         public MergeProcessor(Block mergeBlock) {
 601             super(mergeBlock);
 602             // merge will only be called multiple times for loop headers
 603             needsCaching = mergeBlock.isLoopHeader();
 604         }
 605 
 606         protected <T> PhiNode getPhi(T virtual, Stamp stamp) {
 607             if (needsCaching) {
 608                 return getPhiCached(virtual, stamp);
 609             } else {
 610                 return createValuePhi(stamp);
 611             }
 612         }
 613 
 614         private <T> PhiNode getPhiCached(T virtual, Stamp stamp) {
 615             if (materializedPhis == null) {
 616                 materializedPhis = EconomicMap.create(Equivalence.DEFAULT);
 617             }
 618             ValuePhiNode result = materializedPhis.get(virtual);
 619             if (result == null) {
 620                 result = createValuePhi(stamp);
 621                 materializedPhis.put(virtual, result);
 622             }
 623             return result;
 624         }
 625 
 626         private PhiNode[] getValuePhis(ValueNode key, int entryCount) {
 627             if (needsCaching) {
 628                 return getValuePhisCached(key, entryCount);
 629             } else {
 630                 return new ValuePhiNode[entryCount];
 631             }
 632         }
 633 
 634         private PhiNode[] getValuePhisCached(ValueNode key, int entryCount) {
 635             if (valuePhis == null) {
 636                 valuePhis = EconomicMap.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE);
 637             }
 638             ValuePhiNode[] result = valuePhis.get(key);
 639             if (result == null) {
 640                 result = new ValuePhiNode[entryCount];
 641                 valuePhis.put(key, result);
 642             }
 643             assert result.length == entryCount;
 644             return result;
 645         }
 646 
 647         private VirtualObjectNode getValueObjectVirtual(ValuePhiNode phi, VirtualObjectNode virtual) {
 648             if (needsCaching) {
 649                 return getValueObjectVirtualCached(phi, virtual);
 650             } else {
 651                 VirtualObjectNode duplicate = virtual.duplicate();
 652                 duplicate.setNodeSourcePosition(virtual.getNodeSourcePosition());
 653                 return duplicate;
 654             }
 655         }
 656 
 657         private VirtualObjectNode getValueObjectVirtualCached(ValuePhiNode phi, VirtualObjectNode virtual) {
 658             if (valueObjectVirtuals == null) {
 659                 valueObjectVirtuals = EconomicMap.create(Equivalence.IDENTITY);
 660             }
 661             VirtualObjectNode result = valueObjectVirtuals.get(phi);
 662             if (result == null) {
 663                 result = virtual.duplicate();
 664                 result.setNodeSourcePosition(virtual.getNodeSourcePosition());
 665                 valueObjectVirtuals.put(phi, result);
 666             }
 667             return result;
 668         }
 669 
 670         /**
 671          * Merge all predecessor block states into one block state. This is an iterative process,
 672          * because merging states can lead to materializations which make previous parts of the
 673          * merging operation invalid. The merging process is executed until a stable state has been
 674          * reached. This method needs to be careful to place the effects of the merging operation
 675          * into the correct blocks.
 676          *
 677          * @param statesList the predecessor block states of the merge
 678          */
 679         @Override
 680         protected void merge(List<BlockT> statesList) {
 681 
 682             PartialEscapeBlockState<?>[] states = new PartialEscapeBlockState<?>[statesList.size()];
 683             for (int i = 0; i < statesList.size(); i++) {
 684                 states[i] = statesList.get(i);
 685             }
 686 
 687             // calculate the set of virtual objects that exist in all predecessors
 688             int[] virtualObjTemp = intersectVirtualObjects(states);
 689 
 690             boolean materialized;
 691             do {
 692                 materialized = false;
 693 
 694                 if (PartialEscapeBlockState.identicalObjectStates(states)) {
 695                     newState.adoptAddObjectStates(states[0]);
 696                 } else {
 697 
 698                     for (int object : virtualObjTemp) {
 699                         if (PartialEscapeBlockState.identicalObjectStates(states, object)) {
 700                             newState.addObject(object, states[0].getObjectState(object).share());
 701                             continue;
 702                         }
 703 
 704                         // determine if all inputs are virtual or the same materialized value
 705                         int virtualCount = 0;
 706                         ObjectState startObj = states[0].getObjectState(object);
 707                         boolean locksMatch = true;
 708                         boolean ensureVirtual = true;
 709                         ValueNode uniqueMaterializedValue = startObj.isVirtual() ? null : startObj.getMaterializedValue();
 710                         for (int i = 0; i < states.length; i++) {
 711                             ObjectState obj = states[i].getObjectState(object);
 712                             ensureVirtual &= obj.getEnsureVirtualized();
 713                             if (obj.isVirtual()) {
 714                                 virtualCount++;
 715                                 uniqueMaterializedValue = null;
 716                                 locksMatch &= obj.locksEqual(startObj);
 717                             } else if (obj.getMaterializedValue() != uniqueMaterializedValue) {
 718                                 uniqueMaterializedValue = null;
 719                             }
 720                         }
 721 
 722                         if (virtualCount == states.length && locksMatch) {
 723                             materialized |= mergeObjectStates(object, null, states);
 724                         } else {
 725                             if (uniqueMaterializedValue != null) {
 726                                 newState.addObject(object, new ObjectState(uniqueMaterializedValue, null, ensureVirtual));
 727                             } else {
 728                                 PhiNode materializedValuePhi = getPhi(object, StampFactory.forKind(JavaKind.Object));
 729                                 mergeEffects.addFloatingNode(materializedValuePhi, "materializedPhi");
 730                                 for (int i = 0; i < states.length; i++) {
 731                                     ObjectState obj = states[i].getObjectState(object);
 732                                     if (obj.isVirtual()) {
 733                                         Block predecessor = getPredecessor(i);
 734                                         if (!ensureVirtual && obj.isVirtual()) {
 735                                             // we can materialize if not all inputs are
 736                                             // "ensureVirtualized"
 737                                             obj.setEnsureVirtualized(false);
 738                                         }
 739                                         materialized |= ensureMaterialized(states[i], object, predecessor.getEndNode(), blockEffects.get(predecessor), COUNTER_MATERIALIZATIONS_MERGE);
 740                                         obj = states[i].getObjectState(object);
 741                                     }
 742                                     setPhiInput(materializedValuePhi, i, obj.getMaterializedValue());
 743                                 }
 744                                 newState.addObject(object, new ObjectState(materializedValuePhi, null, false));
 745                             }
 746                         }
 747                     }
 748                 }
 749 
 750                 for (PhiNode phi : getPhis()) {
 751                     aliases.set(phi, null);
 752                     if (hasVirtualInputs.isMarked(phi) && phi instanceof ValuePhiNode) {
 753                         materialized |= processPhi((ValuePhiNode) phi, states);
 754                     }
 755                 }
 756                 if (materialized) {
 757                     newState.resetObjectStates(virtualObjects.size());
 758                     mergeEffects.clear();
 759                     afterMergeEffects.clear();
 760                 }
 761             } while (materialized);
 762         }
 763 
 764         private int[] intersectVirtualObjects(PartialEscapeBlockState<?>[] states) {
 765             int length = states[0].getStateCount();
 766             for (int i = 1; i < states.length; i++) {
 767                 length = Math.min(length, states[i].getStateCount());
 768             }
 769 
 770             int count = 0;
 771             for (int objectIndex = 0; objectIndex < length; objectIndex++) {
 772                 if (intersectObjectState(states, objectIndex)) {
 773                     count++;
 774                 }
 775             }
 776 
 777             int index = 0;
 778             int[] resultInts = new int[count];
 779             for (int objectIndex = 0; objectIndex < length; objectIndex++) {
 780                 if (intersectObjectState(states, objectIndex)) {
 781                     resultInts[index++] = objectIndex;
 782                 }
 783             }
 784             assert index == count;
 785             return resultInts;
 786         }
 787 
 788         private boolean intersectObjectState(PartialEscapeBlockState<?>[] states, int objectIndex) {
 789             for (int i = 0; i < states.length; i++) {
 790                 PartialEscapeBlockState<?> state = states[i];
 791                 if (state.getObjectStateOptional(objectIndex) == null) {
 792                     return false;
 793                 }
 794             }
 795             return true;
 796         }
 797 
 798         /**
 799          * Try to merge multiple virtual object states into a single object state. If the incoming
 800          * object states are compatible, then this method will create PhiNodes for the object's
 801          * entries where needed. If they are incompatible, then all incoming virtual objects will be
 802          * materialized, and a PhiNode for the materialized values will be created. Object states
 803          * can be incompatible if they contain {@code long} or {@code double} values occupying two
 804          * {@code int} slots in such a way that that their values cannot be merged using PhiNodes.
 805          *
 806          * @param states the predecessor block states of the merge
 807          * @return true if materialization happened during the merge, false otherwise
 808          */
 809         private boolean mergeObjectStates(int resultObject, int[] sourceObjects, PartialEscapeBlockState<?>[] states) {
 810             boolean compatible = true;
 811             boolean ensureVirtual = true;
 812             IntUnaryOperator getObject = index -> sourceObjects == null ? resultObject : sourceObjects[index];
 813 
 814             VirtualObjectNode virtual = virtualObjects.get(resultObject);
 815             int entryCount = virtual.entryCount();
 816 
 817             // determine all entries that have a two-slot value
 818             JavaKind[] twoSlotKinds = null;
 819             outer: for (int i = 0; i < states.length; i++) {
 820                 ObjectState objectState = states[i].getObjectState(getObject.applyAsInt(i));
 821                 ValueNode[] entries = objectState.getEntries();
 822                 int valueIndex = 0;
 823                 ensureVirtual &= objectState.getEnsureVirtualized();
 824                 while (valueIndex < entryCount) {
 825                     JavaKind otherKind = entries[valueIndex].getStackKind();
 826                     JavaKind entryKind = virtual.entryKind(valueIndex);
 827                     if (entryKind == JavaKind.Int && otherKind.needsTwoSlots()) {
 828                         if (twoSlotKinds == null) {
 829                             twoSlotKinds = new JavaKind[entryCount];
 830                         }
 831                         if (twoSlotKinds[valueIndex] != null && twoSlotKinds[valueIndex] != otherKind) {
 832                             compatible = false;
 833                             break outer;
 834                         }
 835                         twoSlotKinds[valueIndex] = otherKind;
 836                         // skip the next entry
 837                         valueIndex++;
 838                     } else {
 839                         assert entryKind.getStackKind() == otherKind.getStackKind() || (entryKind == JavaKind.Int && otherKind == JavaKind.Illegal) ||
 840                                         entryKind.getBitCount() >= otherKind.getBitCount() : entryKind + " vs " + otherKind;
 841                     }
 842                     valueIndex++;
 843                 }
 844             }
 845             if (compatible && twoSlotKinds != null) {
 846                 // if there are two-slot values then make sure the incoming states can be merged
 847                 outer: for (int valueIndex = 0; valueIndex < entryCount; valueIndex++) {
 848                     if (twoSlotKinds[valueIndex] != null) {
 849                         assert valueIndex < virtual.entryCount() - 1 && virtual.entryKind(valueIndex) == JavaKind.Int && virtual.entryKind(valueIndex + 1) == JavaKind.Int;
 850                         for (int i = 0; i < states.length; i++) {
 851                             int object = getObject.applyAsInt(i);
 852                             ObjectState objectState = states[i].getObjectState(object);
 853                             ValueNode value = objectState.getEntry(valueIndex);
 854                             JavaKind valueKind = value.getStackKind();
 855                             if (valueKind != twoSlotKinds[valueIndex]) {
 856                                 ValueNode nextValue = objectState.getEntry(valueIndex + 1);
 857                                 if (value.isConstant() && value.asConstant().equals(JavaConstant.INT_0) && nextValue.isConstant() && nextValue.asConstant().equals(JavaConstant.INT_0)) {
 858                                     // rewrite to a zero constant of the larger kind
 859                                     debug.log("Rewriting entry %s to constant of larger size", valueIndex);
 860                                     states[i].setEntry(object, valueIndex, ConstantNode.defaultForKind(twoSlotKinds[valueIndex], graph()));
 861                                     states[i].setEntry(object, valueIndex + 1, ConstantNode.forConstant(JavaConstant.forIllegal(), tool.getMetaAccessProvider(), graph()));
 862                                 } else {
 863                                     compatible = false;
 864                                     break outer;
 865                                 }
 866                             }
 867                         }
 868                     }
 869                 }
 870             }
 871 
 872             if (compatible) {
 873                 // virtual objects are compatible: create phis for all entries that need them
 874                 ValueNode[] values = states[0].getObjectState(getObject.applyAsInt(0)).getEntries().clone();
 875                 PhiNode[] phis = getValuePhis(virtual, virtual.entryCount());
 876                 int valueIndex = 0;
 877                 while (valueIndex < values.length) {
 878                     for (int i = 1; i < states.length; i++) {
 879                         if (phis[valueIndex] == null) {
 880                             ValueNode field = states[i].getObjectState(getObject.applyAsInt(i)).getEntry(valueIndex);
 881                             if (values[valueIndex] != field) {
 882                                 phis[valueIndex] = createValuePhi(values[valueIndex].stamp(NodeView.DEFAULT).unrestricted());
 883                             }
 884                         }
 885                     }
 886                     if (phis[valueIndex] != null && !phis[valueIndex].stamp(NodeView.DEFAULT).isCompatible(values[valueIndex].stamp(NodeView.DEFAULT))) {
 887                         phis[valueIndex] = createValuePhi(values[valueIndex].stamp(NodeView.DEFAULT).unrestricted());
 888                     }
 889                     if (twoSlotKinds != null && twoSlotKinds[valueIndex] != null) {
 890                         // skip an entry after a long/double value that occupies two int slots
 891                         valueIndex++;
 892                         phis[valueIndex] = null;
 893                         values[valueIndex] = ConstantNode.forConstant(JavaConstant.forIllegal(), tool.getMetaAccessProvider(), graph());
 894                     }
 895                     valueIndex++;
 896                 }
 897 
 898                 boolean materialized = false;
 899                 for (int i = 0; i < values.length; i++) {
 900                     PhiNode phi = phis[i];
 901                     if (phi != null) {
 902                         mergeEffects.addFloatingNode(phi, "virtualMergePhi");
 903                         if (virtual.entryKind(i) == JavaKind.Object) {
 904                             materialized |= mergeObjectEntry(getObject, states, phi, i);
 905                         } else {
 906                             for (int i2 = 0; i2 < states.length; i2++) {
 907                                 ObjectState state = states[i2].getObjectState(getObject.applyAsInt(i2));
 908                                 if (!state.isVirtual()) {
 909                                     break;
 910                                 }
 911                                 setPhiInput(phi, i2, state.getEntry(i));
 912                             }
 913                         }
 914                         values[i] = phi;
 915                     }
 916                 }
 917                 newState.addObject(resultObject, new ObjectState(values, states[0].getObjectState(getObject.applyAsInt(0)).getLocks(), ensureVirtual));
 918                 return materialized;
 919             } else {
 920                 // not compatible: materialize in all predecessors
 921                 PhiNode materializedValuePhi = getPhi(resultObject, StampFactory.forKind(JavaKind.Object));
 922                 for (int i = 0; i < states.length; i++) {
 923                     Block predecessor = getPredecessor(i);
 924                     if (!ensureVirtual && states[i].getObjectState(getObject.applyAsInt(i)).isVirtual()) {
 925                         // we can materialize if not all inputs are "ensureVirtualized"
 926                         states[i].getObjectState(getObject.applyAsInt(i)).setEnsureVirtualized(false);
 927                     }
 928                     ensureMaterialized(states[i], getObject.applyAsInt(i), predecessor.getEndNode(), blockEffects.get(predecessor), COUNTER_MATERIALIZATIONS_MERGE);
 929                     setPhiInput(materializedValuePhi, i, states[i].getObjectState(getObject.applyAsInt(i)).getMaterializedValue());
 930                 }
 931                 newState.addObject(resultObject, new ObjectState(materializedValuePhi, null, ensureVirtual));
 932                 return true;
 933             }
 934         }
 935 
 936         /**
 937          * Fill the inputs of the PhiNode corresponding to one {@link JavaKind#Object} entry in the
 938          * virtual object.
 939          *
 940          * @return true if materialization happened during the merge, false otherwise
 941          */
 942         private boolean mergeObjectEntry(IntUnaryOperator objectIdFunc, PartialEscapeBlockState<?>[] states, PhiNode phi, int entryIndex) {
 943             boolean materialized = false;
 944             for (int i = 0; i < states.length; i++) {
 945                 int object = objectIdFunc.applyAsInt(i);
 946                 ObjectState objectState = states[i].getObjectState(object);
 947                 if (!objectState.isVirtual()) {
 948                     break;
 949                 }
 950                 ValueNode entry = objectState.getEntry(entryIndex);
 951                 if (entry instanceof VirtualObjectNode) {
 952                     VirtualObjectNode entryVirtual = (VirtualObjectNode) entry;
 953                     Block predecessor = getPredecessor(i);
 954                     materialized |= ensureMaterialized(states[i], entryVirtual.getObjectId(), predecessor.getEndNode(), blockEffects.get(predecessor), COUNTER_MATERIALIZATIONS_MERGE);
 955                     objectState = states[i].getObjectState(object);
 956                     if (objectState.isVirtual()) {
 957                         states[i].setEntry(object, entryIndex, entry = states[i].getObjectState(entryVirtual.getObjectId()).getMaterializedValue());
 958                     }
 959                 }
 960                 setPhiInput(phi, i, entry);
 961             }
 962             return materialized;
 963         }
 964 
 965         /**
 966          * Examine a PhiNode and try to replace it with merging of virtual objects if all its inputs
 967          * refer to virtual object states. In order for the merging to happen, all incoming object
 968          * states need to be compatible and without object identity (meaning that their object
 969          * identity if not used later on).
 970          *
 971          * @param phi the PhiNode that should be processed
 972          * @param states the predecessor block states of the merge
 973          * @return true if materialization happened during the merge, false otherwise
 974          */
 975         private boolean processPhi(ValuePhiNode phi, PartialEscapeBlockState<?>[] states) {
 976 
 977             // determine how many inputs are virtual and if they're all the same virtual object
 978             int virtualInputs = 0;
 979             boolean uniqueVirtualObject = true;
 980             boolean ensureVirtual = true;
 981             VirtualObjectNode[] virtualObjs = new VirtualObjectNode[states.length];
 982             for (int i = 0; i < states.length; i++) {
 983                 ValueNode alias = getAlias(getPhiValueAt(phi, i));
 984                 if (alias instanceof VirtualObjectNode) {
 985                     VirtualObjectNode virtual = (VirtualObjectNode) alias;
 986                     virtualObjs[i] = virtual;
 987                     ObjectState objectState = states[i].getObjectStateOptional(virtual);
 988                     if (objectState == null) {
 989                         assert getPhiValueAt(phi, i) instanceof PhiNode : "this should only happen for phi nodes";
 990                         return false;
 991                     }
 992                     if (objectState.isVirtual()) {
 993                         if (virtualObjs[0] != alias) {
 994                             uniqueVirtualObject = false;
 995                         }
 996                         ensureVirtual &= objectState.getEnsureVirtualized();
 997                         virtualInputs++;
 998                     }
 999                 }
1000             }
1001             if (virtualInputs == states.length) {
1002                 if (uniqueVirtualObject) {
1003                     // all inputs refer to the same object: just make the phi node an alias
1004                     addVirtualAlias(virtualObjs[0], phi);
1005                     mergeEffects.deleteNode(phi);
1006                     return false;
1007                 } else {
1008                     // all inputs are virtual: check if they're compatible and without identity
1009                     boolean compatible = true;
1010                     VirtualObjectNode firstVirtual = virtualObjs[0];
1011                     for (int i = 0; i < states.length; i++) {
1012                         VirtualObjectNode virtual = virtualObjs[i];
1013 
1014                         if (!firstVirtual.type().equals(virtual.type()) || firstVirtual.entryCount() != virtual.entryCount()) {
1015                             compatible = false;
1016                             break;
1017                         }
1018                         if (!states[0].getObjectState(firstVirtual).locksEqual(states[i].getObjectState(virtual))) {
1019                             compatible = false;
1020                             break;
1021                         }
1022                     }
1023                     if (compatible) {
1024                         for (int i = 0; i < states.length; i++) {
1025                             VirtualObjectNode virtual = virtualObjs[i];
1026                             /*
1027                              * check whether we trivially see that this is the only reference to
1028                              * this allocation
1029                              */
1030                             if (virtual.hasIdentity() && !isSingleUsageAllocation(getPhiValueAt(phi, i), virtualObjs, states[i])) {
1031                                 compatible = false;
1032                             }
1033                         }
1034                     }
1035                     if (compatible) {
1036                         VirtualObjectNode virtual = getValueObjectVirtual(phi, virtualObjs[0]);
1037                         mergeEffects.addFloatingNode(virtual, "valueObjectNode");
1038                         mergeEffects.deleteNode(phi);
1039                         if (virtual.getObjectId() == -1) {
1040                             int id = virtualObjects.size();
1041                             virtualObjects.add(virtual);
1042                             virtual.setObjectId(id);
1043                         }
1044 
1045                         int[] virtualObjectIds = new int[states.length];
1046                         for (int i = 0; i < states.length; i++) {
1047                             virtualObjectIds[i] = virtualObjs[i].getObjectId();
1048                         }
1049                         boolean materialized = mergeObjectStates(virtual.getObjectId(), virtualObjectIds, states);
1050                         addVirtualAlias(virtual, virtual);
1051                         addVirtualAlias(virtual, phi);
1052                         return materialized;
1053                     }
1054                 }
1055             }
1056 
1057             // otherwise: materialize all phi inputs
1058             boolean materialized = false;
1059             if (virtualInputs > 0) {
1060                 for (int i = 0; i < states.length; i++) {
1061                     VirtualObjectNode virtual = virtualObjs[i];
1062                     if (virtual != null) {
1063                         Block predecessor = getPredecessor(i);
1064                         if (!ensureVirtual && states[i].getObjectState(virtual).isVirtual()) {
1065                             // we can materialize if not all inputs are "ensureVirtualized"
1066                             states[i].getObjectState(virtual).setEnsureVirtualized(false);
1067                         }
1068                         materialized |= ensureMaterialized(states[i], virtual.getObjectId(), predecessor.getEndNode(), blockEffects.get(predecessor), COUNTER_MATERIALIZATIONS_PHI);
1069                     }
1070                 }
1071             }
1072             for (int i = 0; i < states.length; i++) {
1073                 VirtualObjectNode virtual = virtualObjs[i];
1074                 if (virtual != null) {
1075                     setPhiInput(phi, i, getAliasAndResolve(states[i], virtual));
1076                 }
1077             }
1078             return materialized;
1079         }
1080 
1081         private boolean isSingleUsageAllocation(ValueNode value, VirtualObjectNode[] virtualObjs, PartialEscapeBlockState<?> state) {
1082             /*
1083              * If the phi input is an allocation, we know that it is a "fresh" value, i.e., that
1084              * this is a value that will only appear through this source, and cannot appear anywhere
1085              * else. If the phi is also the only usage of this input, we know that no other place
1086              * can check object identity against it, so it is safe to lose the object identity here.
1087              */
1088             if (!(value instanceof AllocatedObjectNode && value.hasExactlyOneUsage())) {
1089                 return false;
1090             }
1091 
1092             /*
1093              * Check that the state only references the one virtual object from the Phi.
1094              */
1095             VirtualObjectNode singleVirtual = null;
1096             for (int v = 0; v < virtualObjs.length; v++) {
1097                 if (state.contains(virtualObjs[v])) {
1098                     if (singleVirtual == null) {
1099                         singleVirtual = virtualObjs[v];
1100                     } else if (singleVirtual != virtualObjs[v]) {
1101                         /*
1102                          * More than one virtual object is visible in the object state.
1103                          */
1104                         return false;
1105                     }
1106                 }
1107             }
1108             return true;
1109         }
1110     }
1111 
1112     public ObjectState getObjectState(PartialEscapeBlockState<?> state, ValueNode value) {
1113         if (value == null) {
1114             return null;
1115         }
1116         if (value.isAlive() && !aliases.isNew(value)) {
1117             ValueNode object = aliases.get(value);
1118             return object instanceof VirtualObjectNode ? state.getObjectStateOptional((VirtualObjectNode) object) : null;
1119         } else {
1120             if (value instanceof VirtualObjectNode) {
1121                 return state.getObjectStateOptional((VirtualObjectNode) value);
1122             }
1123             return null;
1124         }
1125     }
1126 
1127     public ValueNode getAlias(ValueNode value) {
1128         if (value != null && !(value instanceof VirtualObjectNode)) {
1129             if (value.isAlive() && !aliases.isNew(value)) {
1130                 ValueNode result = aliases.get(value);
1131                 if (result != null) {
1132                     return result;
1133                 }
1134             }
1135         }
1136         return value;
1137     }
1138 
1139     public ValueNode getAliasAndResolve(PartialEscapeBlockState<?> state, ValueNode value) {
1140         ValueNode result = getAlias(value);
1141         if (result instanceof VirtualObjectNode) {
1142             int id = ((VirtualObjectNode) result).getObjectId();
1143             if (id != -1 && !state.getObjectState(id).isVirtual()) {
1144                 result = state.getObjectState(id).getMaterializedValue();
1145             }
1146         }
1147         return result;
1148     }
1149 
1150     void addVirtualAlias(VirtualObjectNode virtual, ValueNode node) {
1151         if (node.isAlive()) {
1152             aliases.set(node, virtual);
1153             for (Node usage : node.usages()) {
1154                 markVirtualUsages(usage);
1155             }
1156         }
1157     }
1158 
1159     private void markVirtualUsages(Node node) {
1160         if (!hasVirtualInputs.isNew(node) && !hasVirtualInputs.isMarked(node)) {
1161             hasVirtualInputs.mark(node);
1162             if (node instanceof VirtualState) {
1163                 for (Node usage : node.usages()) {
1164                     markVirtualUsages(usage);
1165                 }
1166             }
1167         }
1168     }
1169 }