/*
* 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;
import static org.jikesrvm.classloader.BytecodeConstants.JBC_invokeinterface;
import static org.jikesrvm.classloader.BytecodeConstants.JBC_invokespecial;
import static org.jikesrvm.classloader.BytecodeConstants.JBC_invokestatic;
import static org.jikesrvm.classloader.BytecodeConstants.JBC_invokevirtual;
import static org.jikesrvm.osr.OSRConstants.CLEANREFS;
import static org.jikesrvm.osr.OSRConstants.DOUBLE;
import static org.jikesrvm.osr.OSRConstants.FLOAT;
import static org.jikesrvm.osr.OSRConstants.GETREFAT;
import static org.jikesrvm.osr.OSRConstants.INT;
import static org.jikesrvm.osr.OSRConstants.LONG;
import static org.jikesrvm.osr.OSRConstants.REF;
import static org.jikesrvm.osr.OSRConstants.RET_ADDR;
import static org.jikesrvm.osr.OSRConstants.WORD;
import java.util.LinkedList;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.BytecodeStream;
import org.jikesrvm.classloader.NormalMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.jikesrvm.osr.bytecodes.AConstNull;
import org.jikesrvm.osr.bytecodes.DoubleStore;
import org.jikesrvm.osr.bytecodes.FloatStore;
import org.jikesrvm.osr.bytecodes.Goto;
import org.jikesrvm.osr.bytecodes.IntStore;
import org.jikesrvm.osr.bytecodes.InvokeCompiledMethod;
import org.jikesrvm.osr.bytecodes.InvokeStatic;
import org.jikesrvm.osr.bytecodes.LoadDoubleConst;
import org.jikesrvm.osr.bytecodes.LoadFloatConst;
import org.jikesrvm.osr.bytecodes.LoadIntConst;
import org.jikesrvm.osr.bytecodes.LoadLongConst;
import org.jikesrvm.osr.bytecodes.LoadRetAddrConst;
import org.jikesrvm.osr.bytecodes.LoadWordConst;
import org.jikesrvm.osr.bytecodes.LongStore;
import org.jikesrvm.osr.bytecodes.Nop;
import org.jikesrvm.osr.bytecodes.ParamInitEnd;
import org.jikesrvm.osr.bytecodes.Pop;
import org.jikesrvm.osr.bytecodes.PseudoBytecode;
import org.jikesrvm.osr.bytecodes.RefStore;
import org.jikesrvm.scheduler.RVMThread;
import org.vmmagic.unboxed.Offset;
public class ExecutionState {
/** the caller's state if this method is an inlinee */
public ExecutionState callerState = null;
/** callee's compiled method id */
public int callee_cmid = -1;
/** the method of which the execution state belongs to */
public NormalMethod meth;
/** the program pointer (bytecode index) */
public int bcIndex;
/**
* runtime values of locals and stack expressions at the bytecode index Each
* element is an object of VariableElement.
*/
public LinkedList<VariableElement> varElms;
/** the thread on which the activation is running */
public RVMThread thread;
/** the offset of frame pointer of the activation. */
public Offset fpOffset;
/** the callee (threadSwitch)'s frame pointer of this activation. */
public Offset tsFPOffset;
/**
* the compiled method id of the activation (a Java method may have multiple
* version of compiled method
*/
public int cmid;
public ExecutionState(RVMThread whichThread, Offset framePointerOffset, int compiledMethodID, int pc,
Offset tsFPOffset) {
this.thread = whichThread;
this.fpOffset = framePointerOffset;
this.cmid = compiledMethodID;
this.bcIndex = pc;
this.tsFPOffset = tsFPOffset;
this.varElms = new LinkedList<VariableElement>();
this.meth = (NormalMethod) CompiledMethods.getCompiledMethod(cmid).getMethod();
}
/////////////////////////////
// instance methods for construction
////////////////////////////
public void add(VariableElement elm) {
this.varElms.add(elm);
}
public void addFirst(VariableElement elm) {
this.varElms.addFirst(elm);
}
public RVMThread getThread() {
return this.thread;
}
public Offset getFPOffset() {
return this.fpOffset;
}
public void setMethod(NormalMethod m) {
this.meth = m;
}
public NormalMethod getMethod() {
return this.meth;
}
public Offset getTSFPOffset() {
return this.tsFPOffset;
}
/** print the current state for debugging */
public void printState() {
VM.sysWriteln("Execution state of " + meth);
VM.sysWriteln(" thread index : ", thread.getThreadSlot());
VM.sysWriteln(" FP offset : ", fpOffset);
VM.sysWriteln(" cmid : ", cmid);
VM.sysWriteln(" bcIndex : ", bcIndex);
for (VariableElement var : varElms) {
VM.sysWriteln(" " + var);
}
}
//////////////////////////////////////
// interface to recompilation
/////////////////////////////////////
private Object[] objs;
private int objnum;
private int rid;
/**
* Goes through variable elements and produces specialized
* prologue using pseudo-bytecode.
*
* @return the specialized prologue
*/
public byte[] generatePrologue() {
int size = varElms.size();
this.objs = new Object[size];
this.objnum = 0;
this.rid = ObjectHolder.handinRefs(this.objs);
PseudoBytecode head = new Nop();
PseudoBytecode tail = head;
int elmcount = 0;
// restore parameters first;
// restore "this"
if (!this.meth.isStatic()) {
VariableElement var = varElms.get(elmcount);
tail = processElement(var, tail, elmcount);
elmcount++;
if (VM.VerifyAssertions) {
VM._assert(var.isLocal() && (var.getNumber() == 0));
}
}
// restore other parameters,
int paranum = this.meth.getParameterTypes().length;
for (int i = 0; i < paranum; i++) {
VariableElement var = varElms.get(elmcount);
tail = processElement(var, tail, elmcount);
elmcount++;
if (VM.VerifyAssertions) {
VM._assert(var.isLocal());
// the number may not match because of long and double type
}
}
// ok, ready to indicate param initialized, thread switch
// and stack overflow check happens here
tail.next = new ParamInitEnd();
tail = tail.next;
// restore other locals and stack slots, assuming stack element
// were sorted
for (; elmcount < size; elmcount++) {
VariableElement var = varElms.get(elmcount);
tail = processElement(var, tail, elmcount);
}// end of for loop
if (this.objnum != 0) {
tail.next = new LoadIntConst(this.rid);
tail = tail.next;
tail.next = new InvokeStatic(CLEANREFS);
tail = tail.next;
} else {
ObjectHolder.cleanRefs(this.rid);
}
// default situation
int branchTarget = this.bcIndex;
/* when this method must start with a call of callee,
* we are using invokeCompiledMethod,
*/
if (callee_cmid != -1) {
// remember the callee's cmid, and the index of original index
tail.next = new InvokeCompiledMethod(callee_cmid, this.bcIndex);
tail = tail.next;
// if this method needs a call, than we must jump to
// the instruction after the call.
BytecodeStream bcodes = this.meth.getBytecodes();
bcodes.reset(this.bcIndex);
int code = bcodes.nextInstruction();
switch (code) {
case JBC_invokeinterface: {
branchTarget = this.bcIndex + 5;
break;
}
case JBC_invokespecial:
case JBC_invokestatic:
case JBC_invokevirtual: {
branchTarget = this.bcIndex + 3;
break;
}
default: {
if (VM.VerifyAssertions) {
String msg = "ExecutionState: unknown bytecode " + code + " at " + this.bcIndex + "@" + this.meth;
VM._assert(VM.NOT_REACHED, msg);
}
break;
}
}
}
// add goto statement, be careful, after goto
// there may be several pop instructions
int pops = computeStackHeight(head);
branchTarget += pops; // preserve space
{
Goto togo = new Goto(branchTarget);
int osize = togo.getSize();
togo.patch(branchTarget + osize);
int nsize = togo.getSize();
if (nsize != osize) {
togo.patch(branchTarget + nsize);
}
tail.next = togo;
tail = tail.next;
}
// compute stack heights and padding pops
tail = adjustStackHeight(tail, pops);
int bsize = paddingBytecode(head);
byte[] prologue = generateBinaries(head, bsize);
// clean fields
this.objs = null;
this.objnum = 0;
return prologue;
}// end of method
private PseudoBytecode processElement(VariableElement var, PseudoBytecode tail, int i) {
switch (var.getTypeCode()) {
case INT: {
tail.next = new LoadIntConst(var.getIntBits());
tail = tail.next;
if (var.isLocal()) {
tail.next = new IntStore(var.getNumber());
tail = tail.next;
}
break;
}
case FLOAT: {
tail.next = new LoadFloatConst(var.getIntBits());
tail = tail.next;
if (var.isLocal()) {
tail.next = new FloatStore(var.getNumber());
tail = tail.next;
}
break;
}
case LONG: {
tail.next = new LoadLongConst(var.getLongBits());
tail = tail.next;
if (var.isLocal()) {
tail.next = new LongStore(var.getNumber());
tail = tail.next;
}
break;
}
case DOUBLE: {
tail.next = new LoadDoubleConst(var.getLongBits());
tail = tail.next;
if (var.isLocal()) {
tail.next = new DoubleStore(var.getNumber());
tail = tail.next;
}
break;
}
case RET_ADDR: {
tail.next = new LoadRetAddrConst(var.getIntBits());
tail = tail.next;
if (var.isLocal()) {
tail.next = new RefStore(var.getNumber());
tail = tail.next;
}
break;
}
case REF: {
this.objs[i] = var.getObject();
if (this.objs[i] != null) {
tail.next = new LoadIntConst(this.rid);
tail = tail.next;
tail.next = new LoadIntConst(i);
tail = tail.next;
// the opt compiler will adjust the type of
// return value to the real type of object
// when it sees the invoke target is GETREFAT
tail.next = new InvokeStatic(GETREFAT);
tail = tail.next;
} else {
// just give an aconst_null
tail.next = new AConstNull();
tail = tail.next;
}
if (var.isLocal()) {
tail.next = new RefStore(var.getNumber());
tail = tail.next;
}
this.objnum++;
break;
}
case WORD: {
tail.next = new LoadWordConst(var.getWord());
tail = tail.next;
if (var.isLocal()) {
tail.next = new RefStore(var.getNumber());
tail = tail.next;
}
break;
}
default:
if (VM.VerifyAssertions) {
VM._assert(VM.NOT_REACHED);
}
break;
} // end of switch
return tail;
}
private short maxStackHeight = 0;
public short getMaxStackHeight() {
return this.maxStackHeight;
}
private int computeStackHeight(PseudoBytecode head) {
/* skip the first Nop */
PseudoBytecode bcode = head.next;
short height = 0;
while (bcode != null) {
height += bcode.stackChanges();
if (height > this.maxStackHeight) {
this.maxStackHeight = height;
}
bcode = bcode.next;
}
if (VM.VerifyAssertions) VM._assert(height >= 0);
return height;
}
private static PseudoBytecode adjustStackHeight(PseudoBytecode last, int height) {
// append pop
for (int i = 0; i < height; i++) {
last.next = new Pop();
last = last.next;
}
return last;
}
/**
* Adds padding (NOP) at the beginning of pseudo bytecode
* to make the new bytecode size dividable by 4, then no branch
* target adjustment is needed in the original code.
* @param head the first pseudo bytecode (must be NOP)
* @return the new bytecode size
*/
private static int paddingBytecode(PseudoBytecode head) {
/* skip the first Nop. */
PseudoBytecode bcode = head.next;
/* count the total size of prologue code. */
int bsize = 0;
while (bcode != null) {
bsize += bcode.getSize();
bcode = bcode.next;
}
/* insert Nop at the beginning to make the code size of x4. */
int padding = 3 - (bsize + 3) & 0x03;
for (int i = 0; i < padding; i++) {
bcode = new Nop();
bcode.next = head.next;
head.next = bcode;
}
bsize += padding;
return bsize;
}
/**
* Generating binary code from pseudo code, the size and the code
* list are padded and well calculated.
*
* @param bhead the first pseude bytecode (must be a NOP)
* @param bsize the size of the bytecode
* @return generated bytecodes
*/
private static byte[] generateBinaries(PseudoBytecode bhead, int bsize) {
/* patch the LoalAddrConst instruction, and generate codes. */
byte[] codes = new byte[bsize];
/* skip the first NOP */
PseudoBytecode bcode = bhead.next;
int pos = 0;
while (bcode != null) {
int size = bcode.getSize();
if (bcode instanceof LoadRetAddrConst) {
LoadRetAddrConst laddr = (LoadRetAddrConst) bcode;
/* CAUTION: path relative offset only. */
laddr.patch(laddr.getOffset() + bsize);
}
if (VM.TraceOnStackReplacement) VM.sysWriteln(pos + " : " + bcode.toString());
System.arraycopy(bcode.getBytes(), 0, codes, pos, size);
pos += size;
bcode = bcode.next;
}
return codes;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder("Execution state " + this.bcIndex + "@" + this.meth + " " + this.thread);
for (int i = 0, n = varElms.size(); i < n; i++) {
VariableElement var = varElms.get(i);
buf.append("\n ");
buf.append(var);
}
return new String(buf);
}
}