package org.graalvm.compiler.nodes;
import static jdk.vm.ci.code.BytecodeFrame.getPlaceholderBciName;
import static jdk.vm.ci.code.BytecodeFrame.isPlaceholderBci;
import static org.graalvm.compiler.nodeinfo.InputType.Association;
import static org.graalvm.compiler.nodeinfo.InputType.State;
import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_0;
import static org.graalvm.compiler.nodeinfo.NodeCycles.CYCLES_IGNORED;
import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_1;
import static org.graalvm.compiler.nodeinfo.NodeSize.SIZE_IGNORED;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.graalvm.compiler.api.replacements.MethodSubstitution;
import org.graalvm.compiler.bytecode.Bytecode;
import org.graalvm.compiler.bytecode.Bytecodes;
import org.graalvm.compiler.core.common.type.StampFactory;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.graph.IterableNodeType;
import org.graalvm.compiler.graph.NodeClass;
import org.graalvm.compiler.graph.NodeInputList;
import org.graalvm.compiler.graph.NodeSourcePosition;
import org.graalvm.compiler.graph.iterators.NodeIterable;
import org.graalvm.compiler.nodeinfo.InputType;
import org.graalvm.compiler.nodeinfo.NodeInfo;
import org.graalvm.compiler.nodeinfo.Verbosity;
import org.graalvm.compiler.nodes.java.ExceptionObjectNode;
import org.graalvm.compiler.nodes.java.MonitorIdNode;
import org.graalvm.compiler.nodes.virtual.EscapeObjectState;
import org.graalvm.compiler.nodes.virtual.VirtualObjectNode;
import jdk.vm.ci.code.BytecodeFrame;
import jdk.vm.ci.code.CodeUtil;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.ResolvedJavaMethod;
@NodeInfo(nameTemplate = "@{p#code/s}:{p#bci}", cycles = CYCLES_0, size = SIZE_1)
public final class FrameState extends VirtualState implements IterableNodeType {
public static final NodeClass<FrameState> TYPE = NodeClass.create(FrameState.class);
public static final ValueNode TWO_SLOT_MARKER = new TwoSlotMarker();
@NodeInfo(cycles = CYCLES_IGNORED, size = SIZE_IGNORED)
private static final class TwoSlotMarker extends ValueNode {
public static final NodeClass<TwoSlotMarker> TYPE = NodeClass.create(TwoSlotMarker.class);
protected TwoSlotMarker() {
super(TYPE, StampFactory.forKind(JavaKind.Illegal));
}
}
protected final int localsSize;
protected final int stackSize;
protected final boolean rethrowException;
protected final boolean duringCall;
@OptionalInput(value = InputType.State) FrameState outerFrameState;
@OptionalInput NodeInputList<ValueNode> values;
@Input(Association) NodeInputList<MonitorIdNode> monitorIds;
@OptionalInput(State) NodeInputList<EscapeObjectState> virtualObjectMappings;
public final int bci;
protected final Bytecode code;
public FrameState(FrameState outerFrameState, Bytecode code, int bci, int localsSize, int stackSize, int lockSize, boolean rethrowException, boolean duringCall,
List<MonitorIdNode> monitorIds, List<EscapeObjectState> virtualObjectMappings) {
super(TYPE);
if (code != null) {
int codeSize = code.getCodeSize();
if (codeSize != 0 && bci >= codeSize) {
throw new GraalError("bci %d is out of range for %s %d bytes", bci, code.getMethod().format("%H.%n(%p)"), codeSize);
}
}
assert stackSize >= 0;
this.outerFrameState = outerFrameState;
assert outerFrameState == null || outerFrameState.bci >= 0;
this.code = code;
this.bci = bci;
this.localsSize = localsSize;
this.stackSize = stackSize;
this.values = new NodeInputList<>(this, localsSize + stackSize + lockSize);
if (monitorIds != null && monitorIds.size() > 0) {
this.monitorIds = new NodeInputList<>(this, monitorIds);
}
if (virtualObjectMappings != null && virtualObjectMappings.size() > 0) {
this.virtualObjectMappings = new NodeInputList<>(this, virtualObjectMappings);
}
this.rethrowException = rethrowException;
this.duringCall = duringCall;
assert !this.rethrowException || this.stackSize == 1 : "must have exception on top of the stack";
assert this.locksSize() == this.monitorIdCount();
}
public FrameState(FrameState outerFrameState, Bytecode code, int bci, List<ValueNode> values, int localsSize, int stackSize, boolean rethrowException, boolean duringCall,
List<MonitorIdNode> monitorIds, List<EscapeObjectState> virtualObjectMappings) {
this(outerFrameState, code, bci, localsSize, stackSize, values.size() - localsSize - stackSize, rethrowException, duringCall, monitorIds, virtualObjectMappings);
for (int i = 0; i < values.size(); ++i) {
this.values.initialize(i, values.get(i));
}
}
private void verifyAfterExceptionState() {
if (this.bci == BytecodeFrame.AFTER_EXCEPTION_BCI) {
assert this.outerFrameState == null;
for (int i = 0; i < this.localsSize; i++) {
assertTrue(this.values.get(i) == null, "locals should be null in AFTER_EXCEPTION_BCI state");
}
}
}
public FrameState(int bci) {
this(null, null, bci, 0, 0, 0, false, false, null, Collections.<EscapeObjectState> emptyList());
assert bci == BytecodeFrame.BEFORE_BCI || bci == BytecodeFrame.AFTER_BCI || bci == BytecodeFrame.AFTER_EXCEPTION_BCI || bci == BytecodeFrame.UNKNOWN_BCI ||
bci == BytecodeFrame.INVALID_FRAMESTATE_BCI;
}
public FrameState(int bci, ValueNode returnValueOrExceptionObject) {
this(null, null, bci, 0, returnValueOrExceptionObject.getStackKind().getSlotCount(), 0, returnValueOrExceptionObject instanceof ExceptionObjectNode, false, null,
Collections.<EscapeObjectState> emptyList());
assert (bci == BytecodeFrame.AFTER_BCI && !rethrowException()) || (bci == BytecodeFrame.AFTER_EXCEPTION_BCI && rethrowException());
this.values.initialize(0, returnValueOrExceptionObject);
}
public FrameState(FrameState outerFrameState, Bytecode code, int bci, ValueNode[] locals, ValueNode[] stack, int stackSize, ValueNode[] locks, List<MonitorIdNode> monitorIds,
boolean rethrowException, boolean duringCall) {
this(outerFrameState, code, bci, locals.length, stackSize, locks.length, rethrowException, duringCall, monitorIds, Collections.<EscapeObjectState> emptyList());
createValues(locals, stack, locks);
}
private void createValues(ValueNode[] locals, ValueNode[] stack, ValueNode[] locks) {
int index = 0;
for (int i = 0; i < locals.length; ++i) {
ValueNode value = locals[i];
if (value == TWO_SLOT_MARKER) {
value = null;
}
this.values.initialize(index++, value);
}
for (int i = 0; i < stackSize; ++i) {
ValueNode value = stack[i];
if (value == TWO_SLOT_MARKER) {
value = null;
}
this.values.initialize(index++, value);
}
for (int i = 0; i < locks.length; ++i) {
ValueNode value = locks[i];
assert value != TWO_SLOT_MARKER;
this.values.initialize(index++, value);
}
}
public NodeInputList<ValueNode> values() {
return values;
}
public NodeInputList<MonitorIdNode> monitorIds() {
return monitorIds;
}
public FrameState outerFrameState() {
return outerFrameState;
}
public void setOuterFrameState(FrameState x) {
assert x == null || (!x.isDeleted() && x.bci >= 0) : "cannot set outer frame state of:\n" + toString(this) +
"\nto:\n" + toString(x) + "\nisDeleted=" + x.isDeleted();
updateUsages(this.outerFrameState, x);
this.outerFrameState = x;
}
public static NodeSourcePosition toSourcePosition(FrameState fs) {
if (fs == null) {
return null;
}
return new NodeSourcePosition(toSourcePosition(fs.outerFrameState()), fs.code.getMethod(), fs.bci);
}
public boolean rethrowException() {
return rethrowException;
}
public boolean duringCall() {
return duringCall;
}
public Bytecode getCode() {
return code;
}
public ResolvedJavaMethod getMethod() {
return code == null ? null : code.getMethod();
}
public boolean canProduceBytecodeFrame() {
return code != null && code.getCode() == code.getMethod().getCode();
}
public void addVirtualObjectMapping(EscapeObjectState virtualObject) {
if (virtualObjectMappings == null) {
virtualObjectMappings = new NodeInputList<>(this);
}
virtualObjectMappings.add(virtualObject);
}
public int virtualObjectMappingCount() {
if (virtualObjectMappings == null) {
return 0;
}
return virtualObjectMappings.size();
}
public EscapeObjectState virtualObjectMappingAt(int i) {
return virtualObjectMappings.get(i);
}
public NodeInputList<EscapeObjectState> virtualObjectMappings() {
return virtualObjectMappings;
}
public FrameState duplicate(int newBci) {
return graph().add(new FrameState(outerFrameState(), code, newBci, values, localsSize, stackSize, rethrowException, duringCall, monitorIds, virtualObjectMappings));
}
public FrameState duplicate() {
return duplicate(bci);
}
@Override
public FrameState duplicateWithVirtualState() {
FrameState newOuterFrameState = outerFrameState();
if (newOuterFrameState != null) {
newOuterFrameState = newOuterFrameState.duplicateWithVirtualState();
}
ArrayList<EscapeObjectState> newVirtualMappings = null;
if (virtualObjectMappings != null) {
newVirtualMappings = new ArrayList<>(virtualObjectMappings.size());
for (EscapeObjectState state : virtualObjectMappings) {
newVirtualMappings.add(state.duplicateWithVirtualState());
}
}
return graph().add(new FrameState(newOuterFrameState, code, bci, values, localsSize, stackSize, rethrowException, duringCall, monitorIds, newVirtualMappings));
}
public FrameState duplicateModifiedDuringCall(int newBci, JavaKind popKind) {
return duplicateModified(graph(), newBci, rethrowException, true, popKind, null, null);
}
public FrameState duplicateModifiedBeforeCall(int newBci, JavaKind popKind, JavaKind[] pushedSlotKinds, ValueNode[] pushedValues) {
return duplicateModified(graph(), newBci, rethrowException, false, popKind, pushedSlotKinds, pushedValues);
}
public FrameState duplicateModified(int newBci, boolean newRethrowException, JavaKind popKind, JavaKind[] pushedSlotKinds, ValueNode[] pushedValues) {
return duplicateModified(graph(), newBci, newRethrowException, duringCall, popKind, pushedSlotKinds, pushedValues);
}
public FrameState duplicateModified(int newBci, boolean newRethrowException, boolean newDuringCall, JavaKind popKind, JavaKind[] pushedSlotKinds, ValueNode[] pushedValues) {
return duplicateModified(graph(), newBci, newRethrowException, newDuringCall, popKind, pushedSlotKinds, pushedValues);
}
public FrameState duplicateModified(JavaKind popKind, JavaKind pushedSlotKind, ValueNode pushedValue) {
assert pushedValue != null && pushedValue.getStackKind() == popKind;
return duplicateModified(graph(), bci, rethrowException, duringCall, popKind, new JavaKind[]{pushedSlotKind}, new ValueNode[]{pushedValue});
}
public FrameState duplicateModified(StructuredGraph graph, int newBci, boolean newRethrowException, boolean newDuringCall, JavaKind popKind, JavaKind[] pushedSlotKinds, ValueNode[] pushedValues) {
ArrayList<ValueNode> copy;
if (newRethrowException && !rethrowException && popKind == JavaKind.Void) {
assert popKind == JavaKind.Void;
copy = new ArrayList<>(values.subList(0, localsSize));
} else {
copy = new ArrayList<>(values.subList(0, localsSize + stackSize));
if (popKind != JavaKind.Void) {
if (stackAt(stackSize() - 1) == null) {
copy.remove(copy.size() - 1);
}
ValueNode lastSlot = copy.get(copy.size() - 1);
assert lastSlot.getStackKind() == popKind.getStackKind();
copy.remove(copy.size() - 1);
}
}
if (pushedValues != null) {
assert pushedSlotKinds.length == pushedValues.length;
for (int i = 0; i < pushedValues.length; i++) {
copy.add(pushedValues[i]);
if (pushedSlotKinds[i].needsTwoSlots()) {
copy.add(null);
}
}
}
int newStackSize = copy.size() - localsSize;
copy.addAll(values.subList(localsSize + stackSize, values.size()));
assert checkStackDepth(bci, stackSize, duringCall, rethrowException, newBci, newStackSize, newDuringCall, newRethrowException);
return graph.add(new FrameState(outerFrameState(), code, newBci, copy, localsSize, newStackSize, newRethrowException, newDuringCall, monitorIds, virtualObjectMappings));
}
private boolean checkStackDepth(int oldBci, int oldStackSize, boolean oldDuringCall, boolean oldRethrowException, int newBci, int newStackSize, boolean newDuringCall,
boolean newRethrowException) {
if (BytecodeFrame.isPlaceholderBci(oldBci)) {
return true;
}
byte[] codes = code.getCode();
if (codes == null) {
return true;
}
byte newCode = codes[newBci];
if (oldBci == newBci) {
assert oldStackSize == newStackSize || oldDuringCall != newDuringCall || oldRethrowException != newRethrowException : "bci is unchanged, stack depth shouldn't change";
} else {
byte oldCode = codes[oldBci];
assert Bytecodes.lengthOf(newCode) + newBci == oldBci || Bytecodes.lengthOf(oldCode) + oldBci == newBci : "expecting roll back or forward";
}
return true;
}
public int localsSize() {
return localsSize;
}
public int stackSize() {
return stackSize;
}
public int locksSize() {
return values.size() - localsSize - stackSize;
}
public int nestedLockDepth() {
int depth = locksSize();
for (FrameState outer = outerFrameState(); outer != null; outer = outer.outerFrameState()) {
depth += outer.locksSize();
}
return depth;
}
public ValueNode localAt(int i) {
assert i >= 0 && i < localsSize : "local variable index out of range: " + i;
return values.get(i);
}
public ValueNode stackAt(int i) {
assert i >= 0 && i < stackSize;
return values.get(localsSize + i);
}
public ValueNode lockAt(int i) {
assert i >= 0 && i < locksSize();
return values.get(localsSize + stackSize + i);
}
public MonitorIdNode monitorIdAt(int i) {
assert monitorIds != null && i >= 0 && i < locksSize();
return monitorIds.get(i);
}
public int monitorIdCount() {
if (monitorIds == null) {
return 0;
} else {
return monitorIds.size();
}
}
public NodeIterable<FrameState> innerFrameStates() {
return usages().filter(FrameState.class);
}
private static String toString(FrameState frameState) {
StringBuilder sb = new StringBuilder();
String nl = CodeUtil.NEW_LINE;
FrameState fs = frameState;
while (fs != null) {
Bytecode.appendLocation(sb, fs.getCode(), fs.bci);
if (BytecodeFrame.isPlaceholderBci(fs.bci)) {
sb.append("//").append(getPlaceholderBciName(fs.bci));
}
sb.append(nl);
sb.append("locals: [");
for (int i = 0; i < fs.localsSize(); i++) {
sb.append(i == 0 ? "" : ", ").append(fs.localAt(i) == null ? "_" : fs.localAt(i).toString(Verbosity.Id));
}
sb.append("]").append(nl).append("stack: [");
for (int i = 0; i < fs.stackSize(); i++) {
sb.append(i == 0 ? "" : ", ").append(fs.stackAt(i) == null ? "_" : fs.stackAt(i).toString(Verbosity.Id));
}
sb.append("]").append(nl).append("locks: [");
for (int i = 0; i < fs.locksSize(); i++) {
sb.append(i == 0 ? "" : ", ").append(fs.lockAt(i) == null ? "_" : fs.lockAt(i).toString(Verbosity.Id));
}
sb.append(']').append(nl);
fs = fs.outerFrameState();
}
return sb.toString();
}
@Override
public String toString(Verbosity verbosity) {
if (verbosity == Verbosity.Debugger) {
return toString(this);
} else if (verbosity == Verbosity.Name) {
String res = super.toString(Verbosity.Name) + "@" + bci;
if (BytecodeFrame.isPlaceholderBci(bci)) {
res += "[" + getPlaceholderBciName(bci) + "]";
}
return res;
} else {
return super.toString(verbosity);
}
}
@Override
public Map<Object, Object> getDebugProperties(Map<Object, Object> map) {
Map<Object, Object> properties = super.getDebugProperties(map);
if (code != null) {
StackTraceElement ste = code.asStackTraceElement(bci);
if (ste.getFileName() != null && ste.getLineNumber() >= 0) {
properties.put("sourceFile", ste.getFileName());
properties.put("sourceLine", ste.getLineNumber());
}
}
if (isPlaceholderBci(bci)) {
properties.put("bci", getPlaceholderBciName(bci));
}
properties.put("locksSize", values.size() - stackSize - localsSize);
return properties;
}
@Override
public boolean verify() {
if (virtualObjectMappingCount() > 0) {
for (EscapeObjectState state : virtualObjectMappings()) {
assertTrue(state != null, "must be non-null");
}
}
assertTrue(outerFrameState != null || graph() == null || graph().method() == null || code == null || Objects.equals(graph().method(), code.getMethod()) ||
graph().method().getAnnotation(MethodSubstitution.class) != null, "wrong outerFrameState %s != %s", code == null ? "null" : code.getMethod(), graph().method());
if (monitorIds() != null && monitorIds().size() > 0) {
int depth = outerLockDepth();
for (MonitorIdNode monitor : monitorIds()) {
assertTrue(monitor.getLockDepth() == depth++, "wrong depth");
}
}
assertTrue(locksSize() == monitorIdCount(), "mismatch in number of locks");
for (ValueNode value : values) {
assertTrue(value == null || !value.isDeleted(), "frame state must not contain deleted nodes: %s", value);
assertTrue(value == null || value instanceof VirtualObjectNode || (value.getStackKind() != JavaKind.Void), "unexpected value: %s", value);
}
verifyAfterExceptionState();
return super.verify();
}
private int outerLockDepth() {
int depth = 0;
FrameState outer = outerFrameState;
while (outer != null) {
depth += outer.monitorIdCount();
outer = outer.outerFrameState;
}
return depth;
}
@Override
public void applyToNonVirtual(NodeClosure<? super ValueNode> closure) {
for (ValueNode value : values) {
if (value != null) {
closure.apply(this, value);
}
}
if (monitorIds != null) {
for (MonitorIdNode monitorId : monitorIds) {
if (monitorId != null) {
closure.apply(this, monitorId);
}
}
}
if (virtualObjectMappings != null) {
for (EscapeObjectState state : virtualObjectMappings) {
state.applyToNonVirtual(closure);
}
}
if (outerFrameState() != null) {
outerFrameState().applyToNonVirtual(closure);
}
}
@Override
public void applyToVirtual(VirtualClosure closure) {
closure.apply(this);
if (virtualObjectMappings != null) {
for (EscapeObjectState state : virtualObjectMappings) {
state.applyToVirtual(closure);
}
}
if (outerFrameState() != null) {
outerFrameState().applyToVirtual(closure);
}
}
@Override
public boolean isPartOfThisState(VirtualState state) {
if (state == this) {
return true;
}
if (outerFrameState() != null && outerFrameState().isPartOfThisState(state)) {
return true;
}
if (virtualObjectMappings != null) {
for (EscapeObjectState objectState : virtualObjectMappings) {
if (objectState.isPartOfThisState(state)) {
return true;
}
}
}
return false;
}
}