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