/*
* 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.osr.ia32;
import static org.jikesrvm.VM.NOT_REACHED;
import static org.jikesrvm.compilers.opt.regalloc.ia32.PhysicalRegisterConstants.FIRST_DOUBLE;
import static org.jikesrvm.ia32.RegisterConstants.NONVOLATILE_GPRS;
import static org.jikesrvm.ia32.RegisterConstants.NUM_GPRS;
import static org.jikesrvm.ia32.RegisterConstants.NUM_VOLATILE_GPRS;
import static org.jikesrvm.ia32.RegisterConstants.VOLATILE_GPRS;
import static org.jikesrvm.ia32.StackframeLayoutConstants.BYTES_IN_STACKSLOT;
import static org.jikesrvm.ia32.StackframeLayoutConstants.INVISIBLE_METHOD_ID;
import static org.jikesrvm.ia32.StackframeLayoutConstants.STACKFRAME_METHOD_ID_OFFSET;
import static org.jikesrvm.ia32.StackframeLayoutConstants.STACKFRAME_SENTINEL_FP;
import static org.jikesrvm.osr.OSRConstants.ACONST;
import static org.jikesrvm.osr.OSRConstants.DOUBLE;
import static org.jikesrvm.osr.OSRConstants.FLOAT;
import static org.jikesrvm.osr.OSRConstants.HIGH_64BIT;
import static org.jikesrvm.osr.OSRConstants.ICONST;
import static org.jikesrvm.osr.OSRConstants.INT;
import static org.jikesrvm.osr.OSRConstants.LCONST;
import static org.jikesrvm.osr.OSRConstants.LOCAL;
import static org.jikesrvm.osr.OSRConstants.LONG;
import static org.jikesrvm.osr.OSRConstants.PHYREG;
import static org.jikesrvm.osr.OSRConstants.REF;
import static org.jikesrvm.osr.OSRConstants.RET_ADDR;
import static org.jikesrvm.osr.OSRConstants.SPILL;
import static org.jikesrvm.osr.OSRConstants.WORD;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.MemberReference;
import org.jikesrvm.classloader.MethodReference;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.jikesrvm.compilers.opt.runtimesupport.OptCompiledMethod;
import org.jikesrvm.ia32.RegisterConstants.GPR;
import org.jikesrvm.osr.EncodedOSRMap;
import org.jikesrvm.osr.ExecutionState;
import org.jikesrvm.osr.ExecutionStateExtractor;
import org.jikesrvm.osr.OSRMapIterator;
import org.jikesrvm.osr.VariableElement;
import org.jikesrvm.runtime.Magic;
import org.jikesrvm.runtime.RuntimeEntrypoints;
import org.jikesrvm.scheduler.RVMThread;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Word;
import org.vmmagic.unboxed.WordArray;
/**
* OptExecutionStateExtractor is a subclass of ExecutionStateExtractor.
* It extracts the execution state from an optimized activation.
*/
public final class OptExecutionStateExtractor extends ExecutionStateExtractor {
@Override
public ExecutionState extractState(RVMThread thread, Offset osrFPoff, Offset methFPoff, int cmid) {
/* perform machine and compiler dependent operations here
* osrFPoff is the fp offset of
* OptSaveVolatile.threadSwithFrom<...>
*
* (stack grows downward)
* foo
* |-> <-- methFPoff
* |
* | <tsfrom>
* |-- <-- osrFPoff
*
*
* The threadSwitchFrom method saves all volatiles, nonvolatiles, and
* scratch registers. All register values for 'foo' can be obtained
* from the register save area of '<tsfrom>' method.
*/
byte[] stack = thread.getStack();
// get registers for the caller ( real method )
TempRegisters registers = new TempRegisters(thread.getContextRegisters());
if (VM.VerifyAssertions) {
int foocmid = Magic.getIntAtOffset(stack, methFPoff.plus(STACKFRAME_METHOD_ID_OFFSET));
if (foocmid != cmid) {
CompiledMethod cm = CompiledMethods.getCompiledMethod(cmid);
VM.sysWriteln("unmatch method, it should be " + cm.getMethod());
CompiledMethod foo = CompiledMethods.getCompiledMethod(foocmid);
VM.sysWriteln("but now it is " + foo.getMethod());
walkOnStack(stack, osrFPoff);
}
VM._assert(foocmid == cmid);
}
OptCompiledMethod fooCM = (OptCompiledMethod) CompiledMethods.getCompiledMethod(cmid);
/* Following code get the machine code offset to the
* next instruction. All operation of the stack frame
* are kept in GC critical section.
* All code in the section should not cause any GC
* activities, and avoid lazy compilation.
*/
/* Following code is architecture dependent. In IA32, the return address
* saved in caller stack frames, so use osrFP to get the next instruction
* address of foo
*/
// get the next machine code offset of the real method
VM.disableGC();
Address osrFP = Magic.objectAsAddress(stack).plus(osrFPoff);
Address nextIP = Magic.getReturnAddressUnchecked(osrFP);
Offset ipOffset = fooCM.getInstructionOffset(nextIP);
VM.enableGC();
EncodedOSRMap fooOSRMap = fooCM.getOSRMap();
/* get register reference map from OSR map
* we are using this map to convert addresses to objects,
* thus we can operate objects out of GC section.
*/
int regmap = fooOSRMap.getRegisterMapForMCOffset(ipOffset);
{
int bufCMID = Magic.getIntAtOffset(stack, osrFPoff.plus(STACKFRAME_METHOD_ID_OFFSET));
CompiledMethod bufCM = CompiledMethods.getCompiledMethod(bufCMID);
// offset in bytes, convert it to stack words from fpIndex
// SaveVolatile can only be compiled by OPT compiler
if (VM.VerifyAssertions) VM._assert(bufCM instanceof OptCompiledMethod);
restoreValuesFromOptSaveVolatile(stack, osrFPoff, registers, regmap, bufCM);
}
// return a list of states: from caller to callee
// if the osr happens in an inlined method, the state is
// a chain of recoverd methods.
ExecutionState state =
getExecStateSequence(thread, stack, ipOffset, methFPoff, cmid, osrFPoff, registers, fooOSRMap);
// reverse callerState points, it becomes callee -> caller
ExecutionState prevState = null;
ExecutionState nextState = state;
while (nextState != null) {
// 1. current node
state = nextState;
// 1. hold the next state first
nextState = nextState.callerState;
// 2. redirect pointer
state.callerState = prevState;
// 3. move prev to current
prevState = state;
}
if (VM.TraceOnStackReplacement) {
VM.sysWriteln("OptExecState : recovered states " + thread.toString());
ExecutionState temp = state;
do {
VM.sysWriteln(temp.toString());
temp = temp.callerState;
} while (temp != null);
}
return state;
}
/* OptSaveVolatile has different stack layout from DynamicBridge
* Have to separately recover them now, but there should be unified
* later on.
*
* |----------|
* | NON |
* |Volatiles |
* | | <-- volatile offset
* |Volatiles |
* | |
* |FPR states|
* |__________| ___ FP
*/
private void restoreValuesFromOptSaveVolatile(byte[] stack, Offset osrFPoff, TempRegisters registers, int regmap,
CompiledMethod cm) {
OptCompiledMethod tsfromCM = (OptCompiledMethod) cm;
boolean saveVolatile = tsfromCM.isSaveVolatile();
if (VM.VerifyAssertions) {
VM._assert(saveVolatile);
}
WordArray gprs = registers.gprs;
// enter critical section
// precall methods potientially causing dynamic compilation
int firstNonVolatile = tsfromCM.getFirstNonVolatileGPR();
int nonVolatiles = tsfromCM.getNumberOfNonvolatileGPRs();
int nonVolatileOffset = tsfromCM.getUnsignedNonVolatileOffset() + (nonVolatiles - 1) * BYTES_IN_STACKSLOT;
VM.disableGC();
// recover nonvolatile GPRs
for (int i = firstNonVolatile + nonVolatiles - 1; i >= firstNonVolatile; i--) {
gprs.set(NONVOLATILE_GPRS[i].value(), Magic.objectAsAddress(stack).loadWord(osrFPoff.minus(nonVolatileOffset)));
nonVolatileOffset -= BYTES_IN_STACKSLOT;
}
// restore with VOLATILES yet
int volatileOffset = nonVolatileOffset;
for (int i = NUM_VOLATILE_GPRS - 1; i >= 0; i--) {
gprs.set(VOLATILE_GPRS[i].value(), Magic.objectAsAddress(stack).loadWord(osrFPoff.minus(volatileOffset)));
volatileOffset -= BYTES_IN_STACKSLOT;
}
// currently, all FPRS are volatile on intel,
// DO nothing.
// convert addresses in registers to references, starting from register 0
// powerPC starts from register 1
for (int i = 0; i < NUM_GPRS; i++) {
if (EncodedOSRMap.registerIsSet(regmap, i)) {
registers.objs[i] = Magic.addressAsObject(registers.gprs.get(i).toAddress());
}
}
VM.enableGC();
if (VM.TraceOnStackReplacement) {
for (GPR reg : GPR.values()) {
VM.sysWrite(reg.toString());
VM.sysWrite(" = ");
VM.sysWrite(registers.gprs.get(reg.value()).toAddress());
VM.sysWriteln();
}
}
}
private ExecutionState getExecStateSequence(RVMThread thread, byte[] stack, Offset ipOffset, Offset fpOffset,
int cmid, Offset tsFPOffset, TempRegisters registers,
EncodedOSRMap osrmap) {
// go through the stack frame and extract values
// In the variable value list, we keep the order as follows:
// L0, L1, ..., S0, S1, ....
/* go over osr map element, build list of VariableElement.
* assuming iterator has ordered element as
* L0, L1, ..., S0, S1, ...
*
* ThreadSwitch
* threadSwitchFromOsr
* FOO <-- fpOffset
*
* Also, all registers saved by threadSwitchFromDeopt method
* is restored in "registers", address for object is converted
* back to object references.
*
* This method should be called in non-GC critical section since
* it allocates many objects.
*/
// for 64-bit type values which have two int parts.
// this holds the high part.
int lvalue_one = 0;
int lvtype_one = 0;
// now recover execution states
OSRMapIterator iterator = osrmap.getOsrMapIteratorForMCOffset(ipOffset);
if (VM.VerifyAssertions) VM._assert(iterator != null);
ExecutionState state = new ExecutionState(thread, fpOffset, cmid, iterator.getBcIndex(), tsFPOffset);
MethodReference mref = MemberReference.getMethodRef(iterator.getMethodId());
state.setMethod((NormalMethod) mref.peekResolvedMethod());
// this is not caller, but the callee, reverse it when outside
// of this function.
state.callerState = null;
if (VM.TraceOnStackReplacement) {
VM.sysWriteln("osr map table of " + state.meth.toString());
}
while (iterator.hasMore()) {
if (iterator.getMethodId() != state.meth.getId()) {
ExecutionState newstate = new ExecutionState(thread, fpOffset, cmid, iterator.getBcIndex(), tsFPOffset);
mref = MemberReference.getMethodRef(iterator.getMethodId());
newstate.setMethod((NormalMethod) mref.peekResolvedMethod());
// this is not caller, but the callee, reverse it when outside
// of this function.
newstate.callerState = state;
state = newstate;
if (VM.TraceOnStackReplacement) {
VM.sysWriteln("osr map table of " + state.meth.toString());
}
}
// create a VariableElement for it.
boolean kind = iterator.getKind();
char num = iterator.getNumber();
byte tcode = iterator.getTypeCode();
byte vtype = iterator.getValueType();
int value = iterator.getValue();
iterator.moveToNext();
if (VM.TraceOnStackReplacement) {
VM.sysWrite((kind == LOCAL) ? "L" : "S");
VM.sysWrite((int)num);
VM.sysWrite(" , ");
if (vtype == ICONST) {
VM.sysWrite("ICONST ");
VM.sysWrite(value);
} else if (vtype == PHYREG) {
VM.sysWrite("PHYREG ");
VM.sysWrite(GPR.lookup(value).toString());
} else if (vtype == SPILL) {
VM.sysWrite("SPILL ");
VM.sysWrite(value);
}
VM.sysWriteln();
}
switch (tcode) {
case INT: {
int ibits = getIntBitsFrom(vtype, value, stack, fpOffset, registers);
state.add(new VariableElement(kind, num, tcode, ibits));
break;
}
case FLOAT: {
float fv = (float) getDoubleFrom(vtype, value, stack, fpOffset, registers);
int ibits = Magic.floatAsIntBits(fv);
state.add(new VariableElement(kind, num, tcode, ibits));
break;
}
case HIGH_64BIT: {
lvalue_one = value;
lvtype_one = vtype;
break;
}
case LONG: {
long lbits = getLongBitsFrom(lvtype_one, lvalue_one, vtype, value, stack, fpOffset, registers);
lvalue_one = 0;
lvtype_one = 0;
state.add(new VariableElement(kind, num, LONG, lbits));
break;
}
case DOUBLE: {
double dv = getDoubleFrom(vtype, value, stack, fpOffset, registers);
long lbits = Magic.doubleAsLongBits(dv);
state.add(new VariableElement(kind, num, tcode, lbits));
break;
}
// I believe I did not handle return address correctly because
// the opt compiler did inlining of JSR/RET.
// To be VERIFIED.
case RET_ADDR: {
int bcIndex = getIntBitsFrom(vtype, value, stack, fpOffset, registers);
state.add(new VariableElement(kind, num, tcode, bcIndex));
break;
}
case WORD: { //KV:TODO
if (VM.BuildFor64Addr) {
if (VM.VerifyAssertions) {
VM._assert(VM.NOT_REACHED);
} else {
VM.sysFail("Case not yet implemented for 64-bit addresssing.");
}
}
int word = getIntBitsFrom(vtype, value, stack, fpOffset, registers);
state.add(new VariableElement(kind, num, tcode, word));
break;
}
case REF: {
Object ref = getObjectFrom(vtype, value, stack, fpOffset, registers);
state.add(new VariableElement(kind, num, tcode, ref));
break;
}
default:
if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED);
break;
} // switch
} // for loop
return state;
}
private static int getIntBitsFrom(int vtype, int value, byte[] stack, Offset fpOffset, TempRegisters registers) {
// for INT_CONST type, the value is the value
if (vtype == ICONST || vtype == ACONST) {
return value;
// for physical register type, it is the register number
// because all registers are saved in threadswitch's stack
// frame, we get value from it.
} else if (vtype == PHYREG) {
return registers.gprs.get(value).toInt();
// for spilled locals, the value is the spilled position
// it is on FOO's stackframe.
// ASSUMING, spill offset is offset to FP in bytes.
} else if (vtype == SPILL) {
return Magic.getIntAtOffset(stack, fpOffset.minus(value));
} else {
if (VM.VerifyAssertions) VM._assert(NOT_REACHED);
return -1;
}
}
private static long getLongBitsFrom(int vtypeHigh, int valueHigh, int vtypeLow, int valueLow, byte[] stack, Offset fpOffset,
TempRegisters registers) {
// for LCONST type, the value is the value
if (vtypeLow == LCONST || vtypeLow == ACONST) {
if (VM.VerifyAssertions) VM._assert(vtypeHigh == vtypeLow);
return ((((long) valueHigh) << 32) | ((valueLow) & 0x0FFFFFFFFL));
} else if (VM.BuildFor32Addr) {
if (VM.VerifyAssertions) VM._assert(vtypeHigh == PHYREG || vtypeHigh == SPILL);
if (VM.VerifyAssertions) VM._assert(vtypeLow == PHYREG || vtypeLow == SPILL);
/* For physical registers, value is the register number.
* For spilled locals, the value is the spilled position on FOO's stackframe. */
long lowPart, highPart;
if (vtypeLow == PHYREG) {
lowPart = (registers.gprs.get(valueLow).toInt()) & 0x0FFFFFFFFL;
} else {
lowPart = (Magic.getIntAtOffset(stack, fpOffset.minus(valueLow))) & 0x0FFFFFFFFL;
}
if (vtypeHigh == PHYREG) {
highPart = (registers.gprs.get(valueHigh).toInt());
} else {
highPart = (Magic.getIntAtOffset(stack, fpOffset.minus(valueHigh)));
}
return (highPart << 32) | lowPart;
} else if (VM.BuildFor64Addr) {
// for physical register type, it is the register number
// because all registers are saved in threadswitch's stack
// frame, we get value from it.
if (vtypeLow == PHYREG) {
return registers.gprs.get(valueLow).toLong();
// for spilled locals, the value is the spilled position
// it is on FOO's stackframe.
// ASSUMING, spill offset is offset to FP in bytes.
} else if (vtypeLow == SPILL) {
return Magic.getLongAtOffset(stack, fpOffset.minus(valueLow));
}
}
if (VM.VerifyAssertions) VM._assert(NOT_REACHED);
return -1L;
}
private static double getDoubleFrom(int vtype, int value, byte[] stack, Offset fpOffset,
TempRegisters registers) {
if (vtype == PHYREG) {
return registers.fprs[value - FIRST_DOUBLE];
} else if (vtype == SPILL) {
long lbits = Magic.getLongAtOffset(stack, fpOffset.minus(value));
return Magic.longBitsAsDouble(lbits);
//KV:TODO: why not use getDoubleAtOffset ???
} else {
if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED);
return -1.0;
}
}
private static Object getObjectFrom(int vtype, int value, byte[] stack, Offset fpOffset,
TempRegisters registers) {
if (vtype == ICONST) { //kv:todo : to become ACONST
// the only constant object for 64bit addressing is NULL
if (VM.VerifyAssertions) VM._assert(VM.BuildFor32Addr || value == 0);
return Magic.addressAsObject(Address.fromIntSignExtend(value));
} else if (vtype == PHYREG) {
return registers.objs[value];
} else if (vtype == SPILL) {
return Magic.getObjectAtOffset(stack, fpOffset.minus(value));
} else {
VM.sysWriteln("fatal error : ( vtype = " + vtype + " )");
if (VM.VerifyAssertions) VM._assert(VM.NOT_REACHED);
return null;
}
}
@SuppressWarnings("unused")
private static void dumpStackContent(byte[] stack, Offset fpOffset) {
int cmid = Magic.getIntAtOffset(stack, fpOffset.plus(STACKFRAME_METHOD_ID_OFFSET));
OptCompiledMethod cm = (OptCompiledMethod) CompiledMethods.getCompiledMethod(cmid);
int firstNonVolatile = cm.getFirstNonVolatileGPR();
int nonVolatiles = cm.getNumberOfNonvolatileGPRs();
int nonVolatileOffset = cm.getUnsignedNonVolatileOffset() + (nonVolatiles - 1) * BYTES_IN_STACKSLOT;
VM.sysWriteln("stack of " + cm.getMethod());
VM.sysWriteln(" fp offset ", fpOffset);
VM.sysWriteln(" NV area offset ", nonVolatileOffset);
VM.sysWriteln(" first NV GPR ", firstNonVolatile);
Address aFP = Magic.objectAsAddress(stack).plus(fpOffset);
for (Address a = aFP.plus(nonVolatileOffset); a.GE(aFP); a = a.minus(BYTES_IN_STACKSLOT)) {
Word content = a.loadWord();
VM.sysWriteHex(a);
VM.sysWrite(" ");
VM.sysWrite(content);
VM.sysWriteln();
}
}
@SuppressWarnings("unused")
private static void dumpRegisterContent(WordArray gprs) {
for (GPR reg : GPR.values()) {
VM.sysWrite(reg.toString());
VM.sysWrite(" = ");
VM.sysWriteln(gprs.get(reg.value()));
}
}
/* walk on stack frame, print out methods
*/
private static void walkOnStack(byte[] stack, Offset fpOffset) {
VM.disableGC();
Address fp = Magic.objectAsAddress(stack).plus(fpOffset);
while (Magic.getCallerFramePointer(fp).NE(STACKFRAME_SENTINEL_FP)) {
int cmid = Magic.getCompiledMethodID(fp);
if (cmid == INVISIBLE_METHOD_ID) {
VM.sysWriteln(" invisible method ");
} else {
CompiledMethod cm = CompiledMethods.getCompiledMethod(cmid);
fpOffset = fp.diff(Magic.objectAsAddress(stack));
VM.enableGC();
VM.sysWriteln(cm.getMethod().toString());
VM.disableGC();
fp = Magic.objectAsAddress(stack).plus(fpOffset);
if (cm.getMethod().getDeclaringClass().hasBridgeFromNativeAnnotation()) {
fp = RuntimeEntrypoints.unwindNativeStackFrame(fp);
}
}
fp = Magic.getCallerFramePointer(fp);
}
VM.enableGC();
}
}