package com.oracle.svm.core.deopt;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Array;
import java.nio.ByteOrder;
import java.util.ArrayList;
import org.graalvm.compiler.core.common.util.TypeConversion;
import org.graalvm.compiler.options.Option;
import org.graalvm.compiler.serviceprovider.GraalUnsafeAccess;
import org.graalvm.compiler.word.BarrieredAccess;
import org.graalvm.compiler.word.Word;
import org.graalvm.nativeimage.CurrentIsolate;
import org.graalvm.nativeimage.IsolateThread;
import org.graalvm.nativeimage.c.function.CodePointer;
import org.graalvm.word.Pointer;
import org.graalvm.word.PointerBase;
import org.graalvm.word.SignedWord;
import org.graalvm.word.UnsignedWord;
import org.graalvm.word.WordBase;
import org.graalvm.word.WordFactory;
import com.oracle.svm.core.FrameAccess;
import com.oracle.svm.core.ReservedRegisters;
import com.oracle.svm.core.SubstrateOptions;
import com.oracle.svm.core.annotate.NeverInline;
import com.oracle.svm.core.annotate.Specialize;
import com.oracle.svm.core.annotate.Uninterruptible;
import com.oracle.svm.core.code.CodeInfo;
import com.oracle.svm.core.code.CodeInfoAccess;
import com.oracle.svm.core.code.CodeInfoQueryResult;
import com.oracle.svm.core.code.CodeInfoTable;
import com.oracle.svm.core.code.FrameInfoDecoder;
import com.oracle.svm.core.code.FrameInfoQueryResult;
import com.oracle.svm.core.code.FrameInfoQueryResult.ValueInfo;
import com.oracle.svm.core.code.UntetheredCodeInfo;
import com.oracle.svm.core.config.ConfigurationValues;
import com.oracle.svm.core.config.ObjectLayout;
import com.oracle.svm.core.deopt.DeoptimizedFrame.RelockObjectData;
import com.oracle.svm.core.deopt.DeoptimizedFrame.VirtualFrame;
import com.oracle.svm.core.heap.GCCause;
import com.oracle.svm.core.heap.Heap;
import com.oracle.svm.core.heap.ReferenceAccess;
import com.oracle.svm.core.hub.DynamicHub;
import com.oracle.svm.core.hub.LayoutEncoding;
import com.oracle.svm.core.log.Log;
import com.oracle.svm.core.log.StringBuilderLog;
import com.oracle.svm.core.meta.SharedMethod;
import com.oracle.svm.core.meta.SubstrateObjectConstant;
import com.oracle.svm.core.monitor.MonitorSupport;
import com.oracle.svm.core.option.RuntimeOptionKey;
import com.oracle.svm.core.snippets.KnownIntrinsics;
import com.oracle.svm.core.stack.JavaStackWalker;
import com.oracle.svm.core.stack.StackFrameVisitor;
import com.oracle.svm.core.thread.JavaVMOperation;
import com.oracle.svm.core.thread.VMOperation;
import com.oracle.svm.core.thread.VMThreads;
import com.oracle.svm.core.util.RingBuffer;
import com.oracle.svm.core.util.VMError;
import jdk.vm.ci.code.InstalledCode;
import jdk.vm.ci.meta.DeoptimizationAction;
import jdk.vm.ci.meta.DeoptimizationReason;
import jdk.vm.ci.meta.JavaConstant;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.SpeculationLog.SpeculationReason;
import sun.misc.Unsafe;
public final class Deoptimizer {
private static final Unsafe UNSAFE = GraalUnsafeAccess.getUnsafe();
private static final RingBuffer<char[]> recentDeoptimizationEvents = new RingBuffer<>();
private static final int actionShift = 0;
private static final int actionBits = Integer.SIZE - Integer.numberOfLeadingZeros(DeoptimizationAction.values().length);
private static final int reasonShift = actionShift + actionBits;
private static final int reasonBits = Integer.SIZE - Integer.numberOfLeadingZeros(DeoptimizationReason.values().length);
private static final int idShift = reasonShift + reasonBits;
private static final int idBits = Integer.SIZE;
public static long encodeDeoptActionAndReasonToLong(DeoptimizationAction action, DeoptimizationReason reason, int speculationId) {
return ((long) action.ordinal() << actionShift) | ((long) reason.ordinal() << reasonShift) | ((long) speculationId << idShift);
}
public static JavaConstant encodeDeoptActionAndReason(DeoptimizationAction action, DeoptimizationReason reason, int speculationId) {
JavaConstant result = JavaConstant.forLong(encodeDeoptActionAndReasonToLong(action, reason, speculationId));
assert decodeDeoptAction(result) == action;
assert decodeDeoptReason(result) == reason;
assert decodeDebugId(result) == speculationId;
return result;
}
public static DeoptimizationAction decodeDeoptAction(long actionAndReason) {
return DeoptimizationAction.values()[(int) ((actionAndReason >> actionShift) & ((1L << actionBits) - 1))];
}
public static DeoptimizationReason decodeDeoptReason(long actionAndReason) {
return DeoptimizationReason.values()[(int) ((actionAndReason >> reasonShift) & ((1L << reasonBits) - 1))];
}
public static int decodeDebugId(long actionAndReason) {
return (int) ((actionAndReason >> idShift) & ((1L << idBits) - 1));
}
public static DeoptimizationAction decodeDeoptAction(JavaConstant actionAndReason) {
return decodeDeoptAction(actionAndReason.asLong());
}
public static DeoptimizationReason decodeDeoptReason(JavaConstant actionAndReason) {
return decodeDeoptReason(actionAndReason.asLong());
}
public static int decodeDebugId(JavaConstant actionAndReason) {
return decodeDebugId(actionAndReason.asLong());
}
private static boolean checkEncoding() {
for (DeoptimizationAction action : DeoptimizationAction.values()) {
for (DeoptimizationReason reason : DeoptimizationReason.values()) {
for (int speculationId : new int[]{0, 1, -1, Integer.MIN_VALUE, Integer.MAX_VALUE}) {
encodeDeoptActionAndReason(action, reason, speculationId);
}
}
}
return true;
}
static {
assert checkEncoding();
}
public static class Options {
@Option(help = "Print logging information for every deoptimization")
public static final RuntimeOptionKey<Boolean> TraceDeoptimization = new RuntimeOptionKey<>(false);
@Option(help = "Print verbose logging information for every deoptimization")
public static final RuntimeOptionKey<Boolean> TraceDeoptimizationDetails = new RuntimeOptionKey<>(false);
}
public static boolean testGCinDeoptimizer = false;
@Uninterruptible(reason = "Called from uninterruptible code.", mayBeInlined = true)
public static DeoptimizedFrame checkDeoptimized(Pointer sourceSp) {
if (DeoptimizationSupport.enabled()) {
CodePointer returnAddress = FrameAccess.singleton().readReturnAddress(sourceSp);
if (returnAddress.equal(DeoptimizationSupport.getDeoptStubPointer())) {
DeoptimizedFrame result = KnownIntrinsics.convertUnknownValue(sourceSp.readObject(0), DeoptimizedFrame.class);
assert result != null;
return result;
}
}
return null;
}
private static void installDeoptimizedFrame(Pointer sourceSp, DeoptimizedFrame deoptimizedFrame) {
FrameAccess.singleton().writeReturnAddress(sourceSp, DeoptimizationSupport.getDeoptStubPointer());
sourceSp.writeWord(0, deoptimizedFrame.getPin().addressOfObject());
}
@NeverInline("deoptimize must have a separate stack frame")
public static void deoptimizeAll() {
JavaVMOperation.enqueueBlockingSafepoint("Deoptimizer.deoptimizeInRange", () -> {
deoptimizeInRange((CodePointer) WordFactory.zero(), (CodePointer) WordFactory.zero(), true);
});
}
@NeverInline("deoptimize must have a separate stack frame")
public static void deoptimizeInRange(CodePointer fromIp, CodePointer toIp, boolean deoptAll) {
VMOperation.guaranteeInProgressAtSafepoint("Deoptimization requires a safepoint.");
deoptimizeInRangeOperation(fromIp, toIp, deoptAll);
}
@NeverInline("Starting a stack walk in the caller frame. " +
"Note that we could start the stack frame also further down the stack, because VM operation frames never need deoptimization. " +
"But we don't store stack frame information for the first frame we would need to process.")
private static void deoptimizeInRangeOperation(CodePointer fromIp, CodePointer toIp, boolean deoptAll) {
VMOperation.guaranteeInProgress("Deoptimizer.deoptimizeInRangeOperation, but not in VMOperation.");
Pointer sp = KnownIntrinsics.readCallerStackPointer();
StackFrameVisitor currentThreadDeoptVisitor = getStackFrameVisitor((Pointer) fromIp, (Pointer) toIp, deoptAll, CurrentIsolate.getCurrentThread());
JavaStackWalker.walkCurrentThread(sp, currentThreadDeoptVisitor);
if (SubstrateOptions.MultiThreaded.getValue()) {
for (IsolateThread vmThread = VMThreads.firstThread(); vmThread.isNonNull(); vmThread = VMThreads.nextThread(vmThread)) {
if (vmThread == CurrentIsolate.getCurrentThread()) {
continue;
}
StackFrameVisitor deoptVisitor = getStackFrameVisitor((Pointer) fromIp, (Pointer) toIp, deoptAll, vmThread);
JavaStackWalker.walkThread(vmThread, deoptVisitor);
}
}
if (testGCinDeoptimizer) {
Heap.getHeap().getGC().collect(GCCause.TestGCInDeoptimizer);
}
}
private static StackFrameVisitor getStackFrameVisitor(Pointer fromIp, Pointer toIp, boolean deoptAll, IsolateThread targetThread) {
return new StackFrameVisitor() {
@Override
public boolean visitFrame(Pointer frameSp, CodePointer frameIp, CodeInfo codeInfo, DeoptimizedFrame deoptFrame) {
Pointer ip = (Pointer) frameIp;
if (deoptFrame == null && ((ip.aboveOrEqual(fromIp) && ip.belowThan(toIp)) || deoptAll)) {
CodeInfoQueryResult queryResult = CodeInfoTable.lookupCodeInfoQueryResult(codeInfo, frameIp);
Deoptimizer deoptimizer = new Deoptimizer(frameSp, queryResult, targetThread);
deoptimizer.deoptSourceFrame(frameIp, deoptAll);
}
return true;
}
};
}
@NeverInline("Inlining of this method would require that we have deopt targets for callees of this method (SVM internals).")
public static void deoptimizeFrame(Pointer sourceSp, boolean ignoreNonDeoptimizable, SpeculationReason speculation) {
DeoptimizedFrame deoptFrame = Deoptimizer.checkDeoptimized(sourceSp);
if (deoptFrame != null) {
registerSpeculationFailure(deoptFrame.getSourceInstalledCode(), speculation);
return;
}
IsolateThread targetThread = CurrentIsolate.getCurrentThread();
JavaVMOperation.enqueueBlockingSafepoint("DeoptimizeFrame", () -> Deoptimizer.deoptimizeFrameOperation(sourceSp, ignoreNonDeoptimizable, speculation, targetThread));
}
private static void deoptimizeFrameOperation(Pointer sourceSp, boolean ignoreNonDeoptimizable, SpeculationReason speculation, IsolateThread targetThread) {
VMOperation.guaranteeInProgress("doDeoptimizeFrame");
CodePointer returnAddress = FrameAccess.singleton().readReturnAddress(sourceSp);
deoptimizeFrame(sourceSp, ignoreNonDeoptimizable, speculation, returnAddress, targetThread);
}
@Uninterruptible(reason = "Prevent the GC from freeing the CodeInfo object.")
private static void deoptimizeFrame(Pointer sourceSp, boolean ignoreNonDeoptimizable, SpeculationReason speculation, CodePointer returnAddress, IsolateThread targetThread) {
UntetheredCodeInfo untetheredInfo = CodeInfoTable.lookupCodeInfo(returnAddress);
Object tether = CodeInfoAccess.acquireTether(untetheredInfo);
try {
CodeInfo info = CodeInfoAccess.convert(untetheredInfo, tether);
deoptimize(sourceSp, ignoreNonDeoptimizable, speculation, returnAddress, info, targetThread);
} finally {
CodeInfoAccess.releaseTether(untetheredInfo, tether);
}
}
@Uninterruptible(reason = "Pass the now protected CodeInfo object to interruptible code.", calleeMustBe = false)
private static void deoptimize(Pointer sourceSp, boolean ignoreNonDeoptimizable, SpeculationReason speculation, CodePointer returnAddress, CodeInfo info, IsolateThread targetThread) {
deoptimize0(sourceSp, ignoreNonDeoptimizable, speculation, returnAddress, info, targetThread);
}
private static void deoptimize0(Pointer sourceSp, boolean ignoreNonDeoptimizable, SpeculationReason speculation, CodePointer returnAddress, CodeInfo info, IsolateThread targetThread) {
CodeInfoQueryResult queryResult = CodeInfoTable.lookupCodeInfoQueryResult(info, returnAddress);
Deoptimizer deoptimizer = new Deoptimizer(sourceSp, queryResult, targetThread);
DeoptimizedFrame sourceFrame = deoptimizer.deoptSourceFrame(returnAddress, ignoreNonDeoptimizable);
if (sourceFrame != null) {
registerSpeculationFailure(sourceFrame.getSourceInstalledCode(), speculation);
}
}
public static void invalidateMethodOfFrame(Pointer sourceSp, SpeculationReason speculation) {
CodePointer returnAddress = FrameAccess.singleton().readReturnAddress(sourceSp);
SubstrateInstalledCode installedCode = CodeInfoTable.lookupInstalledCode(returnAddress);
DeoptimizedFrame deoptimizedFrame = checkDeoptimized(sourceSp);
if (deoptimizedFrame != null) {
installedCode = deoptimizedFrame.getSourceInstalledCode();
if (installedCode == null) {
return;
}
} else {
if (installedCode == null) {
CodeInfoTable.getRuntimeCodeCache().logTable();
throw VMError.shouldNotReachHere(
"Only runtime compiled methods can be invalidated. sp = " + Long.toHexString(sourceSp.rawValue()) + ", returnAddress = " + Long.toHexString(returnAddress.rawValue()));
}
}
registerSpeculationFailure(installedCode, speculation);
VMOperation.guaranteeNotInProgress("invalidateMethodOfFrame: running user code that can block");
installedCode.invalidate();
}
private static void registerSpeculationFailure(SubstrateInstalledCode installedCode, SpeculationReason speculation) {
if (installedCode != null && speculation != null) {
SubstrateSpeculationLog speculationLog = installedCode.getSpeculationLog();
if (speculationLog != null) {
speculationLog.addFailedSpeculation(speculation);
}
}
}
int endOfParams;
private final CodeInfoQueryResult sourceChunk;
private final Pointer sourceSp;
private Object[] materializedObjects;
private ArrayList<RelockObjectData> relockedObjects;
protected int targetContentSize;
protected static long deoptStubFrameSize = 0L;
private final IsolateThread targetThread;
public Deoptimizer(Pointer sourceSp, CodeInfoQueryResult sourceChunk, IsolateThread targetThread) {
VMError.guarantee(sourceChunk != null, "Must not be null.");
this.sourceSp = sourceSp;
this.sourceChunk = sourceChunk;
this.targetThread = targetThread;
if (deoptStubFrameSize == 0L) {
CodeInfo info = CodeInfoTable.getImageCodeInfo();
deoptStubFrameSize = CodeInfoAccess.lookupTotalFrameSize(info, CodeInfoAccess.relativeIP(info, DeoptimizationSupport.getDeoptStubPointer()));
}
}
public enum StubType {
NoDeoptStub,
EntryStub,
ExitStub
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface DeoptStub {
StubType stubType();
}
@DeoptStub(stubType = StubType.EntryStub)
@Uninterruptible(reason = "Frame holds Objects in unmanaged storage.")
public static void deoptStub(DeoptimizedFrame frame) {
DeoptimizationCounters.counters().deoptCount.inc();
if (DeoptimizationCounters.Options.ProfileDeoptimization.getValue()) {
DeoptimizationCounters.startTime.set(System.nanoTime());
}
final Pointer newSp = KnownIntrinsics.readStackPointer()
.add(WordFactory.unsigned(deoptStubFrameSize))
.subtract(FrameAccess.singleton().stackPointerAdjustmentOnCall())
.add(WordFactory.unsigned(frame.getSourceTotalFrameSize()))
.subtract(frame.getTargetContent().getSize());
frame.buildContent(newSp);
frame.getPin().close();
recentDeoptimizationEvents.append(frame.getCompletedMessage());
rewriteStackStub(newSp, frame);
}
@DeoptStub(stubType = StubType.ExitStub)
@NeverInline("Custom prologue modifies stack pointer register")
@Uninterruptible(reason = "Frame holds Objects in unmanaged storage.")
private static DeoptimizedFrame rewriteStackStub(Pointer newSp, DeoptimizedFrame frame) {
Pointer bottomSp = newSp.subtract(FrameAccess.returnAddressSize() + FrameAccess.singleton().savedBasePointerSize());
frame.getTargetContent().copyToPointer(bottomSp);
if (DeoptimizationCounters.Options.ProfileDeoptimization.getValue()) {
DeoptimizationCounters.counters().timeSpentInDeopt.add(System.nanoTime() - DeoptimizationCounters.startTime.get());
}
return frame;
}
public JavaConstant readLocalVariable(int idx, FrameInfoQueryResult sourceFrame) {
assert idx >= 0 && idx < sourceFrame.getNumLocals();
if (idx < sourceFrame.getValueInfos().length) {
return readValue(sourceFrame.getValueInfos()[idx], sourceFrame);
} else {
return JavaConstant.forIllegal();
}
}
public DeoptimizedFrame deoptSourceFrame(CodePointer pc, boolean ignoreNonDeoptimizable) {
final DeoptSourceFrameOperation operation = new DeoptSourceFrameOperation(this, pc, ignoreNonDeoptimizable);
operation.enqueue();
return operation.getResult();
}
private static final class DeoptSourceFrameOperation extends JavaVMOperation {
private final Deoptimizer receiver;
private final CodePointer pc;
private final boolean ignoreNonDeoptimizable;
private DeoptimizedFrame result;
DeoptSourceFrameOperation(Deoptimizer receiver, CodePointer pc, boolean ignoreNonDeoptimizable) {
super("DeoptSourceFrameOperation", SystemEffect.SAFEPOINT);
this.receiver = receiver;
this.pc = pc;
this.ignoreNonDeoptimizable = ignoreNonDeoptimizable;
this.result = null;
}
@Override
public void operate() {
result = receiver.deoptSourceFrameOperation(pc, ignoreNonDeoptimizable);
}
public DeoptimizedFrame getResult() {
return result;
}
}
private DeoptimizedFrame deoptSourceFrameOperation(CodePointer pc, boolean ignoreNonDeoptimizable) {
VMOperation.guaranteeInProgress("deoptSourceFrame");
assert DeoptimizationSupport.getDeoptStubPointer().rawValue() != 0;
DeoptimizedFrame existing = checkDeoptimized(sourceSp);
if (existing != null) {
return existing;
}
FrameInfoQueryResult frameInfo = sourceChunk.getFrameInfo();
if (frameInfo == null) {
if (ignoreNonDeoptimizable) {
return null;
} else {
throw VMError.shouldNotReachHere("Deoptimization: cannot deoptimize a method that was not marked as deoptimizable from address " + Long.toHexString(pc.rawValue()));
}
}
assert endOfParams == 0;
FrameInfoQueryResult deoptInfo = frameInfo;
VirtualFrame previousVirtualFrame = null;
VirtualFrame topFrame = null;
while (deoptInfo != null) {
DeoptimizationCounters.counters().virtualFrameCount.inc();
if (deoptInfo.getDeoptMethodOffset() == 0) {
if (ignoreNonDeoptimizable) {
return null;
} else {
throw VMError.shouldNotReachHere("Deoptimization: cannot deoptimize a method that has no deoptimization entry point: " + deoptInfo.getSourceReference());
}
}
CodeInfoQueryResult targetInfo = CodeInfoTable.lookupDeoptimizationEntrypoint(deoptInfo.getDeoptMethodOffset(), deoptInfo.getEncodedBci());
if (targetInfo == null || targetInfo.getFrameInfo() == null) {
throw VMError.shouldNotReachHere("Deoptimization: no matching target bytecode frame found for bci " +
FrameInfoDecoder.readableBci(deoptInfo.getEncodedBci()) + " in method at address " +
Long.toHexString(deoptInfo.getDeoptMethodAddress().rawValue()));
} else if (!targetInfo.getFrameInfo().isDeoptEntry()) {
throw VMError.shouldNotReachHere("Deoptimization: target frame information not marked as deoptimization entry point for bci " +
FrameInfoDecoder.readableBci(deoptInfo.getEncodedBci()) + " in method at address" +
Long.toHexString(deoptInfo.getDeoptMethodAddress().rawValue()));
} else if (targetInfo.getFrameInfo().getDeoptMethod() != null && targetInfo.getFrameInfo().getDeoptMethod().hasCalleeSavedRegisters()) {
throw VMError.shouldNotReachHere("Deoptimization: target method has callee saved registers, which are not properly restored by the deoptimization runtime: " +
targetInfo.getFrameInfo().getDeoptMethod().format("%H.%n(%r)"));
}
VirtualFrame virtualFrame = constructTargetFrame(targetInfo, deoptInfo);
if (previousVirtualFrame != null) {
previousVirtualFrame.caller = virtualFrame;
} else {
topFrame = virtualFrame;
}
previousVirtualFrame = virtualFrame;
deoptInfo = deoptInfo.getCaller();
}
VMError.guarantee(sourceChunk.getTotalFrameSize() >= FrameAccess.wordSize(), "Insufficient space in frame for pointer to DeoptimizedFrame");
RelockObjectData[] relockObjectData = relockedObjects == null ? null : relockedObjects.toArray(new RelockObjectData[relockedObjects.size()]);
DeoptimizedFrame deoptimizedFrame = DeoptimizedFrame.factory(targetContentSize, sourceChunk.getEncodedFrameSize(), CodeInfoTable.lookupInstalledCode(pc), topFrame, relockObjectData, pc);
installDeoptimizedFrame(sourceSp, deoptimizedFrame);
if (Options.TraceDeoptimization.getValue()) {
printDeoptimizedFrame(Log.log(), sourceSp, deoptimizedFrame, frameInfo, false);
}
logDeoptSourceFrameOperation(sourceSp, deoptimizedFrame, frameInfo);
return deoptimizedFrame;
}
private static void logDeoptSourceFrameOperation(Pointer sp, DeoptimizedFrame deoptimizedFrame, FrameInfoQueryResult frameInfo) {
StringBuilderLog log = new StringBuilderLog();
PointerBase deoptimizedFrameAddress = deoptimizedFrame.getPin().addressOfObject();
log.string("deoptSourceFrameOperation: DeoptimizedFrame at ").hex(deoptimizedFrameAddress).string(": ");
printDeoptimizedFrame(log, sp, deoptimizedFrame, frameInfo, true);
recentDeoptimizationEvents.append(log.getResult().toCharArray());
}
private static final RingBuffer.Consumer<char[]> deoptEventsConsumer = (context, entry) -> {
Log l = (Log) context;
for (char c : entry) {
if (c == '\n') {
l.newline();
} else {
l.character(c);
}
}
};
public static void logRecentDeoptimizationEvents(Log log) {
log.string("== [Recent Deoptimizer Events: ").newline();
recentDeoptimizationEvents.foreach(log, deoptEventsConsumer);
log.string("]").newline();
}
private VirtualFrame constructTargetFrame(CodeInfoQueryResult targetInfo, FrameInfoQueryResult sourceFrame) {
FrameInfoQueryResult targetFrame = targetInfo.getFrameInfo();
int savedBasePointerSize = FrameAccess.singleton().savedBasePointerSize();
long targetFrameSize = targetInfo.getTotalFrameSize() - FrameAccess.returnAddressSize() - savedBasePointerSize;
VirtualFrame result = new VirtualFrame(targetFrame);
if (savedBasePointerSize != 0) {
result.savedBasePointer = new DeoptimizedFrame.SavedBasePointer(targetContentSize, targetContentSize + targetFrameSize);
targetContentSize += savedBasePointerSize;
}
result.returnAddress = new DeoptimizedFrame.ReturnAddress(targetContentSize, targetInfo.getIP().rawValue());
targetContentSize += FrameAccess.returnAddressSize();
assert sourceFrame.getNumLocals() == targetFrame.getNumLocals();
assert sourceFrame.getNumStack() == targetFrame.getNumStack();
assert sourceFrame.getNumLocks() == targetFrame.getNumLocks();
assert targetFrame.getVirtualObjects().length == 0;
assert sourceFrame.getValueInfos().length >= targetFrame.getValueInfos().length;
int numValues = targetFrame.getValueInfos().length;
int newEndOfParams = endOfParams;
for (int idx = 0; idx < numValues; idx++) {
ValueInfo targetValue = targetFrame.getValueInfos()[idx];
if (targetValue.getKind() == JavaKind.Illegal) {
} else {
ValueInfo sourceValue = sourceFrame.getValueInfos()[idx];
JavaConstant con = readValue(sourceValue, sourceFrame);
assert con.getJavaKind() != JavaKind.Illegal;
if (con.getJavaKind().isObject() && SubstrateObjectConstant.isCompressed(con) != targetValue.isCompressedReference()) {
Object obj = SubstrateObjectConstant.asObject(con);
con = SubstrateObjectConstant.forObject(obj, targetValue.isCompressedReference());
}
if (sourceValue.isEliminatedMonitor()) {
relockObject(con);
}
switch (targetValue.getType()) {
case StackSlot:
DeoptimizationCounters.counters().stackValueCount.inc();
int targetOffset = TypeConversion.asS4(targetValue.getData());
assert targetOffset != targetFrameSize : "stack slot would overwrite return address";
int totalOffset = targetContentSize + targetOffset;
assert totalOffset >= endOfParams : "stack location overwrites param area";
if (targetOffset < targetFrameSize) {
assert totalOffset >= targetContentSize;
result.values[idx] = DeoptimizedFrame.ConstantEntry.factory(totalOffset, con);
} else if (sourceFrame.getCaller() != null) {
assert totalOffset >= targetContentSize;
result.values[idx] = DeoptimizedFrame.ConstantEntry.factory(totalOffset, con);
int size;
if (targetValue.getKind().isObject() && !targetValue.isCompressedReference()) {
size = FrameAccess.uncompressedReferenceSize();
} else {
size = ConfigurationValues.getObjectLayout().sizeInBytes(con.getJavaKind());
}
int endOffset = totalOffset + size;
if (endOffset > newEndOfParams) {
newEndOfParams = endOffset;
}
}
break;
case DefaultConstant:
case Constant:
assert verifyConstant(targetFrame, targetValue, con);
DeoptimizationCounters.counters().constantValueCount.inc();
break;
case ReservedRegister:
break;
default:
throw VMError.shouldNotReachHere("unknown deopt target value " + targetValue);
}
}
}
targetContentSize += targetFrameSize;
endOfParams = newEndOfParams;
return result;
}
private void relockObject(JavaConstant valueConstant) {
Object lockedObject = KnownIntrinsics.convertUnknownValue(SubstrateObjectConstant.asObject(valueConstant), Object.class);
Object lockData = MonitorSupport.singleton().prepareRelockObject(lockedObject);
if (relockedObjects == null) {
relockedObjects = new ArrayList<>();
}
relockedObjects.add(new RelockObjectData(lockedObject, lockData));
}
private boolean verifyConstant(FrameInfoQueryResult targetFrame, ValueInfo targetValue, JavaConstant source) {
boolean equal;
JavaConstant target = readValue(targetValue, targetFrame);
if (source.getJavaKind() == JavaKind.Object && target.getJavaKind() == JavaKind.Object) {
equal = (SubstrateObjectConstant.asObject(target) == SubstrateObjectConstant.asObject(source));
} else {
equal = source.equals(target);
}
if (!equal) {
Log.log().string("source: ").string(source.toString()).string(" target: ").string(target.toString()).newline();
}
return equal;
}
private JavaConstant readValue(ValueInfo valueInfo, FrameInfoQueryResult sourceFrame) {
switch (valueInfo.getType()) {
case Constant:
case DefaultConstant:
return valueInfo.getValue();
case StackSlot:
case Register:
return readConstant(sourceSp, WordFactory.signed(valueInfo.getData()), valueInfo.getKind(), valueInfo.isCompressedReference());
case ReservedRegister:
if (ReservedRegisters.singleton().getThreadRegister() != null && ReservedRegisters.singleton().getThreadRegister().number == valueInfo.getData()) {
return JavaConstant.forIntegerKind(FrameAccess.getWordKind(), targetThread.rawValue());
} else if (ReservedRegisters.singleton().getHeapBaseRegister() != null && ReservedRegisters.singleton().getHeapBaseRegister().number == valueInfo.getData()) {
return JavaConstant.forIntegerKind(FrameAccess.getWordKind(), CurrentIsolate.getIsolate().rawValue());
} else {
throw VMError.shouldNotReachHere("Unexpected reserved register: " + valueInfo.getData());
}
case VirtualObject:
Object obj = materializeObject(TypeConversion.asS4(valueInfo.getData()), sourceFrame);
return SubstrateObjectConstant.forObject(obj, valueInfo.isCompressedReference());
case Illegal:
return JavaConstant.forIllegal();
default:
throw VMError.shouldNotReachHere();
}
}
private Object materializeObject(int virtualObjectId, FrameInfoQueryResult sourceFrame) {
if (materializedObjects == null) {
materializedObjects = new Object[sourceFrame.getVirtualObjects().length];
}
assert materializedObjects.length == sourceFrame.getVirtualObjects().length;
Object obj = materializedObjects[virtualObjectId];
if (obj != null) {
return obj;
}
DeoptimizationCounters.counters().virtualObjectsCount.inc();
ValueInfo[] encodings = sourceFrame.getVirtualObjects()[virtualObjectId];
DynamicHub hub = KnownIntrinsics.convertUnknownValue(SubstrateObjectConstant.asObject(readValue(encodings[0], sourceFrame)), DynamicHub.class);
ObjectLayout objectLayout = ConfigurationValues.getObjectLayout();
int curIdx;
UnsignedWord curOffset;
if (LayoutEncoding.isArray(hub.getLayoutEncoding())) {
int length = readValue(encodings[1], sourceFrame).asInt();
obj = Array.newInstance(DynamicHub.toClass(hub.getComponentHub()), length);
curOffset = LayoutEncoding.getArrayBaseOffset(hub.getLayoutEncoding());
curIdx = 2;
} else {
try {
obj = UNSAFE.allocateInstance(DynamicHub.toClass(hub));
} catch (InstantiationException ex) {
throw VMError.shouldNotReachHere(ex);
}
curOffset = WordFactory.unsigned(objectLayout.getFirstFieldOffset());
curIdx = 1;
}
materializedObjects[virtualObjectId] = obj;
if (testGCinDeoptimizer) {
Heap.getHeap().getGC().collect(GCCause.TestGCInDeoptimizer);
}
while (curIdx < encodings.length) {
ValueInfo value = encodings[curIdx];
JavaKind kind = value.getKind();
JavaConstant con = readValue(value, sourceFrame);
writeValueInMaterializedObj(obj, curOffset, con);
curOffset = curOffset.add(objectLayout.sizeInBytes(kind));
curIdx++;
}
return obj;
}
private static void writeValueInMaterializedObj(Object materializedObj, UnsignedWord offsetInObj, JavaConstant constant) {
assert offsetInObj.notEqual(0) : "materialized value would overwrite hub";
switch (constant.getJavaKind()) {
case Boolean:
BarrieredAccess.writeByte(materializedObj, offsetInObj, constant.asBoolean() ? (byte) 1 : (byte) 0);
break;
case Byte:
BarrieredAccess.writeByte(materializedObj, offsetInObj, (byte) constant.asInt());
break;
case Char:
BarrieredAccess.writeChar(materializedObj, offsetInObj, (char) constant.asInt());
break;
case Short:
BarrieredAccess.writeShort(materializedObj, offsetInObj, (short) constant.asInt());
break;
case Int:
BarrieredAccess.writeInt(materializedObj, offsetInObj, constant.asInt());
break;
case Long:
BarrieredAccess.writeLong(materializedObj, offsetInObj, constant.asLong());
break;
case Float:
BarrieredAccess.writeFloat(materializedObj, offsetInObj, constant.asFloat());
break;
case Double:
BarrieredAccess.writeDouble(materializedObj, offsetInObj, constant.asDouble());
break;
case Object:
BarrieredAccess.writeObject(materializedObj, offsetInObj, SubstrateObjectConstant.asObject(constant));
break;
default:
throw VMError.shouldNotReachHere();
}
}
private static JavaConstant readConstant(Pointer addr, SignedWord offset, JavaKind kind, boolean compressed) {
switch (kind) {
case Boolean:
return JavaConstant.forBoolean(addr.readByte(offset) != 0);
case Byte:
return JavaConstant.forByte(addr.readByte(offset));
case Char:
return JavaConstant.forChar(addr.readChar(offset));
case Short:
return JavaConstant.forShort(addr.readShort(offset));
case Int:
return JavaConstant.forInt(addr.readInt(offset));
case Long:
return JavaConstant.forLong(addr.readLong(offset));
case Float:
return JavaConstant.forFloat(addr.readFloat(offset));
case Double:
return JavaConstant.forDouble(addr.readDouble(offset));
case Object:
Word p = ((Word) addr).add(offset);
Object obj = ReferenceAccess.singleton().readObjectAt(p, compressed);
return SubstrateObjectConstant.forObject(obj, compressed);
default:
throw VMError.shouldNotReachHere();
}
}
private static void printDeoptimizedFrame(Log log, Pointer sp, DeoptimizedFrame deoptimizedFrame, FrameInfoQueryResult sourceFrameInfo, boolean printOnlyTopFrames) {
log.string("[Deoptimization of frame").newline();
SubstrateInstalledCode installedCode = deoptimizedFrame.getSourceInstalledCode();
if (installedCode != null) {
log.string(" name: ").string(installedCode.getName()).newline();
}
log.string(" sp: ").hex(sp).string(" ip: ").hex(deoptimizedFrame.getSourcePC()).newline();
if (sourceFrameInfo != null) {
log.string(" stack trace where execution continues:").newline();
FrameInfoQueryResult sourceFrame = sourceFrameInfo;
VirtualFrame targetFrame = deoptimizedFrame.getTopFrame();
int count = 0;
while (sourceFrame != null) {
SharedMethod deoptMethod = sourceFrame.getDeoptMethod();
log.string(" at ");
if (deoptMethod != null) {
StackTraceElement element = deoptMethod.asStackTraceElement(sourceFrame.getBci());
if (element.getFileName() != null && element.getLineNumber() >= 0) {
log.string(element.toString());
} else {
log.string(deoptMethod.format("%H.%n(%p)"));
}
} else {
log.string("method at ").hex(sourceFrame.getDeoptMethodAddress());
}
log.string(" bci ");
FrameInfoDecoder.logReadableBci(log, sourceFrame.getEncodedBci());
log.string(" return address ").hex(targetFrame.returnAddress.returnAddress).newline();
if (printOnlyTopFrames || Options.TraceDeoptimizationDetails.getValue()) {
printVirtualFrame(log, targetFrame);
}
count++;
if (printOnlyTopFrames && count >= 4) {
break;
}
sourceFrame = sourceFrame.getCaller();
targetFrame = targetFrame.getCaller();
}
}
log.string("]").newline();
}
private static void printVirtualFrame(Log log, VirtualFrame virtualFrame) {
FrameInfoQueryResult frameInfo = virtualFrame.getFrameInfo();
String sourceReference = frameInfo.getSourceReference().toString();
if (sourceReference != null) {
log.string(" ").string(sourceReference).newline();
}
log.string(" bci: ");
FrameInfoDecoder.logReadableBci(log, frameInfo.getEncodedBci());
log.string(" deoptMethodOffset: ").signed(frameInfo.getDeoptMethodOffset());
log.string(" deoptMethod: ").hex(frameInfo.getDeoptMethodAddress());
log.string(" return address: ").hex(virtualFrame.returnAddress.returnAddress).string(" offset: ").signed(virtualFrame.returnAddress.offset);
for (int i = 0; i < frameInfo.getValueInfos().length; i++) {
JavaConstant con = virtualFrame.getConstant(i);
if (con.getJavaKind() != JavaKind.Illegal) {
log.newline().string(" slot ").signed(i);
String name = frameInfo.getLocalVariableName(i);
if (name != null) {
log.string(" ").string(name);
}
log.string(" kind: ").string(con.getJavaKind().toString());
if (con.getJavaKind() == JavaKind.Object) {
Object val = SubstrateObjectConstant.asObject(con);
if (val == null) {
log.string(" null");
} else {
log.string(" value: ").object(val);
}
} else {
log.string(" value: ").string(con.toValueString());
}
log.string(" offset: ").signed(virtualFrame.values[i].offset);
}
}
log.newline();
}
static class TargetContent {
private final byte[] frameBuffer;
private static final int sizeofInt = JavaKind.Int.getByteCount();
private static final int sizeofLong = JavaKind.Long.getByteCount();
private final int sizeofCompressedReference = ConfigurationValues.getObjectLayout().getReferenceSize();
private final int sizeofUncompressedReference = FrameAccess.uncompressedReferenceSize();
private static final int arrayBaseOffset = ConfigurationValues.getObjectLayout().getArrayBaseOffset(JavaKind.Byte);
private static final ArrayIndexOutOfBoundsException arrayIndexOutOfBoundsException = new ArrayIndexOutOfBoundsException("TargetContent.offsetCheck");
@Uninterruptible(reason = "Called from uninterruptible code.")
private void offsetCheck(int offset, int size) {
if (!((0 <= offset) && (offset <= (frameBuffer.length - size)))) {
throw arrayIndexOutOfBoundsException;
}
}
protected TargetContent(int targetContentSize, ByteOrder byteOrder) {
if (byteOrder != ByteOrder.nativeOrder()) {
VMError.unsupportedFeature("TargetContent with non-native byte order.");
}
if (FrameAccess.returnAddressSize() != sizeofLong) {
VMError.unsupportedFeature("TargetContent with returnAddressSize() != sizeof(long).");
}
this.frameBuffer = new byte[targetContentSize];
}
@Uninterruptible(reason = "Called from uninterruptible code.")
protected int getSize() {
return frameBuffer.length;
}
@Uninterruptible(reason = "Called from uninterruptible code.")
protected void copyToPointer(Pointer p) {
for (int idx = 0; idx < frameBuffer.length; idx++) {
p.writeByte(idx, frameBuffer[idx]);
}
}
@Uninterruptible(reason = "Called from uninterruptible code.")
protected void writeInt(int offset, int value) {
offsetCheck(offset, sizeofInt);
addressOfFrameArray0().writeInt(offset, value);
}
@Uninterruptible(reason = "Called from uninterruptible code.")
protected void writeLong(int offset, long value) {
offsetCheck(offset, sizeofLong);
addressOfFrameArray0().writeLong(offset, value);
}
@Uninterruptible(reason = "Called from uninterruptible code.")
protected void writeWord(int offset, WordBase value) {
if (FrameAccess.wordSize() == 8) {
writeLong(offset, value.rawValue());
} else if (FrameAccess.wordSize() == 4) {
writeInt(offset, (int) value.rawValue());
} else {
throw VMError.shouldNotReachHere("Unexpected word size: " + FrameAccess.wordSize());
}
}
@Uninterruptible(reason = "Called from uninterruptible code.")
protected void writeObject(int offset, Object value, boolean compressed) {
offsetCheck(offset, compressed ? sizeofCompressedReference : sizeofUncompressedReference);
Word address = (Word) addressOfFrameArray0();
address = address.add(offset);
ReferenceAccess.singleton().writeObjectAt(address, value, compressed);
}
@Uninterruptible(reason = "Called from uninterruptible code.")
private Pointer addressOfFrameArray0() {
return Word.objectToUntrackedPointer(frameBuffer).add(arrayBaseOffset);
}
}
}