/*
* Copyright (c) 2012, 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.
*
* 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 com.sun.max.vm.compiler.deopt;
import static com.sun.max.platform.Platform.*;
import static com.sun.max.vm.MaxineVM.*;
import static com.sun.max.vm.compiler.CallEntryPoint.*;
import static com.sun.max.vm.compiler.target.Stub.Type.*;
import static com.sun.max.vm.stack.VMFrameLayout.*;
import static com.sun.max.vm.intrinsics.MaxineIntrinsicIDs.*;
import java.util.*;
import com.sun.cri.ci.*;
import com.sun.max.*;
import com.sun.max.annotate.*;
import com.sun.max.lang.*;
import com.sun.max.platform.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.code.*;
import com.sun.max.vm.compiler.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.compiler.target.TargetMethod.FrameAccess;
import com.sun.max.vm.log.VMLog.Record;
import com.sun.max.vm.log.hosted.*;
import com.sun.max.vm.object.*;
import com.sun.max.vm.reference.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.stack.*;
import com.sun.max.vm.thread.*;
/**
* Mechanism for applying deoptimization to one or more target methods.
* There are two separate parts to deoptimization:
* <ul>
* <li><b>Mark methods for deoptimization.</b>
* <ol>
* <li>A deoptimization {@linkplain #Deoptimization(ArrayList) request} specifies a set of methods, <i>M</i>, to be deoptimized.</li>
* <li>Each method in <i>M</i> is {@linkplain TargetMethod#invalidate(InvalidationMarker) invalidated}.</li>
* <li>Any vtable or itable entry denoting a method in <i>M</i> is reverted to a {@link Stubs#virtualTrampoline(int) vtable}
* or {@link Stubs#interfaceTrampoline(int) itable} trampoline.</li>
* <li>
* Patch the entry points of all invalidated methods to {@linkplain TargetMethod#redirectTo(TargetMethod) redirect} the caller
* to the {@linkplain Stubs#staticTrampoline() static trampoline}. This ensures all direct calls to an
* invalidated method will be re-linked the next time they are executed.
* </li>
* <li>Scan each thread looking for frames executing a method in <i>M</i>:
* <ul>
* <li>For each non-top frame executing a method in <i>M</i>, patch the callee's return address to the relevant {@linkplain Stubs#deoptStub(CiKind, boolean) stub}.</li>
* <li>For each top-frame executing a method in <i>M</i>, patch the return address in the trap stub to {@link Stubs#deoptStubForSafepointPoll()}.</li>
* </ul>
* </li>
* </ol>
* All but step 1 above are performed in a {@linkplain #doIt() VM operation} (i.e. all threads have been stopped at a safepoint).
* <p>
* One optimization applied is for each thread to perform step 5 on itself just
* {@linkplain VmOperation#doAtSafepointBeforeBlocking before} suspending. This is
* analogous to each thread preparing its own reference map when being stopped
* for a garbage collection.
*
* <li><b>Convert the frame of an optimized method into one or more deoptimized frames.</b>
* <p>
* This occurs when one of the deoptimization stubs is executed. A deoptimization stub is executed either as the result of the
* return address patching described above or by a (compiled) call to the {@linkplain Stubs#genUncommonTrapStub() uncommon trap} stub.
* All the deoptimization stubs eventually route through to {@link #deoptimize(CodePointer, Pointer, Pointer, Pointer, CiCalleeSaveLayout, CiConstant) deoptimize()}
* which constructs the deoptimized frames, unrolls them onto the stack and continues execution in the appropriate deoptimized frame.
* </i>
* </ul>
*/
public class Deoptimization extends VmOperation {
/**
* Option for enabling use of deoptimization.
*/
public static boolean UseDeopt = true;
/**
* A VM option for triggering deoptimization at fixed intervals.
*/
public static int DeoptimizeALot;
static {
VMOptions.addFieldOption("-XX:", "UseDeopt", "Enable deoptimization.");
VMOptions.addFieldOption("-XX:", "DeoptimizeALot", Deoptimization.class,
"Invalidate and deoptimize a selection of executing optimized methods every <n> milliseconds. " +
"A value of 0 disables this mechanism.");
}
/**
* The set of target methods to be deoptimized.
*/
private final ArrayList<TargetMethod> methods;
/**
* Creates an object to deoptimize a given set of methods.
*
* @param methods the set of methods to be deoptimized (must not contain duplicates)
*/
public Deoptimization(ArrayList<TargetMethod> methods) {
super("Deoptimization", null, Mode.Safepoint);
this.methods = methods;
}
/**
* Mark methods for deoptimization.
*/
public void go() {
submit();
}
@Override
protected void doIt() {
Stub staticTrampoline = vm().stubs.staticTrampoline();
int i = 0;
while (i < methods.size()) {
TargetMethod tm = methods.get(i);
if (deoptLogger.enabled()) {
deoptLogger.logDoIt("processing ", tm, true);
}
// marks method as invalidated
if (!tm.invalidate(new InvalidationMarker(tm))) {
methods.remove(i);
if (deoptLogger.enabled()) {
deoptLogger.logDoIt("ignoring previously invalidated method ", tm, true);
}
} else {
// Find all references to invalidated target method(s) in dispatch tables (e.g. vtables, itables etc) and revert to trampoline references.
// Concurrent patching ok here as it is atomic.
patchDispatchTables(tm);
tm.redirectTo(staticTrampoline);
if (deoptLogger.enabled()) {
deoptLogger.logDoIt("patched entry points of ", tm, false);
}
i++;
}
}
// Scan the stacks to patch return addresses
doAllThreads();
}
/**
* Find all instances of a given (invalidated) target method in dispatch tables
* (e.g. vtables, itables etc) and revert these entries to be trampolines.
* Concurrent patching ok here as it is atomic.
*/
static void patchDispatchTables(final TargetMethod tm) {
final ClassMethodActor method = tm.classMethodActor;
assert method != null : "de-opting target method with null class method: " + tm;
if (method instanceof VirtualMethodActor) {
VirtualMethodActor virtualMethod = (VirtualMethodActor) method;
final int vtableIndex = virtualMethod.vTableIndex();
if (vtableIndex >= 0) {
// must cast this from CodePointer to Address because the closure will create a field, which is not allowed
final Address trampoline = vm().stubs.virtualTrampoline(vtableIndex).toAddress();
ClassActor.Closure c = new ClassActor.Closure() {
@Override
public boolean doClass(ClassActor classActor) {
DynamicHub hub = classActor.dynamicHub();
if (hub.getWord(vtableIndex).equals(tm.getEntryPoint(VTABLE_ENTRY_POINT))) {
hub.setWord(vtableIndex, trampoline);
logPatchVTable(vtableIndex, classActor);
}
final int lastITableIndex = hub.iTableStartIndex + hub.iTableLength;
for (int i = hub.iTableStartIndex; i < lastITableIndex; i++) {
if (hub.getWord(i).equals(tm.getEntryPoint(VTABLE_ENTRY_POINT))) {
int iIndex = i - hub.iTableStartIndex;
hub.setWord(i, vm().stubs.interfaceTrampoline(iIndex).toAddress());
logPatchITable(classActor, iIndex);
}
}
return true;
}
};
c.doClass(method.holder());
method.holder().allSubclassesDo(c);
}
}
}
@Override
public void doThread(VmThread vmThread, Pointer ip, Pointer sp, Pointer fp) {
Patcher patcher = new Patcher(methods);
patcher.go(vmThread, ip, sp, fp);
}
/**
* Mechanism for a frame reconstruction to specify its execution state when it is resumed or returned to.
*/
public static abstract class Continuation {
/**
* The target method to which this continuation applies.
*/
TargetMethod tm;
/**
* Sets the continuation frame pointer.
*
* @param info a stack modeled as an array of frame slots
* @param fp the continuation frame pointer. This is either a word value encoding an absolute
* address or a {@link CiKind#Jsr} value encoding an index in {@code info.slots} that subsequently
* needs fixing up once absolute address have been determined for the slots.
*/
public abstract void setFP(Info info, CiConstant fp);
/**
* Sets the continuation stack pointer.
*
* @param info a stack modeled as an array of frame slots
* @param sp the continuation stack pointer. This is either a word value encoding an absolute
* address or a {@link CiKind#Jsr} value encoding an index in {@code info.slots} that subsequently
* needs fixing up once absolute address have been determined for the slots.
*/
public abstract void setSP(Info info, CiConstant sp);
/**
* Sets the continuation instruction pointer.
*
* @param info a stack modeled as an array of frame slots
* @param ip the continuation instruction pointer
*/
public abstract void setIP(Info info, Pointer ip);
}
/**
* Mechanism for a caller frame reconstruction to specify its execution state when it is returned to.
*/
public static class CallerContinuation extends Continuation {
public final int callerFPIndex;
public final int callerSPIndex;
public final int returnAddressIndex;
public CallerContinuation(int callerFPIndex, int callerSPIndex, int returnAddressIndex) {
this.callerFPIndex = callerFPIndex;
this.callerSPIndex = callerSPIndex;
this.returnAddressIndex = returnAddressIndex;
}
@Override
public void setFP(Info info, CiConstant callerFP) {
if (callerFPIndex >= 0) {
info.slots.set(callerFPIndex, callerFP);
}
}
@Override
public void setSP(Info info, CiConstant callerSP) {
if (callerSPIndex >= 0) {
info.slots.set(callerSPIndex, callerSP);
}
}
@Override
public void setIP(Info info, Pointer ip) {
if (returnAddressIndex >= 0) {
info.slots.set(returnAddressIndex, WordUtil.archConstant(ip));
if (deoptLogger.enabled()) {
deoptLogger.logContinuation(ip);
}
}
}
}
/**
* Mechanism for a top frame reconstruction to specify its execution state when it is resumed.
*/
static class TopFrameContinuation extends Continuation {
int fp;
int sp;
NativeOrVmIP ip = new NativeOrVmIP();
@Override
public void setFP(Info info, CiConstant fp) {
this.fp = fp.asInt();
}
@Override
public void setSP(Info info, CiConstant sp) {
this.sp = sp.asInt();
}
@Override
public void setIP(Info info, Pointer ip) {
this.ip.derive(ip);
if (deoptLogger.enabled()) {
deoptLogger.logContinuation(ip);
}
}
}
/**
* Overwrites the current thread's stack with deoptimized frames and continues
* execution in the top frame at {@code info.pc}.
*/
@NEVER_INLINE
public static void unroll(Info info) {
ArrayList<CiConstant> slots = info.slots;
Pointer sp = info.slotsAddr.plus(info.slotsSize() - STACK_SLOT_SIZE);
if (deoptLogger.enabled()) {
deoptLogger.logUnroll(info, slots, sp);
}
for (int i = slots.size() - 1; i >= 0; --i) {
CiConstant c = slots.get(i);
if (c.kind.isObject()) {
Object obj = c.asObject();
sp.writeWord(0, Reference.fromJava(obj).toOrigin());
} else if (c.kind.isLong()) {
sp.writeLong(0, c.asLong());
} else if (c.kind.isDouble()) {
sp.writeDouble(0, c.asDouble());
} else {
sp.writeWord(0, Address.fromLong(c.asLong()));
}
sp = sp.minus(STACK_SLOT_SIZE);
}
// Checkstyle: stop
if (info.returnValue == null) {
// Re-enable safepoints
SafepointPoll.enable();
Stubs.unwind(info.ip.asPointer(), info.sp, info.fp);
} else {
if (StackReferenceMapPreparer.VerifyRefMaps || deoptLogger.enabled() || DeoptimizeALot != 0) {
StackReferenceMapPreparer.verifyReferenceMaps(VmThread.current(), info.ip.vmIP(), info.sp, info.fp);
}
// Re-enable safepoints
SafepointPoll.enable();
switch (info.returnValue.kind.stackKind()) {
case Int: Stubs.unwindInt(info.ip.asPointer(), info.sp, info.fp, info.returnValue.asInt());
case Float: Stubs.unwindFloat(info.ip.asPointer(), info.sp, info.fp, info.returnValue.asFloat());
case Long: Stubs.unwindLong(info.ip.asPointer(), info.sp, info.fp, info.returnValue.asLong());
case Double: Stubs.unwindDouble(info.ip.asPointer(), info.sp, info.fp, info.returnValue.asDouble());
case Object: Stubs.unwindObject(info.ip.asPointer(), info.sp, info.fp, info.returnValue.asObject());
default: FatalError.unexpected("unexpected return kind: " + info.returnValue.kind.stackKind());
}
}
// Checkstyle: resume
}
/**
* Called from the {@code int} {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub} for.
*/
@NEVER_INLINE
public static void deoptimizeInt(Pointer ip, Pointer sp, Pointer fp, Pointer csa, int returnValue) {
deoptimizeOnReturn(CodePointer.from(ip), sp, fp, csa, CiConstant.forInt(returnValue));
}
/**
* Called from the {@code float} {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub} for.
*/
@NEVER_INLINE
public static void deoptimizeFloat(Pointer ip, Pointer sp, Pointer fp, Pointer csa, float returnValue) {
deoptimizeOnReturn(CodePointer.from(ip), sp, fp, csa, CiConstant.forFloat(returnValue));
}
/**
* Called from the {@code long} {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub} for.
*/
@NEVER_INLINE
public static void deoptimizeLong(Pointer ip, Pointer sp, Pointer fp, Pointer csa, long returnValue) {
deoptimizeOnReturn(CodePointer.from(ip), sp, fp, csa, CiConstant.forLong(returnValue));
}
/**
* Called from the {@code double} {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub} for.
*/
@NEVER_INLINE
public static void deoptimizeDouble(Pointer ip, Pointer sp, Pointer fp, Pointer csa, double returnValue) {
deoptimizeOnReturn(CodePointer.from(ip), sp, fp, csa, CiConstant.forDouble(returnValue));
}
/**
* Called from the {@code Word} {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub} for.
*/
@NEVER_INLINE
public static void deoptimizeWord(Pointer ip, Pointer sp, Pointer fp, Pointer csa, Word returnValue) {
deoptimizeOnReturn(CodePointer.from(ip), sp, fp, csa, WordUtil.archConstant(returnValue));
}
/**
* Called from the {@code Object} {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub} for.
*/
@NEVER_INLINE
public static void deoptimizeObject(Pointer ip, Pointer sp, Pointer fp, Pointer csa, Object returnValue) {
deoptimizeOnReturn(CodePointer.from(ip), sp, fp, csa, CiConstant.forObject(returnValue));
}
/**
* Called from the {@code void} {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub} for.
*/
@NEVER_INLINE
public static void deoptimizeVoid(Pointer ip, Pointer sp, Pointer fp, Pointer csa) {
deoptimizeOnReturn(CodePointer.from(ip), sp, fp, csa, null);
}
static boolean stackIsWalkable(StackFrameWalker sfw, Pointer ip, Pointer sp, Pointer fp) {
sfw.inspect(ip, sp, fp, new RawStackFrameVisitor.Default());
return true;
}
/**
* Stack visitor used to patch return addresses denoting a method being deoptimized.
*/
public static class Patcher extends com.sun.max.vm.stack.RawStackFrameVisitor {
/**
* The set of methods being deoptimized.
*/
private final ArrayList<TargetMethod> methods;
public Patcher(ArrayList<TargetMethod> methods) {
this.methods = methods;
}
private ClassMethodActor lastCalleeMethod;
/**
* Walk the stack of a given thread and patch all return addresses denoting
* one of the methods in {@link #methods}.
*
* @param thread the thread whose stack is to be walked
*/
public void go(VmThread thread, Pointer ip, Pointer sp, Pointer fp) {
VmStackFrameWalker sfw = new VmStackFrameWalker(thread.tla());
sfw.inspect(ip, sp, fp, this);
assert stackIsWalkable(sfw, ip, sp, fp);
}
@Override
public boolean visitFrame(StackFrameCursor current, StackFrameCursor callee) {
TargetMethod tm = current.targetMethod();
TargetMethod calleeTM = callee.targetMethod();
boolean deopt = methods.contains(tm);
if (deopt && calleeTM.is(TrapStub)) {
// Patches the return address in the trap frame to trigger deoptimization
// of the top frame when the trap stub returns.
Stub stub = vm().stubs.deoptStubForSafepointPoll();
CodePointer to = stub.codeStart();
Pointer save = current.sp().plus(DEOPT_RETURN_ADDRESS_OFFSET);
Pointer patch = tm.returnAddressPointer(callee);
CodePointer from = CodePointer.from(patch.readWord(0));
assert !to.equals(from);
if (deoptLogger.enabled()) {
deoptLogger.logPatchReturnAddress(tm, "TRAP STUB", stub, to, save, patch, from);
}
patch.writeWord(0, to.toAddress());
save.writeWord(0, from.toAddress());
} else {
if (calleeTM != null && calleeTM.classMethodActor != null) {
lastCalleeMethod = calleeTM.classMethodActor;
}
if (deopt) {
patchReturnAddress(current, callee, lastCalleeMethod);
}
}
return true;
}
}
/**
* Encapsulates various info used during deoptimization of a single optimized frame.
*/
public static class Info extends com.sun.max.vm.stack.RawStackFrameVisitor {
/**
* Method being deoptimized.
*/
public final TargetMethod tm;
// The following are initially the details of the frame being deoptimized.
// Just before unrolling, they are then modified to reflect the top
// deoptimized frame.
public final NativeOrVmIP ip = new NativeOrVmIP();
Pointer sp;
Pointer fp;
/**
* The address in the stack at which the {@linkplain #slots slots} of the
* deoptimized frame(s) are unrolled.
*/
Pointer slotsAddr;
// The following are the details of the caller of the frame being deoptimized.
/**
* The actual return address in frame being deoptimized. This will differ from
* {@link #callerIP} when the caller is (also) marked for deoptimization.
*/
NativeOrVmIP returnIP = new NativeOrVmIP();
/**
* The instruction pointer in the caller of the frame being deoptimized.
*/
NativeOrVmIP callerIP = new NativeOrVmIP();
/**
* The stack pointer in the caller (frame) of the frame being deoptimized.
*/
Pointer callerSP;
/**
* The frame pointer in the caller (frame) of the frame being deoptimized.
*/
Pointer callerFP;
/**
* The return value.
*/
CiConstant returnValue;
/**
* The slots of the deoptimized frame(s).
*/
final ArrayList<CiConstant> slots = new ArrayList<CiConstant>();
/**
* Names of the deoptimized stack slots. Only used if {@link Deoptimization#deoptLogger} is enabled.
*/
final ArrayList<String> slotNames = deoptLogger.enabled() ? new ArrayList<String>() : null;
/**
* Creates a context for deoptimizing a frame executing a given method.
*
* @param thread the frame's thread
* @param ip the execution point in the frame
* @param sp the stack pointer of the frame
* @param fp the frame pointer of the frame
*/
public Info(VmThread thread, Pointer ip, Pointer sp, Pointer fp) {
this.ip.derive(ip);
this.tm = this.ip.targetMethod();
this.sp = sp;
this.fp = fp;
VmStackFrameWalker sfw = new VmStackFrameWalker(thread.tla());
sfw.inspect(ip, sp, fp, this);
assert stackIsWalkable(sfw, ip, sp, fp);
}
@Override
public boolean visitFrame(StackFrameCursor current, StackFrameCursor callee) {
if (current.isTopFrame()) {
// Ensure that the top frame of the walk is the method being deoptimized
assert tm == current.targetMethod();
assert ip.asPointer() == current.ipAsPointer();
assert sp == current.sp();
assert fp == current.fp();
return true;
}
assert callee.isTopFrame();
TargetMethod calleeTM = callee.targetMethod();
assert calleeTM == tm;
callerSP = current.sp();
callerFP = current.fp();
callerIP.derive(current.ipAsPointer());
// The caller may (also) be marked for deoptimization. If so, then the callee's
// return address slot may denote a deopt stub address. However,
// the stack frame walker "recovers" the original return address which is
// reflected in current.ip(). We must use the actual return address after
// deoptimizing the callee and so the code below by-passes any
// stack frame walker "recovery".
returnIP.derive(calleeTM.returnAddressPointer(callee).readWord(0).asPointer());
return false;
}
public TargetMethod callerTM() {
return callerIP.targetMethod();
}
public void addSlot(CiConstant slot, String name) {
slots.add(slot);
if (slotNames != null) {
slotNames.add(name);
}
}
public int slotsSize() {
return slotsCount() * STACK_SLOT_SIZE;
}
public int slotsCount() {
return slots.size();
}
}
/**
* Patches the return address in a callee frame to trigger deoptimization of an invalidated caller.
*
* @param caller the frame of the method to be deoptimized
* @param callee the callee frame whose return address is to be patched
* @param calleeMethod the class method actor that is being called. This is required in addition to {@code callee}
* in the case where {@code callee} is an adapter frame
*/
static void patchReturnAddress(StackFrameCursor caller, StackFrameCursor callee, ClassMethodActor calleeMethod) {
assert calleeMethod != null;
TargetMethod tm = caller.targetMethod();
Stub stub = vm().stubs.deoptStub(calleeMethod.descriptor().returnKind(true), callee.targetMethod().is(CompilerStub));
CodePointer to = stub.codeStart();
Pointer save = caller.sp().plus(DEOPT_RETURN_ADDRESS_OFFSET);
Pointer patch = callee.targetMethod().returnAddressPointer(callee);
CodePointer from = CodePointer.from(patch.readWord(0));
assert !to.equals(from);
if (deoptLogger.enabled()) {
deoptLogger.logPatchReturnAddress(tm, callee.targetMethod(), stub, to, save, patch, from);
}
patch.writeWord(0, to.toAddress());
save.writeWord(0, from.toAddress());
}
/**
* The fixed offset in a method's frame where the original return address is saved when the callee's
* return address slot is {@linkplain #patchReturnAddress(StackFrameCursor, StackFrameCursor, ClassMethodActor) patched} with the address
* of a {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub}.
*/
public static final int DEOPT_RETURN_ADDRESS_OFFSET = 0;
/**
* Deoptimizes a method that is being returned to via a {@linkplain Stubs#deoptStub(CiKind, boolean) deoptimization stub}.
*
* @param ip the address in the method returned to
* @param sp the stack pointer of the frame executing the method
* @param fp the frame pointer of the frame executing the method
* @param csa the callee save area. This is non-null iff deoptimizing upon return from a compiler stub.
* @param returnValue the value being returned (will be {@code null} if returning from a void method)
*/
private static void deoptimizeOnReturn(CodePointer ip, Pointer sp, Pointer fp, Pointer csa, CiConstant returnValue) {
deoptimize(ip, sp, fp, csa, csa.isZero() ? null : vm().registerConfigs.compilerStub.csl, returnValue);
}
/**
* Deoptimizes a method executing in a given frame.
* Constructs the deoptimized frames, unrolls them onto the stack and continues execution
* in the top most deoptimized frame.
*
* @param ip the continuation address in the method
* @param sp the stack pointer of the frame executing the method
* @param fp the frame pointer of the frame executing the method
* @param csa the callee save area. This is non-null iff deoptimizing upon return from a compiler stub.
* @param csl the layout of the callee save area pointed to by {@code csa}
* @param returnValue the value being returned (will be {@code null} if returning from a void method or not deoptimizing upon return)
*/
public static void deoptimize(CodePointer ip, Pointer sp, Pointer fp, Pointer csa, CiCalleeSaveLayout csl, CiConstant returnValue) {
SafepointPoll.disable();
Info info = new Info(VmThread.current(), ip.toPointer(), sp, fp);
if (deoptLogger.enabled()) {
deoptLogger.logStart(info.tm);
}
if (StackReferenceMapPreparer.VerifyRefMaps || deoptLogger.enabled() || DeoptimizeALot != 0) {
StackReferenceMapPreparer.verifyReferenceMapsForThisThread();
}
TargetMethod tm = info.tm;
Throwable pendingException = VmThread.current().pendingException();
int safepointIndex = tm.findSafepointIndex(ip);
assert safepointIndex >= 0 : "no safepoint index for " + tm + "+" + tm.posFor(ip);
if (deoptLogger.enabled()) {
deoptLogger.logTmPos(tm, safepointIndex);
}
FrameAccess fa = new FrameAccess(csl, csa, sp, fp, info.callerSP, info.callerFP);
CiDebugInfo debugInfo = tm.debugInfoAt(safepointIndex, fa);
CiFrame topFrame = debugInfo.frame();
FatalError.check(topFrame != null, "No frame info found at deopt site: " + tm.posFor(ip));
if (pendingException != null) {
topFrame = unwindToHandlerFrame(topFrame, pendingException);
assert topFrame != null : "could not (re)find handler for " + pendingException +
" thrown at " + tm + "+" + ip.to0xHexString();
}
if (deoptLogger.enabled()) {
// Trace the frame states in terms of the locations holding the frame values
deoptLogger.logFrames(unwindToHandlerFrame(tm.debugInfoAt(safepointIndex, null).frame(), pendingException), "locations");
// Trace the frame states in terms of the frame values
deoptLogger.logFrames(topFrame, "values");
}
// Construct the deoptimized frames for each frame in the debug info
final TopFrameContinuation topCont = new TopFrameContinuation();
Continuation cont = topCont;
for (CiFrame frame = topFrame; frame != null; frame = frame.caller()) {
ClassMethodActor method = (ClassMethodActor) frame.method;
TargetMethod compiledMethod = vm().compilationBroker.compileForDeopt(method);
FatalError.check(compiledMethod.isBaseline(), compiledMethod + " should be a deopt target");
cont.tm = compiledMethod;
boolean reexecute = false;
if (frame == topFrame && !Safepoints.isCall(tm.safepoints().safepointAt(safepointIndex))) {
reexecute = true;
}
cont = compiledMethod.createDeoptimizedFrame(info, frame, cont, pendingException, reexecute);
// The exception (if any) must be handled in the top frame
pendingException = null;
}
// On AMD64, all compilers agree on the registers used for return values (i.e. RAX and XMM0).
// As such there is no current need to reconstruct an adapter frame between the lowest
// deoptimized frame and the frame of its caller. This may need to be revisited for
// other platforms so use an assertion for now.
assert platform().isa == ISA.AMD64;
// Fix up the caller details for the bottom most deoptimized frame
cont.tm = info.callerTM();
cont.setIP(info, info.returnIP.asPointer());
cont.setSP(info, WordUtil.archConstant(info.callerSP));
cont.setFP(info, WordUtil.archConstant(info.callerFP));
int slotsSize = info.slotsSize();
Pointer slotsAddrs = sp.plus(tm.frameSize() + STACK_SLOT_SIZE).minus(slotsSize);
info.slotsAddr = slotsAddrs;
// Fix up slots referring to other slots (the references are encoded as CiKind.Jsr values)
ArrayList<CiConstant> slots = info.slots;
for (int i = 0; i < slots.size(); i++) {
CiConstant c = slots.get(i);
if (c.kind.isJsr()) {
int slotIndex = c.asInt();
Pointer slotAddr = slotsAddrs.plus(slotIndex * STACK_SLOT_SIZE);
slots.set(i, WordUtil.archConstant(slotAddr));
}
}
// Compute the physical frame details for the top most deoptimized frame
sp = slotsAddrs.plus(topCont.sp * STACK_SLOT_SIZE);
fp = slotsAddrs.plus(topCont.fp * STACK_SLOT_SIZE);
// Set the deopt continuation to the top most deoptimized frame
info.ip.copyFrom(topCont.ip);
info.sp = sp;
info.fp = fp;
info.returnValue = returnValue;
// Compute the stack space between the current frame (executing this method) and the
// the top most deoptimized frame and use it to ensure that the unroll method
// executes with enough stack space below it to unroll the deoptimized frames
int used = info.sp.minus(VMRegister.getCpuStackPointer()).toInt() + tm.frameSize();
int frameSize = Platform.target().alignFrameSize(Math.max(slotsSize - used, 0));
Stubs.unroll(info, frameSize);
FatalError.unexpected("should not reach here");
}
/**
* Finds the frame containing a handler for an exception thrown at the current BCI of the frame.
*
* @param topFrame the frame to start searching in
* @param exception the exception being thrown
* @return the frame that catches {@code exception}
*/
static CiFrame unwindToHandlerFrame(CiFrame topFrame, Throwable exception) {
if (exception == null) {
return topFrame;
}
// Unwind to frame with handler
for (CiFrame frame = topFrame; frame != null; frame = frame.caller()) {
ClassMethodActor method = (ClassMethodActor) frame.method;
CiExceptionHandler[] handlers = method.exceptionHandlers();
if (handlers != null) {
int bci = frame.bci;
for (CiExceptionHandler h : handlers) {
if (h.startBCI <= bci && bci < h.endBCI) {
ClassActor catchType = (ClassActor) h.catchType;
if (catchType == null || catchType.isAssignableFrom(ObjectAccess.readClassActor(exception))) {
return frame.withEmptyStack();
}
}
}
}
}
return null;
}
/**
* Deoptimizes a method that was trapped at a safepoint poll.
*
* @param ip the trap address
* @param sp the trap stack pointer
* @param fp the trap frame pointer
* @param csa the callee save area
*/
public static void deoptimizeAtSafepoint(Pointer ip, Pointer sp, Pointer fp, Pointer csa) {
FatalError.check(!csa.isZero(), "callee save area expected for deopt at safepoint");
deoptimize(CodePointer.from(ip), sp, fp, csa, vm().registerConfigs.trapStub.csl, null);
}
/**
* Deoptimizes at an {@link Stubs#genUncommonTrapStub() uncommon trap}.
*
* @param ip the address of the uncommon trap
* @param sp the stack pointer of the frame executing the method
* @param fp the frame pointer of the frame executing the method
*/
public static void uncommonTrap(Pointer csa, Pointer ip, Pointer sp, Pointer fp) {
FatalError.check(!csa.isZero(), "callee save area expected for uncommon trap");
deoptimize(CodePointer.from(ip), sp, fp, csa, vm().registerConfigs.uncommonTrapStub.getCalleeSaveLayout(), null);
}
@NEVER_INLINE // makes inspecting easier
static void logPatchITable(ClassActor classActor, int iIndex) {
if (deoptLogger.enabled()) {
deoptLogger.logPatchITable(classActor, iIndex);
}
}
@NEVER_INLINE // makes inspecting easier
static void logPatchVTable(final int vtableIndex, ClassActor classActor) {
if (deoptLogger.enabled()) {
deoptLogger.logPatchVTable(vtableIndex, classActor);
}
}
@HOSTED_ONLY
@VMLoggerInterface
private interface DeoptLoggerInterface {
void start(
@VMLogParam(name = "targetMethod") TargetMethod targetMethod);
void doIt(
@VMLogParam(name = "msg") String msg,
@VMLogParam(name = "targetMethod") TargetMethod targetMethod,
@VMLogParam(name = "ts") boolean ts);
void tmPos(
@VMLogParam(name = "targetMethod") TargetMethod targetMethod,
@VMLogParam(name = "safepointIndex") int safepointIndex);
void frames(
@VMLogParam(name = "topFrame") CiFrame topFrame,
@VMLogParam(name = "label") String label);
void patchVTable(
@VMLogParam(name = "vtableIndex") int vtableIndex,
@VMLogParam(name = "classActor") ClassActor classActor);
void patchITable(
@VMLogParam(name = "classActor") ClassActor classActor,
@VMLogParam(name = "iIndex") int iIndex);
void patchReturnAddress(
@VMLogParam(name = "targetMethod") TargetMethod targetMethod,
@VMLogParam(name = "callee") Object callee,
@VMLogParam(name = "stub") Stub stub,
@VMLogParam(name = "to") CodePointer to,
@VMLogParam(name = "save") Pointer save,
@VMLogParam(name = "patch") Pointer patch,
@VMLogParam(name = "from") CodePointer from);
void unroll(
@VMLogParam(name = "info") Info info,
@VMLogParam(name = "slots") ArrayList<CiConstant> slots,
@VMLogParam(name = "sp") Pointer sp);
void continuation(
@VMLogParam(name = "ip") Pointer ip);
void aLot(
@VMLogParam(name = "methods") ArrayList<TargetMethod> methods);
void catchException(
@VMLogParam(name = "thisTargetMethod") TargetMethod thisTargetMethod,
@VMLogParam(name = "fromTargetMethod") TargetMethod fromTargetMethod,
@VMLogParam(name = "stub") Stub deoptStub,
@VMLogParam(name = "sp") Pointer sp,
@VMLogParam(name = "fp") Pointer fp);
}
public static final DeoptLogger deoptLogger = new DeoptLogger();
public static final class DeoptLogger extends DeoptLoggerAuto {
DeoptLogger() {
super("Deopt", "deoptimzation");
}
private static void deoptPrefix() {
Log.print("DEOPT: ");
}
@Override
protected void traceStart(TargetMethod targetMethod) {
deoptPrefix(); Log.println("Deoptimizing " + targetMethod);
Throw.stackDump("DEOPT: Raw stack frames:");
new Throwable("DEOPT: Bytecode stack frames:").printStackTrace(Log.out);
}
@Override
protected void traceDoIt(String msg, TargetMethod targetMethod, boolean ts) {
deoptPrefix();
Log.print(msg);
if (ts) {
Log.println(targetMethod);
} else {
Log.printMethod(targetMethod, true);
}
}
@Override
protected void traceTmPos(TargetMethod tm, int safepointIndex) {
deoptPrefix();
Log.println(tm + ", safepointIndex=" + safepointIndex + ", pos=" + tm.safepoints().posAt(safepointIndex));
}
@Override
protected void traceFrames(CiFrame topFrame, String label) {
deoptPrefix(); Log.println("--- " + label + " start ---");
Log.println(topFrame);
deoptPrefix(); Log.println("--- " + label + " end ---");
}
@Override
protected void tracePatchITable(ClassActor classActor, int iIndex) {
deoptPrefix(); Log.println(" patched itable[" + iIndex + "] of " + classActor + " with trampoline");
}
@Override
protected void tracePatchVTable(int vtableIndex, ClassActor classActor) {
deoptPrefix(); Log.println(" patched vtable[" + vtableIndex + "] of " + classActor + " with trampoline");
}
@Override
protected void tracePatchReturnAddress(TargetMethod tm, Object callee, Stub stub, CodePointer to, Pointer save, Pointer patch, CodePointer from) {
deoptPrefix(); Log.println("patched return address @ " + patch.to0xHexString() + " of call to " + callee +
": " + from.to0xHexString() + '[' + tm + '+' + from.minus(tm.codeStart()).toInt() + ']' +
" -> " + to.to0xHexString() + '[' + stub + "], saved old value @ " + save.to0xHexString());
}
@Override
protected void traceUnroll(Info info, ArrayList<CiConstant> slots, Pointer sp) {
deoptPrefix(); Log.println("Unrolling frames");
for (int i = slots.size() - 1; i >= 0; --i) {
CiConstant c = slots.get(i);
String name = info.slotNames.get(i);
deoptPrefix();
Log.print(sp);
Log.print("[" + name);
for (int pad = name.length(); pad < 12; pad++) {
Log.print(' ');
}
Log.print("]: ");
Log.println(c);
sp = sp.minus(STACK_SLOT_SIZE);
}
deoptPrefix(); Log.print("unrolling: ip=");
Log.print(info.ip);
Log.print(", sp=");
Log.print(info.sp);
Log.print(", fp=");
Log.print(info.fp);
if (info.returnValue != null) {
Log.print(", returnValue=");
Log.print(info.returnValue);
}
Log.println();
}
@Override
protected void traceContinuation(Pointer ip) {
TargetMethod tm = Code.codePointerToTargetMethod(ip);
assert tm != null : "cannot deoptimize frame of a VM entry method";
deoptPrefix(); Log.print("continuation: ");
Log.printLocation(tm, CodePointer.from(ip), true);
}
@Override
protected void traceALot(ArrayList<TargetMethod> methods) {
deoptPrefix(); Log.println("DeoptimizeALot selected methods:");
for (TargetMethod tm : methods) {
deoptPrefix(); Log.print(" ");
Log.printMethod(tm, true);
}
}
@Override
protected void traceCatchException(TargetMethod thisTargetMethod, TargetMethod fromTargetMethod, Stub stub, Pointer sp, Pointer fp) {
deoptPrefix();
Log.println("changed exception handler address in " + this + " from " + fromTargetMethod +
" to " + stub + " [sp=" + sp.to0xHexString() + ", fp=" + fp.to0xHexString() + "]");
}
}
// START GENERATED CODE
private static abstract class DeoptLoggerAuto extends com.sun.max.vm.log.VMLogger {
public enum Operation {
ALot, CatchException, Continuation,
DoIt, Frames, PatchITable, PatchReturnAddress,
PatchVTable, Start, TmPos, Unroll;
@SuppressWarnings("hiding")
public static final Operation[] VALUES = values();
}
private static final int[] REFMAPS = new int[] {0x1, 0x7, 0x0, 0x3, 0x3, 0x0, 0x4f, 0x0, 0x1, 0x1, 0x3};
protected DeoptLoggerAuto(String name, String optionDescription) {
super(name, Operation.VALUES.length, optionDescription, REFMAPS);
}
@Override
public String operationName(int opCode) {
return Operation.VALUES[opCode].name();
}
@INLINE
public final void logALot(ArrayList methods) {
log(Operation.ALot.ordinal(), objectArg(methods));
}
protected abstract void traceALot(ArrayList<TargetMethod> methods);
@INLINE
public final void logCatchException(TargetMethod thisTargetMethod, TargetMethod fromTargetMethod, Stub stub, Pointer sp, Pointer fp) {
log(Operation.CatchException.ordinal(), objectArg(thisTargetMethod), objectArg(fromTargetMethod), objectArg(stub), sp, fp);
}
protected abstract void traceCatchException(TargetMethod thisTargetMethod, TargetMethod fromTargetMethod, Stub stub, Pointer sp, Pointer fp);
@INLINE
public final void logContinuation(Pointer ip) {
log(Operation.Continuation.ordinal(), ip);
}
protected abstract void traceContinuation(Pointer ip);
@INLINE
public final void logDoIt(String msg, TargetMethod targetMethod, boolean ts) {
log(Operation.DoIt.ordinal(), objectArg(msg), objectArg(targetMethod), booleanArg(ts));
}
protected abstract void traceDoIt(String msg, TargetMethod targetMethod, boolean ts);
@INLINE
public final void logFrames(CiFrame topFrame, String label) {
log(Operation.Frames.ordinal(), objectArg(topFrame), objectArg(label));
}
protected abstract void traceFrames(CiFrame topFrame, String label);
@INLINE
public final void logPatchITable(ClassActor classActor, int iIndex) {
log(Operation.PatchITable.ordinal(), classActorArg(classActor), intArg(iIndex));
}
protected abstract void tracePatchITable(ClassActor classActor, int iIndex);
@INLINE
public final void logPatchReturnAddress(TargetMethod targetMethod, Object callee, Stub stub, CodePointer to, Pointer save,
Pointer patch, CodePointer from) {
log(Operation.PatchReturnAddress.ordinal(), objectArg(targetMethod), objectArg(callee), objectArg(stub), codePointerArg(to), save,
patch, codePointerArg(from));
}
protected abstract void tracePatchReturnAddress(TargetMethod targetMethod, Object callee, Stub stub, CodePointer to, Pointer save,
Pointer patch, CodePointer from);
@INLINE
public final void logPatchVTable(int vtableIndex, ClassActor classActor) {
log(Operation.PatchVTable.ordinal(), intArg(vtableIndex), classActorArg(classActor));
}
protected abstract void tracePatchVTable(int vtableIndex, ClassActor classActor);
@INLINE
public final void logStart(TargetMethod targetMethod) {
log(Operation.Start.ordinal(), objectArg(targetMethod));
}
protected abstract void traceStart(TargetMethod targetMethod);
@INLINE
public final void logTmPos(TargetMethod targetMethod, int safepointIndex) {
log(Operation.TmPos.ordinal(), objectArg(targetMethod), intArg(safepointIndex));
}
protected abstract void traceTmPos(TargetMethod targetMethod, int safepointIndex);
@INLINE
public final void logUnroll(Info info, ArrayList slots, Pointer sp) {
log(Operation.Unroll.ordinal(), objectArg(info), objectArg(slots), sp);
}
protected abstract void traceUnroll(Info info, ArrayList<CiConstant> slots, Pointer sp);
@Override
protected void trace(Record r) {
switch (r.getOperation()) {
case 0: { //ALot
traceALot(toArrayListTargetMethod(r, 1));
break;
}
case 1: { //CatchException
traceCatchException(toTargetMethod(r, 1), toTargetMethod(r, 2), toStub(r, 3), toPointer(r, 4), toPointer(r, 5));
break;
}
case 2: { //Continuation
traceContinuation(toPointer(r, 1));
break;
}
case 3: { //DoIt
traceDoIt(toString(r, 1), toTargetMethod(r, 2), toBoolean(r, 3));
break;
}
case 4: { //Frames
traceFrames(toCiFrame(r, 1), toString(r, 2));
break;
}
case 5: { //PatchITable
tracePatchITable(toClassActor(r, 1), toInt(r, 2));
break;
}
case 6: { //PatchReturnAddress
tracePatchReturnAddress(toTargetMethod(r, 1), toObject(r, 2), toStub(r, 3), toCodePointer(r, 4), toPointer(r, 5), toPointer(r, 6), toCodePointer(r, 7));
break;
}
case 7: { //PatchVTable
tracePatchVTable(toInt(r, 1), toClassActor(r, 2));
break;
}
case 8: { //Start
traceStart(toTargetMethod(r, 1));
break;
}
case 9: { //TmPos
traceTmPos(toTargetMethod(r, 1), toInt(r, 2));
break;
}
case 10: { //Unroll
traceUnroll(toInfo(r, 1), toArrayListCiConstant(r, 2), toPointer(r, 3));
break;
}
}
}
static ArrayList<CiConstant> toArrayListCiConstant(Record r, int argNum) {
if (MaxineVM.isHosted()) {
Class<ArrayList<CiConstant>> type = null;
return Utils.cast(type, ObjectArg.getArg(r, argNum));
} else {
return asArrayListCiConstant(toObject(r, argNum));
}
}
@INTRINSIC(UNSAFE_CAST)
private static native ArrayList<CiConstant> asArrayListCiConstant(Object arg);
static ArrayList<TargetMethod> toArrayListTargetMethod(Record r, int argNum) {
if (MaxineVM.isHosted()) {
Class<ArrayList<TargetMethod>> type = null;
return Utils.cast(type, ObjectArg.getArg(r, argNum));
} else {
return asArrayListTargetMethod(toObject(r, argNum));
}
}
@INTRINSIC(UNSAFE_CAST)
private static native ArrayList<TargetMethod> asArrayListTargetMethod(Object arg);
static CiFrame toCiFrame(Record r, int argNum) {
if (MaxineVM.isHosted()) {
return (CiFrame) ObjectArg.getArg(r, argNum);
} else {
return asCiFrame(toObject(r, argNum));
}
}
@INTRINSIC(UNSAFE_CAST)
private static native CiFrame asCiFrame(Object arg);
static Info toInfo(Record r, int argNum) {
if (MaxineVM.isHosted()) {
return (Info) ObjectArg.getArg(r, argNum);
} else {
return asInfo(toObject(r, argNum));
}
}
@INTRINSIC(UNSAFE_CAST)
private static native Info asInfo(Object arg);
}
// END GENERATED CODE
}