/*
* Copyright (c) 2009, 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.oracle.max.vm.ext.maxri;
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.deopt.Deoptimization.*;
import static com.sun.max.vm.compiler.target.Stub.Type.*;
import static com.sun.max.vm.stack.StackReferenceMapPreparer.*;
import java.util.*;
import com.sun.cri.ci.*;
import com.sun.cri.ci.CiCallingConvention.Type;
import com.sun.cri.ci.CiRegister.RegisterFlag;
import com.sun.cri.ci.CiTargetMethod.Call;
import com.sun.cri.ci.CiTargetMethod.CodeAnnotation;
import com.sun.cri.ci.CiTargetMethod.ExceptionHandler;
import com.sun.cri.ci.CiTargetMethod.Mark;
import com.sun.cri.ci.CiTargetMethod.Safepoint;
import com.sun.cri.ri.*;
import com.sun.max.annotate.*;
import com.sun.max.lang.*;
import com.sun.max.program.*;
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.code.CodeManager.Lifespan;
import com.sun.max.vm.collect.*;
import com.sun.max.vm.compiler.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.compiler.target.amd64.*;
import com.sun.max.vm.object.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.stack.*;
import com.sun.max.vm.thread.*;
import com.sun.max.vm.type.*;
/**
* This class implements a {@link TargetMethod target method} for
* the Maxine VM compiled by a compiler written against the
* CRI project.
*/
public final class MaxTargetMethod extends TargetMethod implements Cloneable {
/**
* An array of pairs denoting the code positions protected by an exception handler.
* A pair {@code {p,h}} at index {@code i} in this array specifies that code position
* {@code h} is the handler for an exception of type {@code t} occurring at position
* {@code p} where {@code t} is the element at index {@code i / 2} in {@link #exceptionClassActors}.
*/
private int[] exceptionPositionsToCatchPositions;
/**
* @see #exceptionPositionsToCatchPositions
*/
private ClassActor[] exceptionClassActors;
/**
* The handler BCI values corresponding to {@link #exceptionPositionsToCatchPositions}.
* Needed by JVMTI.
*/
private int[] exceptionHandlerBCIs;
/**
* Debug info.
*/
private DebugInfo debugInfo;
private final CodeAnnotation[] annotations;
@HOSTED_ONLY
private CiTargetMethod bootstrappingCiTargetMethod;
public MaxTargetMethod(ClassMethodActor classMethodActor, CiTargetMethod ciTargetMethod, boolean install) {
super(classMethodActor, CallEntryPoint.OPTIMIZED_ENTRY_POINT);
assert classMethodActor != null;
List<CodeAnnotation> annotations = ciTargetMethod.annotations();
this.annotations = annotations == null ? null : annotations.toArray(new CodeAnnotation[annotations.size()]);
init(ciTargetMethod, install);
}
private void init(CiTargetMethod ciTargetMethod, boolean install) {
if (isHosted()) {
// Save the target method for later gathering of calls and duplication
this.bootstrappingCiTargetMethod = ciTargetMethod;
}
for (Mark mark : ciTargetMethod.marks) {
FatalError.unexpected("Unknown mark in code generated for " + this + ": " + mark);
}
if (classMethodActor != null) {
int customStackAreaOffset = ciTargetMethod.customStackAreaOffset();
if (customStackAreaOffset != DEOPT_RETURN_ADDRESS_OFFSET) {
throw new InternalError("custom stack area offset should be " + DEOPT_RETURN_ADDRESS_OFFSET + ", not " + customStackAreaOffset);
}
}
initCodeBuffer(ciTargetMethod, install);
initFrameLayout(ciTargetMethod);
CiDebugInfo[] debugInfos = initSafepoints(ciTargetMethod);
initExceptionTable(ciTargetMethod);
debugInfo = new DebugInfo(debugInfos, this);
if (!isHosted()) {
if (install) {
linkDirectCalls();
} else {
// the displacement between a call site in the heap and a code cache location may not fit in the offset operand of a call
}
}
}
@Override
public Lifespan lifespan() {
return Lifespan.LONG;
}
@Override
public CodeAnnotation[] annotations() {
return annotations;
}
/**
* Gets the size (in bytes) of a bit map covering all the registers that may store references.
* The bit position of a register in the bit map is the register's {@linkplain CiRegister#encoding encoding}.
*/
@FOLD
public static int regRefMapSize() {
return ByteArrayBitMap.computeBitMapSize(target().arch.registerReferenceMapBitCount);
}
/**
* @return the size of an activation frame for this target method in words.
*/
private int frameWords() {
return frameSize() / Word.size();
}
@Override
public VMFrameLayout frameLayout() {
if (platform().isa == ISA.AMD64) {
return AMD64TargetMethodUtil.frameLayout(this);
} else {
throw FatalError.unimplemented();
}
}
/**
* @return the size (in bytes) of a reference map covering an activation frame for this target method.
*/
public int frameRefMapSize() {
return ByteArrayBitMap.computeBitMapSize(frameWords());
}
/**
* @return the number of bytes in {@link #refMaps} corresponding to one safepoint.
*/
public int totalRefMapSize() {
return regRefMapSize() + frameRefMapSize();
}
public DebugInfo debugInfo() {
return debugInfo;
}
private static int totalHandlersSize;
private void initExceptionTable(CiTargetMethod ciTargetMethod) {
if (ciTargetMethod.exceptionHandlers.size() > 0) {
totalHandlersSize += ciTargetMethod.exceptionHandlers.size();
exceptionPositionsToCatchPositions = new int[ciTargetMethod.exceptionHandlers.size() * 2];
exceptionClassActors = new ClassActor[ciTargetMethod.exceptionHandlers.size()];
exceptionHandlerBCIs = new int[ciTargetMethod.exceptionHandlers.size()];
int z = 0;
for (ExceptionHandler handler : ciTargetMethod.exceptionHandlers) {
exceptionPositionsToCatchPositions[z * 2] = handler.pcOffset;
exceptionPositionsToCatchPositions[z * 2 + 1] = handler.handlerPos;
exceptionClassActors[z] = (handler.exceptionType == null) ? null : (ClassActor) handler.exceptionType;
exceptionHandlerBCIs[z] = handler.handlerBci;
z++;
}
}
}
@Override
public boolean isPatchableCallSite(CodePointer callSite) {
if (platform().isa == ISA.AMD64) {
return AMD64TargetMethodUtil.isPatchableCallSite(callSite);
} else {
throw FatalError.unimplemented();
}
}
@Override
public CodePointer fixupCallSite(int callOffset, CodePointer callEntryPoint) {
if (platform().isa == ISA.AMD64) {
return AMD64TargetMethodUtil.fixupCall32Site(this, callOffset, callEntryPoint);
} else {
throw FatalError.unimplemented();
}
}
@Override
public CodePointer patchCallSite(int callOffset, CodePointer callEntryPoint) {
if (platform().isa == ISA.AMD64) {
return AMD64TargetMethodUtil.mtSafePatchCallDisplacement(this, codeAt(callOffset), callEntryPoint);
} else {
throw FatalError.unimplemented();
}
}
@Override
public void redirectTo(TargetMethod tm) {
if (platform().isa == ISA.AMD64) {
AMD64TargetMethodUtil.patchWithJump(this, OPTIMIZED_ENTRY_POINT.offset(), OPTIMIZED_ENTRY_POINT.in(tm));
if (vm().compilationBroker.needsAdapters()) {
AMD64TargetMethodUtil.patchWithJump(this, BASELINE_ENTRY_POINT.offset(), BASELINE_ENTRY_POINT.in(tm));
}
FatalError.check(Stubs.isJumpToStaticTrampoline(this), "sanity check");
} else {
throw FatalError.unimplemented();
}
}
@Override
public CodePointer throwAddressToCatchAddress(CodePointer throwAddress, Throwable exception) {
return throwAddressToCatchAddress(throwAddress, exception, null);
}
@Override
public boolean catchExceptionInfo(StackFrameCursor current, Throwable throwable, CatchExceptionInfo info) {
CodePointer codePointer = throwAddressToCatchAddress(current.vmIP(), throwable, info);
if (!codePointer.isZero()) {
info.codePointer = codePointer;
return true;
} else {
return false;
}
}
private CodePointer throwAddressToCatchAddress(CodePointer throwAddress, Throwable exception, CatchExceptionInfo info) {
final int exceptionPos = throwAddress.minus(codeStart()).toInt();
int count = getExceptionHandlerCount();
for (int i = 0; i < count; i++) {
int codePos = getExceptionPosAt(i);
int catchPos = getCatchPosAt(i);
ClassActor catchType = getCatchTypeAt(i);
if (codePos == exceptionPos && checkType(exception, catchType)) {
if (info != null) {
info.bci = getHandlerBCIAt(i);
}
return codeAt(catchPos);
}
}
return CodePointer.zero();
}
private boolean checkType(Throwable exception, ClassActor catchType) {
return catchType == null || catchType.isAssignableFrom(ObjectAccess.readClassActor(exception));
}
/**
* Gets the exception code position of an entry in the exception handler table.
*
* @param i the index of the requested exception handler table entry
* @return the exception code position of element {@code i} in the exception handler table
*/
private int getExceptionPosAt(int i) {
return exceptionPositionsToCatchPositions[i * 2];
}
/**
* Gets the exception handler code position of an entry in the exception handler table.
*
* @param i the index of the requested exception handler table entry
* @return the exception handler position of element {@code i} in the exception handler table
*/
private int getCatchPosAt(int i) {
return exceptionPositionsToCatchPositions[i * 2 + 1];
}
/**
* Gets the exception type of an entry in the exception handler table.
*
* @param i the index of the requested exception handler table entry
* @return the exception type of element {@code i} in the exception handler table
*/
private ClassActor getCatchTypeAt(int i) {
return exceptionClassActors[i];
}
/**
* Gets the number of entries in the exception handler table.
*/
private int getExceptionHandlerCount() {
return exceptionClassActors == null ? 0 : exceptionClassActors.length;
}
/**
* Used in JVMTI context to get the bci for a catch address.
*/
private int getHandlerBCIAt(int i) {
return exceptionHandlerBCIs[i];
}
@HOSTED_ONLY
private void gatherInlinedMethods(Safepoint safepoint, Set<MethodActor> inlinedMethods) {
CiDebugInfo debugInfo = safepoint.debugInfo;
if (debugInfo != null) {
for (CiCodePos pos = debugInfo.codePos; pos != null; pos = pos.caller) {
inlinedMethods.add((MethodActor) pos.method);
}
}
}
@Override
@HOSTED_ONLY
public void gatherCalls(Set<MethodActor> directCalls, Set<MethodActor> virtualCalls, Set<MethodActor> interfaceCalls, Set<MethodActor> inlinedMethods) {
// first gather methods in the directCallees array
if (directCallees != null) {
for (Object o : directCallees) {
if (o instanceof MethodActor) {
directCalls.add((MethodActor) o);
}
}
}
// iterate over direct calls
for (Safepoint safepoint : bootstrappingCiTargetMethod.safepoints) {
if (safepoint instanceof Call) {
Call call = (Call) safepoint;
MethodActor callee = CallTarget.asMethodActor(call.target);
if (callee != null) {
if (call.direct) {
directCalls.add(callee);
} else {
if (callee.holder().isInterface()) {
interfaceCalls.add(callee);
} else {
virtualCalls.add(callee);
}
}
}
}
gatherInlinedMethods(safepoint, inlinedMethods);
}
}
@HOSTED_ONLY
private ClassMethodActor getClassMethodActor(CiRuntimeCall runtimeCall, RiMethod method) {
if (method != null) {
return (ClassMethodActor) method;
}
assert runtimeCall != null : "A call can either be a call to a method or a runtime call";
return MaxRuntimeCalls.getClassMethodActor(runtimeCall);
}
/**
* Prepares the reference map for this frame.
*
* @param current the current frame
* @param callee the callee frame
* @param preparer the reference map preparer
*/
@Override
public void prepareReferenceMap(StackFrameCursor current, StackFrameCursor callee, FrameReferenceMapVisitor preparer) {
CiCalleeSaveLayout csl = callee.csl();
Pointer csa = callee.csa();
TargetMethod calleeTM = callee.targetMethod();
if (calleeTM != null) {
Stub.Type st = calleeTM.stubType();
if (st == StaticTrampoline || st == VirtualTrampoline || st == InterfaceTrampoline) {
prepareTrampolineRefMap(current, callee, preparer);
} else if (calleeTM.is(TrapStub) && Trap.Number.isStackOverflow(csa)) {
// a method can never catch stack overflow for itself so there
// is no need to scan the references in the trapped method
return;
}
}
int safepointIndex = findSafepointIndex(current.vmIP());
if (safepointIndex < 0) {
// this is very bad.
throw FatalError.unexpected("could not find safepoint index");
}
int frameRefMapSize = frameRefMapSize();
if (!csa.isZero()) {
// the callee contains register state from this frame;
// use register reference maps in this method to fill in the map for the callee
Pointer slotPointer = csa;
int byteIndex = debugInfo.regRefMapStart(safepointIndex);
preparer.logPrepareReferenceMap(this, safepointIndex, slotPointer, "registers");
// Need to translate from register numbers (as stored in the reg ref maps) to frame slots.
for (int i = 0; i < regRefMapSize(); i++) {
int b = debugInfo.data[byteIndex] & 0xff;
int reg = i * 8;
while (b != 0) {
if ((b & 1) != 0) {
int offset = csl.offsetOf(reg);
if (logStackRootScanning()) {
StackReferenceMapPreparer.stackRootScanLogger.logRegisterState(csl.registers[reg]);
}
preparer.visitReferenceMapBits(callee, slotPointer.plus(offset), 1, 1);
}
reg++;
b = b >>> 1;
}
byteIndex++;
}
}
// prepare the map for this stack frame
Pointer slotPointer = current.sp();
preparer.logPrepareReferenceMap(this, safepointIndex, slotPointer, "frame");
int byteIndex = debugInfo.frameRefMapStart(safepointIndex);
for (int i = 0; i < frameRefMapSize; i++) {
preparer.visitReferenceMapBits(current, slotPointer, debugInfo.data[byteIndex] & 0xff, Bytes.WIDTH);
slotPointer = slotPointer.plusWords(Bytes.WIDTH);
byteIndex++;
}
}
/**
* Prepares the reference map for the frame of a call to a trampoline from an OPT compiled method.
*
* An opto-compiled caller may pass some arguments in registers. The trampoline is polymorphic, i.e. it does not have any
* helpful maps regarding the actual callee. It does store all potential parameter registers on its stack, though,
* and recovers them before returning. We mark those that contain references.
*
* @param current
* @param callee
* @param preparer
*/
public static void prepareTrampolineRefMap(StackFrameCursor current, StackFrameCursor callee, FrameReferenceMapVisitor preparer) {
RiRegisterConfig registerConfig = vm().registerConfigs.trampoline;
TargetMethod trampoline = callee.targetMethod();
ClassMethodActor calledMethod = null;
TargetMethod targetMethod = current.targetMethod();
CiCalleeSaveLayout csl = callee.csl();
Pointer csa = callee.csa();
FatalError.check(csl != null && !csa.isZero(), "trampoline must have callee save area");
CiRegister[] regs = registerConfig.getCallingConventionRegisters(Type.JavaCall, RegisterFlag.CPU);
// figure out what method the caller is trying to call
if (trampoline.is(StaticTrampoline)) {
int dcIndex = 0;
Safepoints safepoints = targetMethod.safepoints();
int safepointPos = targetMethod.posFor(current.vmIP());
for (int safepointIndex = safepoints.nextDirectCall(0); safepointIndex >= 0; safepointIndex = safepoints.nextDirectCall(safepointIndex + 1)) {
if (safepoints.posAt(safepointIndex) == safepointPos) {
calledMethod = (ClassMethodActor) targetMethod.directCallees()[dcIndex];
break;
}
dcIndex++;
}
if (calledMethod == null) {
// this is very bad.
throw FatalError.unexpected("could not find stop index");
}
} else {
// this is a virtual or interface call; figure out the receiver method based on the
// virtual or interface index
Object receiver = csa.plus(csl.offsetOf(regs[0])).getReference().toJava();
ClassActor classActor = ObjectAccess.readClassActor(receiver);
// The virtual dispatch trampoline stubs put the virtual dispatch index into the
// scratch register and then saves it to the stack.
int index = vm().stubs.readVirtualDispatchIndexFromTrampolineFrame(csa);
if (trampoline.is(VirtualTrampoline)) {
calledMethod = classActor.getVirtualMethodActorByVTableIndex(index);
} else {
assert trampoline.is(InterfaceTrampoline);
calledMethod = classActor.getVirtualMethodActorByIIndex(index);
}
}
int regIndex = 0;
if (!calledMethod.isStatic()) {
// set a bit for the receiver object
int offset = csl.offsetOf(regs[regIndex++]);
preparer.visitReferenceMapBits(current, csa.plus(offset), 1, 1);
}
SignatureDescriptor sig = calledMethod.descriptor();
for (int i = 0; i < sig.numberOfParameters() && regIndex < regs.length; ++i) {
TypeDescriptor arg = sig.parameterDescriptorAt(i);
CiRegister reg = regs[regIndex];
Kind kind = arg.toKind();
if (kind.isReference) {
// set a bit for this parameter
int offset = csl.offsetOf(reg);
preparer.visitReferenceMapBits(current, csa.plus(offset), 1, 1);
}
if (kind != Kind.FLOAT && kind != Kind.DOUBLE) {
// Only iterating over the integral arg registers
regIndex++;
}
}
}
/**
* Attempt to catch an exception that has been thrown with this method on the call stack.
* @param current the current stack frame
* @param callee the callee stack frame
* @param throwable the exception being thrown
*/
@Override
public void catchException(StackFrameCursor current, StackFrameCursor callee, Throwable throwable) {
CodePointer ip = current.vmIP();
Pointer sp = current.sp();
Pointer fp = current.fp();
CodePointer catchAddress = throwAddressToCatchAddress(ip, throwable);
if (!catchAddress.isZero()) {
if (StackFrameWalker.TraceStackWalk) {
Log.print("StackFrameWalk: Handler position for exception at position ");
Log.print(ip.minus(codeStart()).toInt());
Log.print(" is ");
Log.println(catchAddress.minus(codeStart()).toInt());
}
int catchPos = posFor(catchAddress);
if (invalidated() != null) {
assert current.sp().readWord(DEOPT_RETURN_ADDRESS_OFFSET) == current.ipAsPointer() : "real caller IP should have been saved in rescue slot";
Pointer returnAddress = callee.targetMethod().returnAddressPointer(callee).readWord(0).asPointer();
assert Stub.isDeoptStubEntry(returnAddress, Code.codePointerToTargetMethod(returnAddress)) :
"the return address of a method that was on the stack when marked for deoptimization should have been patched with a deopt stub";
Stub voidDeoptStub = vm().stubs.deoptStub(CiKind.Void, callee.targetMethod().is(CompilerStub));
if (deoptLogger.enabled()) {
deoptLogger.logCatchException(this, Code.codePointerToTargetMethod(returnAddress), voidDeoptStub, sp, fp);
}
catchAddress = voidDeoptStub.codeStart();
}
TargetMethod calleeMethod = callee.targetMethod();
// Reset the stack walker
current.stackFrameWalker().reset();
// Store the exception for the handler
VmThread.current().storeExceptionForHandler(throwable, this, catchPos);
if (calleeMethod != null && calleeMethod.registerRestoreEpilogueOffset() != -1) {
unwindToCalleeEpilogue(catchAddress, sp, calleeMethod);
} else {
Stubs.unwind(catchAddress.toPointer(), sp, fp);
}
throw ProgramError.unexpected("Should not reach here, unwind must jump to the exception handler!");
}
}
@NEVER_INLINE
public static void unwindToCalleeEpilogue(CodePointer catchAddress, Pointer stackPointer, TargetMethod lastJavaCallee) {
// Overwrite return address of callee with catch address
final Pointer returnAddressPointer = stackPointer.minus(Word.size());
returnAddressPointer.setWord(catchAddress.toAddress());
CodePointer epilogueAddress = lastJavaCallee.codeAt(lastJavaCallee.registerRestoreEpilogueOffset());
final Pointer calleeStackPointer = stackPointer.minus(Word.size()).minus(lastJavaCallee.frameSize());
Stubs.unwind(epilogueAddress.toPointer(), calleeStackPointer, Pointer.zero());
}
/**
* Accept a visitor for this frame.
* @param current the current stack frame
* @param visitor the visitor
* @return {@code true} if the stack walker should continue walking, {@code false} if the visitor is finished visiting
*/
@Override
@HOSTED_ONLY
public boolean acceptStackFrameVisitor(StackFrameCursor current, StackFrameVisitor visitor) {
if (platform().isa == ISA.AMD64) {
return AMD64TargetMethodUtil.acceptStackFrameVisitor(current, visitor);
}
throw FatalError.unimplemented();
}
/**
* Advances the cursor to the caller's frame.
* @param current the current frame
*/
@Override
public void advance(StackFrameCursor current) {
if (platform().isa == ISA.AMD64) {
CiCalleeSaveLayout csl = calleeSaveLayout();
Pointer csa = Pointer.zero();
if (csl != null) {
// See FrameMap
csa = current.sp().plus(frameSize() - csl.size);
}
AMD64TargetMethodUtil.advance(current, csl, csa);
} else {
throw FatalError.unimplemented();
}
}
@Override
public Pointer returnAddressPointer(StackFrameCursor frame) {
if (platform().isa == ISA.AMD64) {
return AMD64TargetMethodUtil.returnAddressPointer(frame);
} else {
throw FatalError.unimplemented();
}
}
@Override
public int forEachCodePos(CodePosClosure cpc, CodePointer ip) {
int index = findSafepointIndex(ip);
if (index < 0) {
return 0;
}
return debugInfo.forEachCodePos(cpc, index);
}
@Override
public CiDebugInfo debugInfoAt(int stopIndex, FrameAccess fa) {
return debugInfo.infoAt(stopIndex, fa, true);
}
}