/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.compilers.opt.runtimesupport;
import static org.jikesrvm.VM.NOT_REACHED;
import static org.jikesrvm.compilers.opt.ir.Operators.IG_PATCH_POINT;
import org.jikesrvm.VM;
import org.jikesrvm.architecture.ArchConstants;
import org.jikesrvm.classloader.RVMArray;
import org.jikesrvm.classloader.MemberReference;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.common.CodeArray;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.common.ExceptionTable;
import org.jikesrvm.compilers.opt.ir.IR;
import org.jikesrvm.compilers.opt.ir.InlineGuard;
import org.jikesrvm.compilers.opt.ir.Instruction;
import org.jikesrvm.compilers.opt.mir2mc.MachineCodeOffsets;
import org.jikesrvm.osr.EncodedOSRMap;
import org.jikesrvm.runtime.DynamicLink;
import org.jikesrvm.runtime.ExceptionDeliverer;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.runtime.Memory;
import org.jikesrvm.runtime.StackBrowser;
import org.jikesrvm.scheduler.RVMThread;
import org.jikesrvm.util.PrintLN;
import org.vmmagic.pragma.Interruptible;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.pragma.Unpreemptible;
import org.vmmagic.unboxed.Offset;
/**
* An implementation of CompiledMethod for the OPT compiler.
*
* <p> NOTE: OptCompiledMethod live as long as their corresponding
* compiled machine code. Therefore, they should only contain
* state that is really required to be persistent. Anything
* transitory should be stored on the IR object.
*/
@Uninterruptible
public final class OptCompiledMethod extends CompiledMethod {
public OptCompiledMethod(int id, RVMMethod m) {
super(id, m);
}
/**
* @return {@link CompiledMethod#OPT}
*/
@Override
public int getCompilerType() {
return CompiledMethod.OPT;
}
@Override
public String getCompilerName() {
return "optimizing compiler";
}
@Override
public ExceptionDeliverer getExceptionDeliverer() {
return exceptionDeliverer;
}
/**
* Find "catch" block for a machine instruction of this method.
*/
@Override
@Unpreemptible
public int findCatchBlockForInstruction(Offset instructionOffset, RVMType exceptionType) {
if (eTable == null) {
return -1;
} else {
int catchOffset = ExceptionTable.findCatchBlockForInstruction(eTable, instructionOffset, exceptionType);
dealWithPossibleRemovalOfCatchBlockByTheOptCompiler(instructionOffset,
exceptionType, catchOffset);
return catchOffset;
}
}
@Uninterruptible
private void dealWithPossibleRemovalOfCatchBlockByTheOptCompiler(
Offset instructionOffset, RVMType exceptionType, int catchOffset) {
if (OptExceptionTable.belongsToUnreachableCatchBlock(catchOffset)) {
if (VM.VerifyAssertions) {
VM.sysWriteln("Attempted to use a catch block that was determined to be unreachable" +
" by the optimizing compiler and thus removed from the IR before " +
"code was generated for it.");
VM.sysWrite("Instruction offset: ");
VM.sysWrite(instructionOffset);
VM.sysWriteln();
VM.sysWrite("Exception type: ");
VM.sysWrite(exceptionType.getDescriptor());
VM.sysWriteln();
ExceptionTable.printExceptionTableUninterruptible(eTable);
VM._assert(VM.NOT_REACHED,
"Attempted to use catch block that was removed from the code by the opt compiler!");
} else {
if (VM.TraceExceptionDelivery) {
VM.sysWriteln("Found a catch block that was determined by the optimizing" +
" compiler to be unreachable (and thus removed), ignoring it.");
}
// Nothing more to do. The unreachable catch block marker is negative which
// will cause the exception delivery code to ignore the catch block.
}
}
}
/**
* Fetch symbolic reference to a method that's called
* by one of this method's instructions.
* @param dynamicLink place to put return information
* @param instructionOffset offset of machine instruction that issued
* the call
*/
@Override
public void getDynamicLink(DynamicLink dynamicLink, Offset instructionOffset) {
int bci = _mcMap.getBytecodeIndexForMCOffset(instructionOffset);
NormalMethod realMethod = _mcMap.getMethodForMCOffset(instructionOffset);
if (bci == -1 || realMethod == null) {
VM.sysFail("Mapping to source code location not available at Dynamic Linking point\n");
}
realMethod.getDynamicLink(dynamicLink, bci);
}
@Override
@Interruptible
public boolean isWithinUninterruptibleCode(Offset instructionOffset) {
NormalMethod realMethod = _mcMap.getMethodForMCOffset(instructionOffset);
// Use an explicit null check here because this method is called from
// code for delivery of hardware exceptions. That code is unpreemptible, so
// a NullPointerException in this method would lead to a crash due to recursive
// use of hardware exception registers. It is better to crash with a reasonable
// error message when no method is found.
if (realMethod == null) {
VM.sysWrite("Failing instruction offset: ");
VM.sysWrite(instructionOffset);
VM.sysWrite(" in method ");
RVMMethod thisMethod = this.getMethod();
VM.sysWrite(thisMethod.getName());
VM.sysWrite(" with descriptor ");
VM.sysWrite(thisMethod.getDescriptor());
VM.sysWrite(" declared by class with descriptor ");
VM.sysWriteln(thisMethod.getDeclaringClass().getDescriptor());
String msg = "Couldn't find a method for given instruction offset";
if (VM.VerifyAssertions) {
VM._assert(NOT_REACHED, msg);
} else {
VM.sysFail(msg);
}
}
return realMethod.isUninterruptible();
}
/**
* Find source line number corresponding to one of this method's
* machine instructions.
*/
@Override
public int findLineNumberForInstruction(Offset instructionOffset) {
int bci = _mcMap.getBytecodeIndexForMCOffset(instructionOffset);
if (bci < 0) {
return 0;
}
return ((NormalMethod) method).getLineNumberForBCIndex(bci);
}
@Override
@Interruptible
public void set(StackBrowser browser, Offset instr) {
OptMachineCodeMap map = getMCMap();
int iei = map.getInlineEncodingForMCOffset(instr);
if (iei >= 0) {
int[] inlineEncoding = map.inlineEncoding;
int mid = OptEncodedCallSiteTree.getMethodID(iei, inlineEncoding);
browser.setInlineEncodingIndex(iei);
browser.setBytecodeIndex(map.getBytecodeIndexForMCOffset(instr));
browser.setCompiledMethod(this);
browser.setMethod(MemberReference.getMethodRef(mid).peekResolvedMethod());
if (VM.TraceStackTrace) {
VM.sysWrite("setting stack to frame (opt): ");
VM.sysWrite(browser.getMethod());
VM.sysWrite(browser.getBytecodeIndex());
VM.sysWriteln();
}
} else {
if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED);
}
}
@Override
@Interruptible
public boolean up(StackBrowser browser) {
OptMachineCodeMap map = getMCMap();
int iei = browser.getInlineEncodingIndex();
int[] ie = map.inlineEncoding;
int next = OptEncodedCallSiteTree.getParent(iei, ie);
if (next >= 0) {
int mid = OptEncodedCallSiteTree.getMethodID(next, ie);
int bci = OptEncodedCallSiteTree.getByteCodeOffset(iei, ie);
browser.setInlineEncodingIndex(next);
browser.setBytecodeIndex(bci);
browser.setMethod(MemberReference.getMethodRef(mid).peekResolvedMethod());
if (VM.TraceStackTrace) {
VM.sysWrite("up within frame stack (opt): ");
VM.sysWrite(browser.getMethod());
VM.sysWrite(browser.getBytecodeIndex());
VM.sysWriteln();
}
return true;
} else {
return false;
}
}
@Override
@Interruptible
public void printStackTrace(Offset instructionOffset, PrintLN out) {
OptMachineCodeMap map = getMCMap();
int iei = map.getInlineEncodingForMCOffset(instructionOffset);
if (iei >= 0) {
int[] inlineEncoding = map.inlineEncoding;
int bci = map.getBytecodeIndexForMCOffset(instructionOffset);
for (int j = iei; j >= 0; j = OptEncodedCallSiteTree.getParent(j, inlineEncoding)) {
int mid = OptEncodedCallSiteTree.getMethodID(j, inlineEncoding);
NormalMethod m =
(NormalMethod) MemberReference.getMethodRef(mid).peekResolvedMethod();
int lineNumber = m.getLineNumberForBCIndex(bci); // might be 0 if unavailable.
out.print("\tat ");
out.print(m.getDeclaringClass());
out.print('.');
out.print(m.getName());
out.print('(');
out.print(m.getDeclaringClass().getSourceName());
out.print(':');
out.print(lineNumber);
out.print(')');
out.println();
if (j > 0) {
bci = OptEncodedCallSiteTree.getByteCodeOffset(j, inlineEncoding);
}
}
} else {
out.print("\tat ");
out.print(method.getDeclaringClass());
out.print('.');
out.print(method.getName());
out.print('(');
out.print(method.getDeclaringClass().getSourceName());
out.print("; machine code offset: ");
out.printHex(instructionOffset.toInt());
out.print(')');
out.println();
}
}
@Override
@Interruptible
public int size() {
int size = TypeReference.ExceptionTable.peekType().asClass().getInstanceSize();
size += _mcMap.size();
if (eTable != null) size += RVMArray.IntArray.getInstanceSize(eTable.length);
if (patchMap != null) size += RVMArray.IntArray.getInstanceSize(patchMap.length);
return size;
}
//----------------//
// implementation //
//----------------//
private static final ExceptionDeliverer exceptionDeliverer;
static {
if (VM.BuildForIA32) {
exceptionDeliverer = new org.jikesrvm.compilers.opt.runtimesupport.ia32.OptExceptionDeliverer();
} else {
if (VM.VerifyAssertions) VM._assert(VM.BuildForPowerPC);
exceptionDeliverer = new org.jikesrvm.compilers.opt.runtimesupport.ppc.OptExceptionDeliverer();
}
}
private EncodedOSRMap _osrMap;
@Interruptible
public void createFinalOSRMap(IR ir) {
this._osrMap = EncodedOSRMap.makeMap(ir.MIRInfo.osrVarMap, ir.MIRInfo.mcOffsets);
}
public EncodedOSRMap getOSRMap() {
return this._osrMap;
}
//////////////////////////////////////
// Information the opt compiler needs to persistently associate
// with a particular compiled method.
/** The primary machine code maps */
private OptMachineCodeMap _mcMap;
/** The encoded exception tables (null if there are none) */
private int[] eTable;
private int[] patchMap;
/**
* unsigned offset (off the framepointer) of nonvolatile save area
* in bytes
*/
private char nonvolatileOffset;
/**
* unsigned offset (off the framepointer) of caught exception
* object in bytes
*/
private char exceptionObjectOffset;
/**
* size of the fixed portion of the stackframe
*/
private char stackFrameFixedSize;
/**
* first saved nonvolatile integer register (-1 if no nonvolatile
* GPRs)
*/
private byte firstNonvolatileGPR;
/**
* first saved nonvolatile floating point register (-1 if no
* nonvolatile FPRs)
*/
private byte firstNonvolatileFPR;
/** opt level at which the method was compiled */
private byte optLevel;
/** were the volatile registers saved? */
private boolean volatilesSaved;
/** is the current method executing with instrumentation */
private boolean instrumented;
public int getUnsignedNonVolatileOffset() {
return nonvolatileOffset;
}
public int getUnsignedExceptionOffset() {
return exceptionObjectOffset;
}
public int getFirstNonVolatileGPR() {
return firstNonvolatileGPR;
}
public int getFirstNonVolatileFPR() {
return firstNonvolatileFPR;
}
public int getOptLevel() {
return optLevel;
}
public boolean isSaveVolatile() {
return volatilesSaved;
}
public boolean isInstrumentedMethod() {
return instrumented;
}
public int getFrameFixedSize() {
return stackFrameFixedSize;
}
public void setUnsignedNonVolatileOffset(int x) {
if (VM.VerifyAssertions) VM._assert(x >= 0 && x < 0xFFFF);
nonvolatileOffset = (char) x;
}
public void setUnsignedExceptionOffset(int x) {
if (VM.VerifyAssertions) VM._assert(x >= 0 && x < 0xFFFF);
exceptionObjectOffset = (char) x;
}
public void setFirstNonVolatileGPR(int x) {
if (VM.VerifyAssertions) VM._assert(x >= -1 && x < 0x7F);
firstNonvolatileGPR = (byte) x;
}
public void setFirstNonVolatileFPR(int x) {
if (VM.VerifyAssertions) VM._assert(x >= -1 && x < 0x7F);
firstNonvolatileFPR = (byte) x;
}
public void setOptLevel(int x) {
if (VM.VerifyAssertions) VM._assert(x >= 0 && x < 0x7F);
optLevel = (byte) x;
}
public void setSaveVolatile(boolean sv) {
volatilesSaved = sv;
}
public void setInstrumentedMethod(boolean _instrumented) {
instrumented = _instrumented;
}
public void setFrameFixedSize(int x) {
if (VM.VerifyAssertions) VM._assert(x >= 0 && x < 0xFFFF);
stackFrameFixedSize = (char) x;
}
/**
* @return the number of non-volatile GPRs used by this method.
*/
public int getNumberOfNonvolatileGPRs() {
if (VM.BuildForPowerPC) {
return org.jikesrvm.ppc.RegisterConstants.NUM_GPRS - getFirstNonVolatileGPR();
} else if (VM.BuildForIA32) {
return org.jikesrvm.ia32.RegisterConstants.NUM_NONVOLATILE_GPRS - getFirstNonVolatileGPR();
} else if (VM.VerifyAssertions) {
VM._assert(VM.NOT_REACHED);
}
return -1;
}
/**
* @return the number of non-volatile FPRs used by this method.
*/
public int getNumberOfNonvolatileFPRs() {
if (VM.BuildForPowerPC) {
return org.jikesrvm.ppc.RegisterConstants.NUM_FPRS - getFirstNonVolatileFPR();
} else if (VM.BuildForIA32) {
return org.jikesrvm.ia32.RegisterConstants.NUM_NONVOLATILE_FPRS - getFirstNonVolatileFPR();
} else if (VM.VerifyAssertions) {
VM._assert(VM.NOT_REACHED);
}
return -1;
}
public void setNumberOfNonvolatileGPRs(short n) {
if (VM.BuildForPowerPC) {
setFirstNonVolatileGPR(org.jikesrvm.ppc.RegisterConstants.NUM_GPRS - n);
} else if (VM.BuildForIA32) {
setFirstNonVolatileGPR(org.jikesrvm.ia32.RegisterConstants.NUM_NONVOLATILE_GPRS - n);
} else if (VM.VerifyAssertions) {
VM._assert(VM.NOT_REACHED);
}
}
public void setNumberOfNonvolatileFPRs(short n) {
if (VM.BuildForPowerPC) {
setFirstNonVolatileFPR(org.jikesrvm.ppc.RegisterConstants.NUM_FPRS - n);
} else if (VM.BuildForIA32) {
setFirstNonVolatileFPR(org.jikesrvm.ia32.RegisterConstants.NUM_NONVOLATILE_FPRS - n);
} else if (VM.VerifyAssertions) {
VM._assert(VM.NOT_REACHED);
}
}
@Interruptible
public void printExceptionTable() {
if (eTable != null) ExceptionTable.printExceptionTable(eTable);
}
/**
* @return the machine code map for the compiled method.
*/
public OptMachineCodeMap getMCMap() {
return _mcMap;
}
/**
* Create the final machine code map for the compiled method.
* Remember the offset for the end of prologue too for debugger.
* @param ir the ir
* @param machineCodeLength the number of machine code instructions.
*/
@Interruptible
public void createFinalMCMap(IR ir, int machineCodeLength) {
_mcMap = OptMachineCodeMap.create(ir, machineCodeLength);
}
/**
* Create the final exception table from the IR for the method.
* @param ir the ir
*/
@Interruptible
public void createFinalExceptionTable(IR ir) {
if (ir.hasReachableExceptionHandlers()) {
eTable = OptExceptionTable.encode(ir);
}
}
/**
* Create the code patching maps from the IR for the method
* @param ir the ir
*/
@Interruptible
public void createCodePatchMaps(IR ir) {
// (1) count the patch points
int patchPoints = 0;
for (Instruction s = ir.firstInstructionInCodeOrder(); s != null; s = s.nextInstructionInCodeOrder()) {
if (s.operator() == IG_PATCH_POINT) {
patchPoints++;
}
}
// (2) if we have patch points, create the map.
if (patchPoints != 0) {
patchMap = new int[patchPoints * 2];
MachineCodeOffsets mcOffsets = ir.MIRInfo.mcOffsets;
int idx = 0;
for (Instruction s = ir.firstInstructionInCodeOrder(); s != null; s = s.nextInstructionInCodeOrder()) {
if (s.operator() == IG_PATCH_POINT) {
int patchPoint = mcOffsets.getMachineCodeOffset(s);
int newTarget = mcOffsets.getMachineCodeOffset(InlineGuard.getTarget(s).target);
// A patch map is the offset of the last byte of the patch point
// and the new branch immediate to lay down if the code is ever patched.
if (VM.BuildForIA32) {
patchMap[idx++] = patchPoint - 1;
patchMap[idx++] = newTarget - patchPoint;
} else if (VM.BuildForPowerPC) {
/* since currently we use only one NOP scheme, the offset
* is adjusted for one word
*/
patchMap[idx++] = (patchPoint >> ArchConstants.getLogInstructionWidth()) - 1;
patchMap[idx++] =
(newTarget - patchPoint + (1 << ArchConstants.getLogInstructionWidth()));
} else if (VM.VerifyAssertions) {
VM._assert(VM.NOT_REACHED);
}
}
}
}
}
/**
* Applies the code patches to the INSTRUCTION array of cm.
*
* @param cm the method which will be patched
*/
@Interruptible
public void applyCodePatches(CompiledMethod cm) {
if (patchMap != null) {
for (int idx = 0; idx < patchMap.length; idx += 2) {
CodeArray code = cm.codeArrayForOffset(Offset.fromIntZeroExtend(patchMap[idx]));
if (VM.BuildForIA32) {
org.jikesrvm.compilers.common.assembler.ia32.Assembler.patchCode(code, patchMap[idx], patchMap[idx + 1]);
} else if (VM.BuildForPowerPC) {
org.jikesrvm.compilers.opt.mir2mc.ppc.AssemblerOpt.patchCode(code, patchMap[idx], patchMap[idx + 1]);
} else if (VM.VerifyAssertions) {
VM._assert(VM.NOT_REACHED);
}
}
if (VM.BuildForPowerPC) {
// we need synchronization on PPC to handle the weak memory model
// and its icache/dcache synchronization requirements.
// Before the class loading finishes, other processors must get
// synchronized.
boolean DEBUG_CODE_PATCH = false;
// let other processors see changes.
Magic.sync();
// All other processors now will see the patched code in their data cache.
// We now need to force everyone's instruction caches to be in synch with their
// data caches. Some of the work of this call is redundant (since we already have
// forced the data caches to be in synch), but we need the icbi instructions
// to invalidate the instruction caches.
Memory.sync(Magic.objectAsAddress(instructions),
instructions.length() << ArchConstants.getLogInstructionWidth());
// Force all other threads to execute isync at the next thread switch point
// so that the icbi instructions take effect. Another effect is that
// prefetched instructions are discarded.
// Note: it would be sufficient to execute isync once for each
// physical processor.
RVMThread.softHandshake(codePatchSyncRequestVisitor);
if (DEBUG_CODE_PATCH) {
VM.sysWriteln("all processors got synchronized!");
}
}
}
}
private static RVMThread.SoftHandshakeVisitor codePatchSyncRequestVisitor =
new CodePatchSyncRequestVisitor();
}