
jdk.graal.compiler.virtual.phases.ea.PartialEscapeClosure Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of compiler Show documentation
Show all versions of compiler Show documentation
The GraalVM compiler and the Graal-truffle optimizer.
/*
* Copyright (c) 2011, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.graal.compiler.virtual.phases.ea;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.Iterator;
import java.util.List;
import java.util.function.IntUnaryOperator;
import org.graalvm.collections.EconomicMap;
import org.graalvm.collections.EconomicSet;
import org.graalvm.collections.Equivalence;
import org.graalvm.collections.MapCursor;
import jdk.graal.compiler.core.common.GraalOptions;
import jdk.graal.compiler.core.common.RetryableBailoutException;
import jdk.graal.compiler.core.common.cfg.Loop;
import jdk.graal.compiler.core.common.type.Stamp;
import jdk.graal.compiler.core.common.type.StampFactory;
import jdk.graal.compiler.debug.Assertions;
import jdk.graal.compiler.debug.CounterKey;
import jdk.graal.compiler.debug.DebugContext;
import jdk.graal.compiler.debug.GraalError;
import jdk.graal.compiler.graph.Node;
import jdk.graal.compiler.graph.NodeBitMap;
import jdk.graal.compiler.graph.NodeInputList;
import jdk.graal.compiler.graph.Position;
import jdk.graal.compiler.nodes.AbstractEndNode;
import jdk.graal.compiler.nodes.CallTargetNode;
import jdk.graal.compiler.nodes.ConstantNode;
import jdk.graal.compiler.nodes.ControlSinkNode;
import jdk.graal.compiler.nodes.FixedNode;
import jdk.graal.compiler.nodes.FixedWithNextNode;
import jdk.graal.compiler.nodes.FrameState;
import jdk.graal.compiler.nodes.GraphState.StageFlag;
import jdk.graal.compiler.nodes.Invoke;
import jdk.graal.compiler.nodes.LoopBeginNode;
import jdk.graal.compiler.nodes.LoopExitNode;
import jdk.graal.compiler.nodes.NodeView;
import jdk.graal.compiler.nodes.PhiNode;
import jdk.graal.compiler.nodes.ProxyNode;
import jdk.graal.compiler.nodes.StructuredGraph;
import jdk.graal.compiler.nodes.StructuredGraph.ScheduleResult;
import jdk.graal.compiler.nodes.UnwindNode;
import jdk.graal.compiler.nodes.ValueNode;
import jdk.graal.compiler.nodes.ValuePhiNode;
import jdk.graal.compiler.nodes.ValueProxyNode;
import jdk.graal.compiler.nodes.VirtualState;
import jdk.graal.compiler.nodes.cfg.HIRBlock;
import jdk.graal.compiler.nodes.java.AbstractNewObjectNode;
import jdk.graal.compiler.nodes.java.AccessMonitorNode;
import jdk.graal.compiler.nodes.java.MonitorEnterNode;
import jdk.graal.compiler.nodes.spi.Canonicalizable;
import jdk.graal.compiler.nodes.spi.CoreProviders;
import jdk.graal.compiler.nodes.spi.NodeWithState;
import jdk.graal.compiler.nodes.spi.Virtualizable;
import jdk.graal.compiler.nodes.spi.VirtualizableAllocation;
import jdk.graal.compiler.nodes.spi.VirtualizerTool;
import jdk.graal.compiler.nodes.virtual.AllocatedObjectNode;
import jdk.graal.compiler.nodes.virtual.CommitAllocationNode;
import jdk.graal.compiler.nodes.virtual.EnsureVirtualizedNode;
import jdk.graal.compiler.nodes.virtual.EscapeObjectState;
import jdk.graal.compiler.nodes.virtual.VirtualObjectNode;
import jdk.graal.compiler.nodes.virtual.VirtualObjectState;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
public abstract class PartialEscapeClosure> extends EffectsClosure {
public static final CounterKey COUNTER_MATERIALIZATIONS = DebugContext.counter("Materializations");
public static final CounterKey COUNTER_MATERIALIZATIONS_PHI = DebugContext.counter("MaterializationsPhi");
public static final CounterKey COUNTER_MATERIALIZATIONS_MERGE = DebugContext.counter("MaterializationsMerge");
public static final CounterKey COUNTER_MATERIALIZATIONS_UNHANDLED = DebugContext.counter("MaterializationsUnhandled");
public static final CounterKey COUNTER_MATERIALIZATIONS_LOOP_EXIT = DebugContext.counter("MaterializationsLoopExit");
public static final CounterKey COUNTER_ALLOCATION_REMOVED = DebugContext.counter("AllocationsRemoved");
public static final CounterKey COUNTER_MEMORYCHECKPOINT = DebugContext.counter("MemoryCheckpoint");
/**
* Nodes with inputs that were modified during analysis are marked in this bitset - this way
* nodes that are not influenced at all by analysis can be rejected quickly.
*/
private final NodeBitMap hasVirtualInputs;
/**
* This is handed out to implementers of {@link Virtualizable}.
*/
protected final VirtualizerToolImpl tool;
/**
* The indexes into this array correspond to {@link VirtualObjectNode#getObjectId()}.
*/
public final ArrayList virtualObjects = new ArrayList<>();
/**
* Indicates whether lock order must be preserved during PEA transformation.
*/
public final boolean requiresStrictLockOrder;
@Override
public boolean needsApplyEffects() {
if (hasChanged()) {
return true;
}
/*
* If there is a mismatch between the number of materializations and the number of
* virtualizations, we need to apply effects, even if there were no other significant
* changes to the graph. This applies to each block, since moving from one block to the
* other can also be important (if the probabilities of the block differ).
*/
for (HIRBlock block : cfg.getBlocks()) {
GraphEffectList effects = blockEffects.get(block);
if (effects != null) {
if (effects.getVirtualizationDelta() != 0 || effects.getAllocationDelta() != 0) {
return true;
}
}
}
return false;
}
private final class CollectVirtualObjectsClosure2 implements VirtualState.NodePositionClosure {
private final EconomicSet virtual;
private final GraphEffectList effects;
private final BlockT state;
private CollectVirtualObjectsClosure2(EconomicSet virtual, GraphEffectList effects, BlockT state) {
this.virtual = virtual;
this.effects = effects;
this.state = state;
}
@Override
public void apply(Node from, Position p) {
ValueNode value = (ValueNode) p.get(from);
Node usage = from;
if (value instanceof VirtualObjectNode) {
VirtualObjectNode object = (VirtualObjectNode) value;
if (object.getObjectId() != -1 && state.getObjectStateOptional(object) != null) {
virtual.add(object);
}
} else {
ValueNode alias = getAlias(value);
if (alias instanceof VirtualObjectNode) {
VirtualObjectNode object = (VirtualObjectNode) alias;
virtual.add(object);
effects.replaceFirstInput(usage, value, object);
}
}
}
}
/**
* Final subclass of PartialEscapeClosure, for performance and to make everything behave nicely
* with generics.
*/
public static final class Final extends PartialEscapeClosure {
public Final(ScheduleResult schedule, CoreProviders providers) {
super(schedule, providers);
}
@Override
protected PartialEscapeBlockState.Final getInitialState() {
return new PartialEscapeBlockState.Final(tool.getOptions(), tool.getDebug());
}
@Override
protected PartialEscapeBlockState.Final cloneState(PartialEscapeBlockState.Final oldState) {
return new PartialEscapeBlockState.Final(oldState);
}
}
@SuppressWarnings("this-escape")
public PartialEscapeClosure(ScheduleResult schedule, CoreProviders providers) {
super(schedule, schedule.getCFG());
StructuredGraph graph = schedule.getCFG().graph;
this.hasVirtualInputs = graph.createNodeBitMap();
this.tool = new VirtualizerToolImpl(providers, this, graph.getAssumptions(), graph.getOptions(), debug);
this.requiresStrictLockOrder = providers.getPlatformConfigurationProvider().requiresStrictLockOrder();
}
/**
* @return true if the node was deleted, false otherwise
*/
@Override
protected boolean processNode(Node node, BlockT state, GraphEffectList effects, FixedWithNextNode lastFixedNode) {
/*
* These checks make up for the fact that an earliest schedule moves CallTargetNodes upwards
* and thus materializes virtual objects needlessly. Also, FrameStates and ConstantNodes are
* scheduled, but can safely be ignored.
*/
if (node instanceof CallTargetNode || node instanceof FrameState || node instanceof ConstantNode) {
return false;
} else if (node instanceof Invoke) {
processNodeInternal(((Invoke) node).callTarget(), state, effects, lastFixedNode);
}
return processNodeInternal(node, state, effects, lastFixedNode);
}
@Override
protected void processStateBeforeLoopOnOverflow(BlockT initialState, FixedNode materializeBefore, GraphEffectList effects) {
for (int i = 0; i < initialState.getStateCount(); i++) {
if (initialState.hasObjectState(i) && initialState.getObjectState(i).isVirtual()) {
VirtualObjectNode virtual = virtualObjects.get(i);
initialState.materializeBefore(materializeBefore, virtual, effects);
}
}
}
private boolean processNodeInternal(Node node, BlockT state, GraphEffectList effects, FixedWithNextNode lastFixedNode) {
FixedNode nextFixedNode = lastFixedNode == null ? null : lastFixedNode.next();
VirtualUtil.trace(node.getOptions(), debug, "%s", node);
if (requiresProcessing(node)) {
if (!processVirtualizable((ValueNode) node, nextFixedNode, state, effects)) {
return false;
}
if (tool.isDeleted()) {
// we only consider real allocation nodes here
if (node instanceof AbstractNewObjectNode || node instanceof CommitAllocationNode) {
effects.addAllocationDelta(1);
}
VirtualUtil.trace(node.getOptions(), debug, "deleted virtualizable allocation %s", node);
return true;
}
}
if (hasVirtualInputs.isMarked(node) && node instanceof ValueNode) {
if (node instanceof Virtualizable) {
if (!processVirtualizable((ValueNode) node, nextFixedNode, state, effects)) {
return false;
}
if (tool.isDeleted()) {
VirtualUtil.trace(node.getOptions(), debug, "deleted virtualizable node %s", node);
return true;
} else if (requiresStrictLockOrder && node instanceof MonitorEnterNode monitorEnterNode) {
materializeVirtualLocksBefore(state, monitorEnterNode, effects, COUNTER_MATERIALIZATIONS, monitorEnterNode.getMonitorId().getLockDepth());
}
}
processNodeInputs((ValueNode) node, nextFixedNode, state, effects);
}
if (hasScalarReplacedInputs(node) && node instanceof ValueNode) {
if (processNodeWithScalarReplacedInputs((ValueNode) node, nextFixedNode, state, effects)) {
return true;
}
}
return false;
}
protected boolean requiresProcessing(Node node) {
return node instanceof VirtualizableAllocation;
}
private boolean processVirtualizable(ValueNode node, FixedNode insertBefore, BlockT state, GraphEffectList effects) {
tool.reset(state, node, insertBefore, effects);
switch (currentMode) {
case REGULAR_VIRTUALIZATION:
break;
case STOP_NEW_VIRTUALIZATIONS_LOOP_NEST:
if (node instanceof VirtualizableAllocation) {
boolean mayEnsureVirtualized = false;
for (Node usage : node.usages()) {
if (usage instanceof EnsureVirtualizedNode) {
mayEnsureVirtualized = true;
break;
}
}
if (!mayEnsureVirtualized) {
/*
* Do not try to do new devirtualizations of allocations after we reached a
* certain loop nest.
*/
return false;
}
}
if (!hasVirtualInputs.isMarked(node)) {
// a virtualizable node that is no allocation, leave it as is if the inputs have
// not been virtualized yet
return false;
}
break;
case MATERIALIZE_ALL:
boolean virtualizationResult = virtualize(node, tool);
for (VirtualObjectNode virtualObject : virtualObjects) {
ValueNode alias = getAlias(virtualObject);
if (alias instanceof VirtualObjectNode) {
int id = ((VirtualObjectNode) alias).getObjectId();
if (state.hasObjectState(id)) {
FixedNode materializeBefore = insertBefore;
if (insertBefore == node && tool.isDeleted()) {
materializeBefore = ((FixedWithNextNode) insertBefore).next();
}
ensureMaterialized(state, id, materializeBefore, effects, COUNTER_MATERIALIZATIONS);
}
}
}
return virtualizationResult;
default:
throw GraalError.shouldNotReachHere("Unknown effects closure mode " + currentMode); // ExcludeFromJacocoGeneratedReport
}
return virtualize(node, tool);
}
protected boolean virtualize(ValueNode node, VirtualizerTool vt) {
((Virtualizable) node).virtualize(vt);
return true; // request further processing
}
/**
* This tries to canonicalize the node based on improved (replaced) inputs.
*/
private boolean processNodeWithScalarReplacedInputs(ValueNode node, FixedNode insertBefore, BlockT state, GraphEffectList effects) {
ValueNode canonicalizedValue = node;
if (node instanceof Canonicalizable.Unary>) {
@SuppressWarnings("unchecked")
Canonicalizable.Unary canonicalizable = (Canonicalizable.Unary) node;
ObjectState valueObj = getObjectState(state, canonicalizable.getValue());
ValueNode valueAlias = valueObj != null ? valueObj.getMaterializedValue() : getScalarAlias(canonicalizable.getValue());
if (valueAlias != canonicalizable.getValue()) {
canonicalizedValue = (ValueNode) canonicalizable.canonical(tool, valueAlias);
}
} else if (node instanceof Canonicalizable.Binary>) {
@SuppressWarnings("unchecked")
Canonicalizable.Binary canonicalizable = (Canonicalizable.Binary) node;
ObjectState xObj = getObjectState(state, canonicalizable.getX());
ValueNode xAlias = xObj != null ? xObj.getMaterializedValue() : getScalarAlias(canonicalizable.getX());
ObjectState yObj = getObjectState(state, canonicalizable.getY());
ValueNode yAlias = yObj != null ? yObj.getMaterializedValue() : getScalarAlias(canonicalizable.getY());
if (xAlias != canonicalizable.getX() || yAlias != canonicalizable.getY()) {
canonicalizedValue = (ValueNode) canonicalizable.canonical(tool, xAlias, yAlias);
}
} else {
return false;
}
if (canonicalizedValue != node && canonicalizedValue != null) {
if (canonicalizedValue.isAlive()) {
ValueNode alias = getAliasAndResolve(state, canonicalizedValue);
if (alias instanceof VirtualObjectNode) {
addVirtualAlias((VirtualObjectNode) alias, node);
effects.deleteNode(node);
} else {
effects.replaceAtUsages(node, alias, insertBefore);
addScalarAlias(node, alias);
}
} else {
if (!prepareCanonicalNode(canonicalizedValue, state, effects)) {
VirtualUtil.trace(node.getOptions(), debug, "replacement via canonicalization too complex: %s -> %s", node, canonicalizedValue);
return false;
}
if (canonicalizedValue instanceof ControlSinkNode) {
effects.replaceWithSink((FixedWithNextNode) node, (ControlSinkNode) canonicalizedValue);
state.markAsDead();
} else {
effects.replaceAtUsages(node, canonicalizedValue, insertBefore);
addScalarAlias(node, canonicalizedValue);
}
}
VirtualUtil.trace(node.getOptions(), debug, "replaced via canonicalization: %s -> %s", node, canonicalizedValue);
return true;
}
return false;
}
/**
* Nodes created during canonicalizations need to be scanned for values that were replaced.
*/
private boolean prepareCanonicalNode(ValueNode node, BlockT state, GraphEffectList effects) {
assert !node.isAlive();
for (Position pos : node.inputPositions()) {
Node input = pos.get(node);
if (input instanceof ValueNode) {
if (input.isAlive()) {
if (!(input instanceof VirtualObjectNode)) {
ObjectState obj = getObjectState(state, (ValueNode) input);
if (obj != null) {
if (obj.isVirtual()) {
return false;
} else {
pos.initialize(node, obj.getMaterializedValue());
}
} else {
pos.initialize(node, getScalarAlias((ValueNode) input));
}
}
} else {
if (!prepareCanonicalNode((ValueNode) input, state, effects)) {
return false;
}
}
}
}
return true;
}
/**
* This replaces all inputs that point to virtual or materialized values with the actual value,
* materializing if necessary. Also takes care of frame states, adding the necessary
* {@link VirtualObjectState}.
*/
protected void processNodeInputs(ValueNode node, FixedNode insertBefore, BlockT state, GraphEffectList effects) {
VirtualUtil.trace(node.getOptions(), debug, "processing nodewithstate: %s", node);
for (Node input : node.inputs()) {
if (input instanceof ValueNode) {
ValueNode alias = getAlias((ValueNode) input);
if (alias instanceof VirtualObjectNode) {
int id = ((VirtualObjectNode) alias).getObjectId();
if (shouldMaterializeNonVirtualizable(state, id, insertBefore)) {
ensureMaterialized(state, id, insertBefore, effects, COUNTER_MATERIALIZATIONS_UNHANDLED);
effects.replaceFirstInput(node, input, state.getObjectState(id).getMaterializedValue());
VirtualUtil.trace(node.getOptions(), debug, "replacing input %s at %s", input, node);
}
}
}
}
if (node instanceof NodeWithState) {
processNodeWithState((NodeWithState) node, state, effects);
}
}
@SuppressWarnings("unused")
protected boolean shouldMaterializeNonVirtualizable(BlockT state, int id, FixedNode insertBefore) {
return true;
}
protected void processNodeWithState(NodeWithState nodeWithState, BlockT state, GraphEffectList effects) {
for (FrameState fs : nodeWithState.states()) {
FrameState frameState = getUniqueFramestate(nodeWithState, fs);
EconomicSet virtual = EconomicSet.create(Equivalence.IDENTITY_WITH_SYSTEM_HASHCODE);
frameState.applyToNonVirtual(new CollectVirtualObjectsClosure2(virtual, effects, state));
collectLockedVirtualObjects(state, virtual);
collectReferencedVirtualObjects(state, virtual);
addVirtualMappings(frameState, virtual, state, effects);
}
}
private static FrameState getUniqueFramestate(NodeWithState nodeWithState, FrameState frameState) {
if (frameState.hasMoreThanOneUsage()) {
// Can happen for example from inlined snippets with multiple state split nodes.
FrameState copy = (FrameState) frameState.copyWithInputs();
nodeWithState.asNode().replaceFirstInput(frameState, copy);
return copy;
}
return frameState;
}
private void addVirtualMappings(FrameState frameState, EconomicSet virtual, BlockT state, GraphEffectList effects) {
object: for (VirtualObjectNode obj : virtual) {
/*
* Look for existing mappings: Update a virtual object mapping for the given {@link
* VirtualObjectState} with a new value. This can be necessary in iterative escape
* analysis where a previous iteration already virtualized an object. We must not update
* such mappings if no new virtualization occurred. Updating them would create invalid
* framestate - virtualization mappings of constructor written fields.
*/
for (int i = 0; i < frameState.virtualObjectMappingCount(); i++) {
EscapeObjectState mapping = frameState.virtualObjectMappingAt(i);
if (mapping.object() == obj && mapping instanceof VirtualObjectState) {
VirtualObjectState virtualState = (VirtualObjectState) mapping;
NodeInputList values = virtualState.values();
for (int v = 0; v < values.size(); v++) {
ValueNode value = values.get(v);
ValueNode alias = getAlias(value);
if (alias != value) {
effects.updateVirtualMapping(virtualState, v, alias);
}
}
continue object;
}
}
effects.addVirtualMapping(frameState, state.getObjectState(obj).createEscapeObjectState(debug, tool.getMetaAccessExtensionProvider(), obj));
}
}
private void collectReferencedVirtualObjects(BlockT state, EconomicSet virtual) {
Iterator iterator = virtual.iterator();
while (iterator.hasNext()) {
VirtualObjectNode object = iterator.next();
int id = object.getObjectId();
if (id != -1) {
ObjectState objState = state.getObjectStateOptional(id);
if (objState != null && objState.isVirtual()) {
for (ValueNode entry : objState.getEntries()) {
if (entry instanceof VirtualObjectNode) {
VirtualObjectNode entryVirtual = (VirtualObjectNode) entry;
if (!virtual.contains(entryVirtual)) {
virtual.add(entryVirtual);
}
}
}
}
}
}
}
private void collectLockedVirtualObjects(BlockT state, EconomicSet virtual) {
for (int i = 0; i < state.getStateCount(); i++) {
ObjectState objState = state.getObjectStateOptional(i);
if (objState != null && objState.isVirtual() && objState.hasLocks()) {
virtual.add(virtualObjects.get(i));
}
}
}
/**
* @return true if materialization happened, false if not.
*/
protected boolean ensureMaterialized(PartialEscapeBlockState> state, int object, FixedNode materializeBefore, GraphEffectList effects, CounterKey counter) {
return ensureMaterialized(state, object, materializeBefore, effects, counter, true);
}
private boolean ensureMaterialized(PartialEscapeBlockState> state, int object, FixedNode materializeBefore, GraphEffectList effects, CounterKey counter, boolean materializedAcquiredLocks) {
ObjectState objectState = state.getObjectState(object);
if (objectState.isVirtual()) {
if (currentMode == EffectsClosureMode.STOP_NEW_VIRTUALIZATIONS_LOOP_NEST) {
if (objectState.getEnsureVirtualized()) {
/*
* We materialize something after heaving reached the loop depth cut-off, that
* is virtualized because it has the ensure virtualized flag set.
*
* In this case the algorithm would again become exponential in runtime over the
* loop nest depth, thus we throw a non-permanent bailout excpetion.
*/
throw new RetryableBailoutException(
"Materializing an ensureVirtualized marked allocation inside a very deep loop nest, this may lead to exponential " + "runtime of the partial escape analysis.");
}
/*
* If we ever enter a state where we do not allow new virtualizations to occur, we
* can never materialize something since no new virtualizations happened in the
* first place, thus if we see a materialization after we reached the depth cut off
* it means we try to materialize an allocation from an outer loop, this causes
* multiple iterations of the PEA algorithm for iterative loop processing and the
* algorithm becomes exponential over the loop depth, thus we leave this loop and do
* not virtualize anything
*/
throw new EffectsClosure.EffecsClosureOverflowException();
}
counter.increment(debug);
VirtualObjectNode virtual = virtualObjects.get(object);
state.materializeBefore(materializeBefore, virtual, effects);
if (requiresStrictLockOrder && materializedAcquiredLocks && objectState.hasLocks()) {
materializeVirtualLocksBefore(state, materializeBefore, effects, counter, objectState.getLockDepth());
}
assert !updateStatesForMaterialized(state, virtual, state.getObjectState(object).getMaterializedValue()) : "method must already have been called before";
return true;
} else {
return false;
}
}
/**
* PEA may materialize virtualized allocation at a subsequent control flow point, and
* potentially emit unstructured locking. For instance, for the following code,
*
*
* A obj = new A();
* synchronized (obj) {
* synchronized (otherObj) {
* ...
* // obj escape here
* ...
* }
* }
*
*
* PEA may emit:
*
*
* monitorenter otherObj
* ...
* A obj = new A()
* monitorenter obj
* ...
* monitorexit otherObj
* monitorexit obj
*
*
* On HotSpot, unstructured locking is acceptable for stack locking (LM_LEGACY) and heavy
* monitor (LM_MONITOR). This is because locks under these locking modes are referenced by
* pointers stored in the object mark word, and are not necessary contiguous in memory. There is
* no way to observe a lock disorder from outside, as long as PEA guarantee that a virtual lock
* is materialized and held before it escapes or before the runtime deoptimizes and transfers to
* interpreter.
*
* Lightweight locking (LM_LIGHTWEIGHT), however, maintains locks in a thread-local lock stack.
* The more inner lock occupies the closer slot to the lock stack top. Unstructured locking code
* will disrupt the lock stack and result in an inconsistent state. For instance, the lock stack
* before monitorexits in the above code example is:
*
*
* ------------
* | obj | <-- stack top
* ------------
* | otherObj |
* ------------
*
*
* and is
*
*
* ------------
* | otherObj | <-- stack top
* ------------
*
*
* after the first {code monitorexit} instruction. At this point, the still-locked object
* {@code obj} is not maintained in the lock stack, while the lock stack top points to
* {@code otherObj}, which is with an unlocked mark word. Such inconsistent state can be
* observed from outside by scanning a thread's lock stack.
*
* To avoid such scenario, we disallow PEA to emit unstructured locking code when using
* lightweight locking. We materialize all virtual objects that potentially get materialized in
* subsequent control flow point and hold locks with lock depth smaller than {@code lockDepth}.
* For those will not get materialized (see {@link #mayBeMaterialized}), we keep them virtual
* and effectively apply lock elimination. The runtime deoptimization code will take care of
* rematerialization of these virtual locks (see JDK-8318895).
*/
private void materializeVirtualLocksBefore(PartialEscapeBlockState> state, FixedNode materializeBefore,
GraphEffectList effects, CounterKey counter, int lockDepth) {
for (VirtualObjectNode other : virtualObjects) {
int otherID = other.getObjectId();
if (state.hasObjectState(otherID)) {
ObjectState otherState = state.getObjectState(other);
if (otherState.isVirtual() && otherState.hasLocks() && otherState.getLockDepth() < lockDepth && mayBeMaterialized(other)) {
ensureMaterialized(state, other.getObjectId(), materializeBefore, effects, counter, false);
}
}
}
}
/**
* @return true if this virtual object may be materialized.
*/
private boolean mayBeMaterialized(VirtualObjectNode virtualObjectNode) {
MapCursor cursor = aliases.getEntries();
while (cursor.advance()) {
if (virtualObjectNode == cursor.getValue()) {
Node allocation = cursor.getKey();
// This is conservative. In practice, PEA may scalar-replace field accesses and
// prevent this virtual object from escaping.
return allocation.usages().filter(n -> n instanceof ValueNode && !(n instanceof AccessMonitorNode)).isNotEmpty();
}
}
return true;
}
public static boolean updateStatesForMaterialized(PartialEscapeBlockState> state, VirtualObjectNode virtual, ValueNode materializedValue) {
// update all existing states with the newly materialized object
boolean change = false;
for (int i = 0; i < state.getStateCount(); i++) {
ObjectState objState = state.getObjectStateOptional(i);
if (objState != null && objState.isVirtual()) {
ValueNode[] entries = objState.getEntries();
for (int i2 = 0; i2 < entries.length; i2++) {
if (entries[i2] == virtual) {
state.setEntry(i, i2, materializedValue);
change = true;
}
}
}
}
return change;
}
@Override
protected BlockT stripKilledLoopLocations(Loop loop, BlockT originalInitialState) {
BlockT initialState = super.stripKilledLoopLocations(loop, originalInitialState);
if (loop.getDepth() > GraalOptions.EscapeAnalysisLoopCutoff.getValue(cfg.graph.getOptions())) {
/*
* After we've reached the maximum loop nesting, we'll simply materialize everything we
* can to make sure that the loops only need to be iterated one time. Care is taken here
* to not materialize virtual objects that have the "ensureVirtualized" flag set.
*/
LoopBeginNode loopBegin = (LoopBeginNode) loop.getHeader().getBeginNode();
AbstractEndNode end = loopBegin.forwardEnd();
HIRBlock loopPredecessor = loop.getHeader().getFirstPredecessor();
assert loopPredecessor.getEndNode() == end : Assertions.errorMessageContext("loopPred", loopPredecessor, "loopPred.end", loopPredecessor.getEndNode(), "end", end);
int length = initialState.getStateCount();
boolean change;
BitSet ensureVirtualized = new BitSet(length);
for (int i = 0; i < length; i++) {
ObjectState state = initialState.getObjectStateOptional(i);
if (state != null && state.isVirtual() && state.getEnsureVirtualized()) {
ensureVirtualized.set(i);
}
}
do {
// propagate "ensureVirtualized" flag
change = false;
for (int i = 0; i < length; i++) {
if (!ensureVirtualized.get(i)) {
ObjectState state = initialState.getObjectStateOptional(i);
if (state != null && state.isVirtual()) {
for (ValueNode entry : state.getEntries()) {
if (entry instanceof VirtualObjectNode) {
if (ensureVirtualized.get(((VirtualObjectNode) entry).getObjectId())) {
change = true;
ensureVirtualized.set(i);
break;
}
}
}
}
}
}
} while (change);
if (currentMode == EffectsClosureMode.REGULAR_VIRTUALIZATION) {
currentMode = EffectsClosureMode.STOP_NEW_VIRTUALIZATIONS_LOOP_NEST;
}
}
return initialState;
}
@Override
protected void processInitialLoopState(Loop loop, BlockT initialState) {
for (PhiNode phi : ((LoopBeginNode) loop.getHeader().getBeginNode()).phis()) {
if (phi.valueAt(0) != null) {
ValueNode alias = getAliasAndResolve(initialState, phi.valueAt(0));
if (alias instanceof VirtualObjectNode) {
VirtualObjectNode virtual = (VirtualObjectNode) alias;
addVirtualAlias(virtual, phi);
} else {
aliases.set(phi, null);
}
}
}
}
@Override
protected void processLoopExit(LoopExitNode exitNode, BlockT initialState, BlockT exitState, GraphEffectList effects) {
if (exitNode.graph().isBeforeStage(StageFlag.VALUE_PROXY_REMOVAL)) {
// We cannot go below loop exits with an exception handling BCI, it would create
// allocations whose slow path has an invalid frame state.
boolean forceMaterialization = exitNode.stateAfter().isExceptionHandlingBCI();
EconomicMap proxies = EconomicMap.create(Equivalence.DEFAULT);
for (ProxyNode proxy : exitNode.proxies()) {
ValueNode alias = getAlias(proxy.value());
if (alias instanceof VirtualObjectNode) {
VirtualObjectNode virtual = (VirtualObjectNode) alias;
if (forceMaterialization) {
ensureMaterialized(exitState, virtual.getObjectId(), exitNode, effects, COUNTER_MATERIALIZATIONS_LOOP_EXIT);
}
proxies.put(virtual.getObjectId(), proxy);
}
}
for (int i = 0; i < exitState.getStateCount(); i++) {
ObjectState exitObjState = exitState.getObjectStateOptional(i);
if (exitObjState != null) {
ObjectState initialObjState = initialState.getObjectStateOptional(i);
if (exitObjState.isVirtual()) {
processVirtualAtLoopExit(exitNode, effects, i, exitObjState, initialObjState, exitState);
} else {
processMaterializedAtLoopExit(exitNode, effects, proxies, i, exitObjState, initialObjState, exitState);
}
}
}
}
}
private static void processMaterializedAtLoopExit(LoopExitNode exitNode, GraphEffectList effects, EconomicMap proxies, int object, ObjectState exitObjState,
ObjectState initialObjState, PartialEscapeBlockState> exitState) {
// Create a value proxy at the loop exit if either:
// a) the object was virtual at the loop beginning or
// b) the materialized value of the object is different at the loop exit than it was at the
// loop beginning.
if (initialObjState == null || initialObjState.isVirtual() || initialObjState.getMaterializedValue() != exitObjState.getMaterializedValue()) {
ProxyNode proxy = proxies.get(object);
if (proxy == null) {
proxy = new ValueProxyNode(exitObjState.getMaterializedValue(), exitNode);
effects.addFloatingNode(proxy, "proxy");
} else {
effects.replaceFirstInput(proxy, proxy.value(), exitObjState.getMaterializedValue());
// nothing to do - will be handled in processNode
}
exitState.updateMaterializedValue(object, proxy);
}
}
private static void processVirtualAtLoopExit(LoopExitNode exitNode, GraphEffectList effects, int object, ObjectState exitObjState, ObjectState initialObjState,
PartialEscapeBlockState> exitState) {
for (int i = 0; i < exitObjState.getEntries().length; i++) {
ValueNode value = exitState.getObjectState(object).getEntry(i);
if (!(value instanceof VirtualObjectNode || value.isConstant())) {
if (exitNode.loopBegin().isPhiAtMerge(value) || initialObjState == null || !initialObjState.isVirtual() || initialObjState.getEntry(i) != value) {
ProxyNode proxy = new ValueProxyNode(value, exitNode);
exitState.setEntry(object, i, proxy);
effects.addFloatingNode(proxy, "virtualProxy");
}
}
}
}
@Override
protected MergeProcessor createMergeProcessor(HIRBlock merge) {
return new MergeProcessor(merge);
}
protected class MergeProcessor extends EffectsClosure.MergeProcessor {
private EconomicMap
© 2015 - 2025 Weber Informatics LLC | Privacy Policy