/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Common Public License (CPL);
* 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/cpl1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.jikesrvm.jni.ia32;
import org.jikesrvm.ArchitectureSpecific;
import org.jikesrvm.classloader.VM_Class;
import org.jikesrvm.classloader.VM_Method;
import org.jikesrvm.classloader.VM_NativeMethod;
import org.jikesrvm.classloader.VM_NormalMethod;
import org.jikesrvm.classloader.VM_TypeReference;
import org.jikesrvm.compilers.common.VM_CompiledMethod;
import org.jikesrvm.compilers.common.VM_CompiledMethods;
import org.jikesrvm.compilers.common.assembler.VM_ForwardReference;
import org.jikesrvm.compilers.common.assembler.ia32.VM_Assembler;
import org.jikesrvm.ia32.VM_BaselineConstants;
import org.jikesrvm.ia32.VM_MachineCode;
import org.jikesrvm.ia32.VM_ProcessorLocalState;
import org.jikesrvm.jni.VM_JNICompiledMethod;
import org.jikesrvm.jni.VM_JNIGlobalRefTable;
import org.jikesrvm.runtime.VM_ArchEntrypoints;
import org.jikesrvm.runtime.VM_Entrypoints;
import org.jikesrvm.scheduler.VM_Processor;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
/**
* This class compiles the prolog and epilog for all code that makes
* the transition between Java and Native C
* <pre>
* 2 cases:
* -from Java to C: all user-defined native methods
* -C to Java: all JNI functions in VM_JNIFunctions.java
* </pre>
*
* If this code is being used, then we assume RVM_WITH_SVR4_ABI is set.
*/
public abstract class VM_JNICompiler implements VM_BaselineConstants {
// offsets to saved regs and addresses in java to C glue frames
// EDI (JTOC) and EBX are nonvolatile registers in RVM
//
private static final int SAVED_GPRS = 5;
public static final Offset EDI_SAVE_OFFSET = Offset.fromIntSignExtend(STACKFRAME_BODY_OFFSET);
public static final Offset EBX_SAVE_OFFSET = EDI_SAVE_OFFSET.minus(WORDSIZE);
public static final Offset EBP_SAVE_OFFSET = EBX_SAVE_OFFSET.minus(WORDSIZE);
public static final Offset JNI_RETURN_ADDRESS_OFFSET = EBP_SAVE_OFFSET.minus(WORDSIZE);
public static final Offset JNI_ENV_OFFSET = JNI_RETURN_ADDRESS_OFFSET.minus(WORDSIZE);
// following used in prolog & epilog for JNIFunctions
// offset of saved offset to preceeding java frame
public static final Offset SAVED_JAVA_FP_OFFSET = Offset.fromIntSignExtend(STACKFRAME_BODY_OFFSET);
// following used in VM_Compiler to compute offset to first local:
// includes 5 words:
// SAVED_JAVA_FP, VM_JNIEnvironment, S0 (ECX), EBX, and JTOC (EDI)
public static final int SAVED_GPRS_FOR_JNI = 5;
/*****************************************************************
* Handle the Java to C transition: native methods
*/
public static synchronized VM_CompiledMethod compile(VM_NativeMethod method) {
VM_JNICompiledMethod cm =
(VM_JNICompiledMethod) VM_CompiledMethods.createCompiledMethod(method, VM_CompiledMethod.JNI, false);
VM_Assembler asm = new ArchitectureSpecific.VM_Assembler(100); // some size for the instruction array
Address nativeIP = method.getNativeIP();
// recompute some constants
int parameterWords = method.getParameterWords();
// Meaning of constant offset into frame:
// STACKFRAME_HEADER_SIZE = 12 (CHECK ??)
// SAVED_GPRS = 4 words/registers
// Stack frame:
// on entry after prolog
//
// high address high address
// | | | | Caller frame
// | | | |
// + |arg 0 | |arg 0 | -> firstParameterOffset
// + |arg 1 | |arg 1 |
// + |... | |... |
// +8 |arg n-1 | |arg n-1 |
// +4 |returnAddr| |returnAddr|
// 0 + + +saved FP + <---- FP for glue frame
// -4 | | |methodID |
// -8 | | |saved EDI | -> STACKFRAME_BODY_OFFSET = -8
// -C | | |saved EBX |
// -10 | | |saved EBP |
// -14 | | |returnAddr| (return from OutOfLine to generated epilog)
// -18 | | |saved ENV | (VM_JNIEnvironment)
// -1C | | |arg n-1 | reordered args to native method
// -20 | | | ... | ...
// -24 | | |arg 1 | ...
// -28 | | |arg 0 | ...
// -2C | | |class/obj | required second arg to native method
// -30 | | |jniEnv | required first arg to native method
// -34 | | | |
// | | | |
// | | | |
// low address low address
// TODO: check and resize stack once on the lowest Java to C transition
// on the stack. Not needed if we use the thread original stack
// Fill in frame header - similar to normal prolog
prepareStackHeader(asm, method, cm.getId());
// Process the arguments - specific to method being called
storeParametersForLintel(asm, method);
// load address of native code to invoke into S0
asm.emitMOV_Reg_Imm(S0, nativeIP.toInt());
// branch to outofline code in bootimage
asm.emitCALL_RegDisp(JTOC, VM_ArchEntrypoints.invokeNativeFunctionInstructionsField.getOffset());
// return here from VM_OutOfLineMachineCode upon return from native code
// PR and RVM JTOC restored, T0,T1 contain return from native call
// If the return type is reference, look up the real value in the JNIref array
// S0 <- threads' VM_JNIEnvironment
VM_ProcessorLocalState.emitMoveFieldToReg(asm, S0, VM_Entrypoints.activeThreadField.getOffset());
asm.emitMOV_Reg_RegDisp(S0, S0, VM_Entrypoints.jniEnvField.getOffset());
if (method.getReturnType().isReferenceType()) {
asm.emitCMP_Reg_Imm(T0, 0);
VM_ForwardReference globalRef = asm.forwardJcc(VM_Assembler.LT);
// Deal with local references
asm.emitADD_Reg_RegDisp(T0,
S0,
VM_Entrypoints.JNIRefsField.getOffset()); // T0 <- address of entry (not index)
asm.emitMOV_Reg_RegInd(T0, T0); // get the reference
VM_ForwardReference afterGlobalRef = asm.forwardJMP();
// Deal with global references
globalRef.resolve(asm);
asm.emitMOV_Reg_Reg(T1, T0);
asm.emitTEST_Reg_Imm(T1, VM_JNIGlobalRefTable.STRONG_REF_BIT);
asm.emitMOV_Reg_RegDisp(T1, JTOC, VM_Entrypoints.JNIGlobalRefsField.getOffset());
VM_ForwardReference weakGlobalRef = asm.forwardJcc(VM_Assembler.EQ);
// Strong global references
asm.emitNEG_Reg(T0);
asm.emitMOV_Reg_RegIdx(T0, T1, T0, VM_Assembler.WORD, Offset.zero());
VM_ForwardReference afterWeakGlobalRef = asm.forwardJMP();
// Weak global references
weakGlobalRef.resolve(asm);
asm.emitOR_Reg_Imm(T0, VM_JNIGlobalRefTable.STRONG_REF_BIT);
asm.emitNEG_Reg(T0);
asm.emitMOV_Reg_RegIdx(T0, T1, T0, VM_Assembler.WORD, Offset.zero());
asm.emitMOV_Reg_RegDisp(T0, T0, VM_Entrypoints.referenceReferentField.getOffset());
afterWeakGlobalRef.resolve(asm);
afterGlobalRef.resolve(asm);
} else if (method.getReturnType().isLongType()) {
asm.emitPUSH_Reg(T1); // need to use T1 in popJNIrefForEpilog and to swap order T0-T1
}
// pop frame in JNIRefs array (assumes S0 holds VM_JNIEnvironment)
popJNIrefForEpilog(asm);
// then swap order of T0 and T1 for long
if (method.getReturnType().isLongType()) {
asm.emitMOV_Reg_Reg(T1, T0);
asm.emitPOP_Reg(T0);
}
// CHECK EXCEPTION AND BRANCH TO ATHROW CODE OR RETURN NORMALLY
// get pending exception from JNIEnv
asm.emitMOV_Reg_RegDisp(EBX,
S0,
VM_Entrypoints.JNIPendingExceptionField.getOffset()); // EBX <- JNIPendingException
asm.emitMOV_RegDisp_Imm(S0,
VM_Entrypoints.JNIPendingExceptionField.getOffset(),
0); // clear the current pending exception
asm.emitCMP_Reg_Imm(EBX, 0); // check for exception pending: JNIPendingException = non zero
VM_ForwardReference fr = asm.forwardJcc(VM_Assembler.EQ); // Br if yes
// if pending exception, discard the return value and current stack frame
// then jump to athrow
asm.emitMOV_Reg_Reg(T0, EBX);
asm.emitMOV_Reg_RegDisp(T1,
JTOC,
VM_Entrypoints.athrowMethod.getOffset()); // acquire jump addr before restoring nonvolatiles
asm.emitMOV_Reg_Reg(SP, EBP); // discard current stack frame
asm.emitMOV_Reg_RegDisp(JTOC, SP, EDI_SAVE_OFFSET); // restore nonvolatile EDI register
asm.emitMOV_Reg_RegDisp(EBX, SP, EBX_SAVE_OFFSET); // restore nonvolatile EBX register
asm.emitMOV_Reg_RegDisp(EBP, SP, EBP_SAVE_OFFSET); // restore nonvolatile EBP register
asm.emitPOP_RegDisp(PR, VM_ArchEntrypoints.framePointerField.getOffset());
// don't use CALL since it will push on the stack frame the return address to here
asm.emitJMP_Reg(T1); // jumps to VM_Runtime.athrow
fr.resolve(asm); // branch to here if no exception
// no exception, proceed to return to caller
asm.emitMOV_Reg_Reg(SP, EBP); // discard current stack frame
asm.emitMOV_Reg_RegDisp(JTOC, SP, EDI_SAVE_OFFSET); // restore nonvolatile EDI register
asm.emitMOV_Reg_RegDisp(EBX, SP, EBX_SAVE_OFFSET); // restore nonvolatile EBX register
asm.emitMOV_Reg_RegDisp(EBP, SP, EBP_SAVE_OFFSET); // restore nonvolatile EBP register
asm.emitPOP_RegDisp(PR, VM_ArchEntrypoints.framePointerField.getOffset());
if (SSE2_FULL) {
// Marshall from FP0 to XMM0
if (method.getReturnType().isFloatType()) {
asm.emitFSTP_RegDisp_Reg(PR, VM_Entrypoints.scratchStorageField.getOffset(), FP0);
asm.emitMOVSS_Reg_RegDisp(XMM0, PR, VM_Entrypoints.scratchStorageField.getOffset());
} else if (method.getReturnType().isDoubleType()) {
asm.emitFSTP_RegDisp_Reg_Quad(PR, VM_Entrypoints.scratchStorageField.getOffset(), FP0);
asm.emitMOVSD_Reg_RegDisp(XMM0, PR, VM_Entrypoints.scratchStorageField.getOffset());
}
}
// return to caller
// pop parameters from stack (Note that parameterWords does not include "this")
if (method.isStatic()) {
asm.emitRET_Imm(parameterWords << LG_WORDSIZE);
} else {
asm.emitRET_Imm((parameterWords + 1) << LG_WORDSIZE);
}
VM_MachineCode machineCode = new ArchitectureSpecific.VM_MachineCode(asm.getMachineCodes(), null);
cm.compileComplete(machineCode.getInstructions());
return cm;
}
/**************************************************************
* Prepare the stack header for Java to C transition.
* <pre>
* before after
* high address high address
* | | | | Caller frame
* | | | |
* + |arg 0 | |arg 0 |
* + |arg 1 | |arg 1 |
* + |... | |... |
* +8 |arg n-1 | |arg n-1 |
* +4 |returnAddr| |returnAddr|
* 0 + + +saved FP + <---- FP for glue frame
* -4 | | |methodID |
* -8 | | |saved EDI | (EDI == JTOC - for baseline methods)
* -C | | |saved EBX |
* -10 | | | |
*
* </pre>
*/
static void prepareStackHeader(VM_Assembler asm, VM_Method method, int compiledMethodId) {
// set 2nd word of header = return address already pushed by CALL
asm.emitPUSH_RegDisp(PR, VM_ArchEntrypoints.framePointerField.getOffset());
// start new frame: set FP to point to the new frame
VM_ProcessorLocalState.emitMoveRegToField(asm, VM_ArchEntrypoints.framePointerField.getOffset(), SP);
// set first word of header: method ID
asm.emitMOV_RegDisp_Imm(SP, Offset.fromIntSignExtend(STACKFRAME_METHOD_ID_OFFSET), compiledMethodId);
// save nonvolatile registrs: EDI, EBX, EBP
asm.emitMOV_RegDisp_Reg(SP, EDI_SAVE_OFFSET, JTOC);
asm.emitMOV_RegDisp_Reg(SP, EBX_SAVE_OFFSET, EBX);
asm.emitMOV_RegDisp_Reg(SP, EBP_SAVE_OFFSET, EBP);
asm.emitMOV_Reg_Reg(EBP, SP); // Establish EBP as the framepointer for use in the rest of the glue frame
// restore JTOC with the value saved in VM_Processor.jtoc for use in prolog
VM_ProcessorLocalState.emitMoveFieldToReg(asm, JTOC, VM_ArchEntrypoints.jtocField.getOffset());
}
/**************************************************************
* Process the arguments.
*
* <pre>
* -insert the 2 JNI args
* -replace pointers
* -reverse the order of the args from Java to fit the C convention
* -
*
* before after
*
* high address high address
* | | | | Caller frame
* | | | |
* + |arg 0 | |arg 0 | -> firstParameterOffset
* + |arg 1 | |arg 1 |
* + |... | |... |
* +8 |arg n-1 | |arg n-1 |
* +4 |returnAddr| |returnAddr|
* 0 +saved FP + +saved FP + <---- FP for glue frame
* -4 |methodID | |methodID |
* -8 |saved EDI | |saved EDI | -> STACKFRAME_BODY_OFFSET = -8
* -C |saved EBX | |saved EBX |
* | | |align pad |
* -10 | | |returnAddr| (return from OutOfLine to generated epilog)
* -14 | | |saved PR |
* -18 | | |arg n-1 | reordered args to native method (firstLocalOffset
* -1C | | | ... | ...
* -20 | | |arg 1 | ...
* -24 | | |arg 0 | ...
* -28 | | |class/obj | required second arg
* -2C | | SP -> |jniEnv | required first arg (emptyStackOffset)
* -30 | | | |
* | | | |
* low address low address
* </pre>
*/
static void storeParametersForLintel(VM_Assembler asm, VM_Method method) {
VM_Class klass = method.getDeclaringClass();
int parameterWords = method.getParameterWords();
int savedRegistersSize = SAVED_GPRS << LG_WORDSIZE;
int firstLocalOffset = STACKFRAME_BODY_OFFSET - savedRegistersSize;
Offset emptyStackOffset =
Offset.fromIntSignExtend(firstLocalOffset - ((parameterWords + 2) << LG_WORDSIZE) + WORDSIZE);
Offset firstParameterOffset =
Offset.fromIntSignExtend(STACKFRAME_BODY_OFFSET + STACKFRAME_HEADER_SIZE + (parameterWords << LG_WORDSIZE));
VM_TypeReference[] types = method.getParameterTypes(); // does NOT include implicit this or class ptr
int numArguments = types.length; // number of arguments for this method
int numRefArguments = 1; // initialize with count of 1 for the JNI arg
int numFloats = 0; // number of float or double arguments
// quick count of number of references
for (int i = 0; i < numArguments; i++) {
if (types[i].isReferenceType()) {
numRefArguments++;
}
if (types[i].isFloatType() || types[i].isDoubleType()) {
numFloats++;
}
}
// first push the parameters passed in registers back onto the caller frame
// to free up the registers for use
// The number of registers holding parameter is
// VM_RegisterConstants.NUM_PARAMETER_GPRS
// Their indices are in VM_RegisterConstants.VOLATILE_GPRS[]
int gpr = 0;
// note that firstParameterOffset does not include "this"
Offset parameterOffset = firstParameterOffset;
// handle the "this" parameter
if (!method.isStatic()) {
asm.emitMOV_RegDisp_Reg(EBP, firstParameterOffset.plus(WORDSIZE), VOLATILE_GPRS[gpr]);
gpr++;
}
for (int i = 0; i < numArguments && gpr < NUM_PARAMETER_GPRS; i++) {
if (types[i].isDoubleType()) {
parameterOffset = parameterOffset.minus(2 * WORDSIZE);
} else if (types[i].isFloatType()) {
parameterOffset = parameterOffset.minus(WORDSIZE);
} else if (types[i].isLongType()) {
if (gpr < NUM_PARAMETER_GPRS) { // get the hi word
asm.emitMOV_RegDisp_Reg(EBP, parameterOffset, VOLATILE_GPRS[gpr]);
gpr++;
parameterOffset = parameterOffset.minus(WORDSIZE);
}
if (gpr < NUM_PARAMETER_GPRS) { // get the lo word
asm.emitMOV_RegDisp_Reg(EBP, parameterOffset, VOLATILE_GPRS[gpr]);
gpr++;
parameterOffset = parameterOffset.minus(WORDSIZE);
}
} else {
if (gpr < NUM_PARAMETER_GPRS) { // all other types fit in one word
asm.emitMOV_RegDisp_Reg(EBP, parameterOffset, VOLATILE_GPRS[gpr]);
gpr++;
parameterOffset = parameterOffset.minus(WORDSIZE);
}
}
}
// bump SP to set aside room for the args + 2 additional JNI args
asm.emitADD_Reg_Imm(SP, emptyStackOffset.toInt());
// SP should now point to the bottom of the argument stack,
// which is arg[n-1]
// Prepare the side stack to hold new refs
// Leave S0 holding the threads' VM_JNIEnvironment
VM_ProcessorLocalState.emitMoveFieldToReg(asm, S0, VM_Entrypoints.activeThreadField.getOffset());
asm.emitMOV_Reg_RegDisp(S0, S0, VM_Entrypoints.jniEnvField.getOffset()); // S0 <- jniEnv
// save PR in the jniEnv for JNI call from native
VM_ProcessorLocalState.emitStoreProcessor(asm, S0, VM_Entrypoints.JNIEnvSavedPRField.getOffset());
// save VM_JNIEnvironemt in stack frame so we can find it when we return
asm.emitMOV_RegDisp_Reg(EBP, JNI_ENV_OFFSET, S0);
// save FP for glue frame in JNI env - used by GC when in C
asm.emitMOV_RegDisp_Reg(S0, VM_Entrypoints.JNITopJavaFPField.getOffset(), EBP); // jniEnv.JNITopJavaFP <- FP
//********************************************
// Between HERE and THERE, S0 and T0 are in use
// S0 holds the VM_JNIEnvironemnt for the thread.
// T0 holds the address to TOP of JNIRefs stack
// (set up by startJNIrefForProlog, used by pushJNIRef)
// >>>> HERE <<<<
startJNIrefForProlog(asm, numRefArguments);
// Insert the JNIEnv* arg at the first entry:
// This is an interior pointer to VM_JNIEnvironment, which is held in S0.
asm.emitMOV_Reg_Reg(EBX, S0);
asm.emitADD_Reg_Imm(EBX, VM_Entrypoints.JNIExternalFunctionsField.getOffset().toInt());
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset, EBX); // store as 1st arg
// Insert the JNI arg at the second entry: class or object as a jref index
// first reload JTOC, baseline compiler assumes JTOC register -> jtoc
// TODO: DAVE: doesn't it already have the JTOC????
VM_ProcessorLocalState.emitMoveFieldToReg(asm, JTOC, VM_ArchEntrypoints.jtocField.getOffset());
if (method.isStatic()) {
// For static method, push on arg stack the VM_Class object
// jtoc[tibOffset] -> class TIB ptr -> first TIB entry -> class object -> classForType
klass.getClassForType(); // ensure the Java class object is created
Offset tibOffset = klass.getTibOffset();
asm.emitMOV_Reg_RegDisp(EBX, JTOC, tibOffset);
asm.emitMOV_Reg_RegInd(EBX, EBX);
asm.emitMOV_Reg_RegDisp(EBX, EBX, VM_Entrypoints.classForTypeField.getOffset());
} else {
// For nonstatic method, "this" pointer should be the first arg in the caller frame,
// make it the 2nd arg in the glue frame
asm.emitMOV_Reg_RegDisp(EBX, EBP, firstParameterOffset.plus(WORDSIZE));
}
// Generate the code to push this pointer in ebx on to the JNIRefs stack
// and use the JREF index in its place
// Assume: S0 is the jniEnv pointer (left over from above)
// T0 contains the address to TOP of JNIRefs stack
// Kill value in ebx
// On return, ebx contains the JREF index
pushJNIref(asm);
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE), EBX); // store as 2nd arg
// Now fill in the rest: copy parameters from caller frame into glue frame
// in reverse order for C
int i = parameterWords - 1;
int fpr = numFloats - 1;
for (int argIndex = numArguments - 1; argIndex >= 0; argIndex--) {
// for reference, substitute with a jref index
if (types[argIndex].isReferenceType()) {
asm.emitMOV_Reg_RegDisp(EBX, EBP, firstParameterOffset.minus(i * WORDSIZE));
asm.emitCMP_Reg_Imm(EBX, 0);
VM_ForwardReference beq = asm.forwardJcc(VM_Assembler.EQ);
pushJNIref(asm);
beq.resolve(asm);
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i)), EBX);
i--;
// for float and double, the first NUM_PARAMETER_FPRS args have
// been loaded in the FPU stack, need to pop them from there
} else if (types[argIndex].isDoubleType()) {
if (fpr < NUM_PARAMETER_FPRS) {
// pop this 2-word arg from the FPU stack
if (SSE2_FULL) {
asm.emitMOVSD_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i - 1)), (byte)fpr);
} else {
asm.emitFSTP_RegDisp_Reg_Quad(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i - 1)), FP0);
}
} else {
// copy this 2-word arg from the caller frame
asm.emitMOV_Reg_RegDisp(EBX, EBP, firstParameterOffset.minus(i * WORDSIZE));
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i - 1)), EBX);
asm.emitMOV_Reg_RegDisp(EBX, EBP, firstParameterOffset.minus((i - 1) * WORDSIZE));
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i)), EBX);
}
i -= 2;
fpr--;
} else if (types[argIndex].isFloatType()) {
if (fpr < NUM_PARAMETER_FPRS) {
// pop this 1-word arg from the FPU stack
if (SSE2_FULL) {
asm.emitMOVSS_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i)), (byte)fpr);
} else {
asm.emitFSTP_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i)), FP0);
}
} else {
// copy this 1-word arg from the caller frame
asm.emitMOV_Reg_RegDisp(EBX, EBP, firstParameterOffset.minus(i * WORDSIZE));
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i)), EBX);
}
i--;
fpr--;
} else if (types[argIndex].isLongType()) {
// copy other 2-word parameters: observe the high/low order when moving
asm.emitMOV_Reg_RegDisp(EBX, EBP, firstParameterOffset.minus(i * WORDSIZE));
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i - 1)), EBX);
asm.emitMOV_Reg_RegDisp(EBX, EBP, firstParameterOffset.minus((i - 1) * WORDSIZE));
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i)), EBX);
i -= 2;
} else {
// copy other 1-word parameters
asm.emitMOV_Reg_RegDisp(EBX, EBP, firstParameterOffset.minus(i * WORDSIZE));
asm.emitMOV_RegDisp_Reg(EBP, emptyStackOffset.plus(WORDSIZE * (2 + i)), EBX);
i--;
}
}
// Now set the top offset based on how many values we actually pushed.
asm.emitSUB_Reg_RegDisp(T0, S0, VM_Entrypoints.JNIRefsField.getOffset());
asm.emitMOV_RegDisp_Reg(S0, VM_Entrypoints.JNIRefsTopField.getOffset(), T0);
// >>>> THERE <<<<
// End use of T0 and S0
}
/**************************************************************
* Generate code to convert a pointer value to a JREF index.
*
* <pre>
* This includes the following steps:
* (1) start by calling startJNIrefForProlog()
* (2) for each reference, put it in ebx and call pushJNIref()
* to convert; the handler will be left in ebx
*
* +-------+
* | | <-JNIRefsMax (byte index of last entry)
* | |
* | |
* | | <-JNIRefsTop (byte index of valid top entry)
* | |
* | |
* |FPindex| <-JNIRefsSavedFP (byte index of Java to C transition)
* | |
* | |
* | |
* | |
* | |
* | |
* | | <-JNIRefs
* +-------+
* </pre>
*/
/**
* Start a new frame for this Java to C transition.
* <pre>
* Expect:
* -S0 contains a pointer to the VM_Thread.jniEnv
* Perform these steps:
* -push current SavedFP index
* -set SaveFP index <- current TOP
* Leave registers ready for more push onto the jniEnv.JNIRefs array
* -S0 holds jniEnv so we can update jniEnv.JNIRefsTop and
* -T0 holds address of top = starting address of jniEnv.JNIRefs array + jniEnv.JNIRefsTop
* T0 is to be incremented before each push
* S0 ebx T0
* jniEnv
* . jniEnv.JNIRefs jniEnv.JNIRefsTop
* . . jniEnv.JNIRefsTop + 4
* . jniEnv.JNIRefsSavedFP .
* . . jniEnv.JNIRefsTop
* . . address(JNIRefsTop)
* .
* </pre>
*/
static void startJNIrefForProlog(VM_Assembler asm, int numRefsExpected) {
// on entry, S0 contains a pointer to the VM_Thread.jniEnv
asm.emitMOV_Reg_RegDisp(EBX, S0, VM_Entrypoints.JNIRefsField.getOffset()); // ebx <- JNIRefs base
// get and check index of top for overflow
asm.emitMOV_Reg_RegDisp(T0, S0, VM_Entrypoints.JNIRefsTopField.getOffset()); // T0 <- index of top
asm.emitADD_Reg_Imm(T0, numRefsExpected * WORDSIZE); // increment index of top
asm.emitCMP_Reg_RegDisp(T0,
S0,
VM_Entrypoints.JNIRefsMaxField.getOffset()); // check against JNIRefsMax for overflow
// TODO: Do something if overflow!!!
asm.emitADD_RegDisp_Imm(S0, VM_Entrypoints.JNIRefsTopField.getOffset(), WORDSIZE); // increment index of top
asm.emitMOV_Reg_RegDisp(T0, S0, VM_Entrypoints.JNIRefsTopField.getOffset()); // T0 <- index of top
asm.emitADD_Reg_Reg(T0, EBX); // T0 <- address of top (not index)
// start new frame: push current JNIRefsSavedFP onto stack and set it to the new top index
asm.emitMOV_Reg_RegDisp(EBX, S0, VM_Entrypoints.JNIRefsSavedFPField.getOffset()); // ebx <- jniEnv.JNIRefsSavedFP
asm.emitMOV_RegInd_Reg(T0, EBX); // push (T0) <- ebx
asm.emitMOV_Reg_RegDisp(T0, S0, VM_Entrypoints.JNIRefsTopField.getOffset()); // reload T0 <- index of top
asm.emitMOV_RegDisp_Reg(S0,
VM_Entrypoints.JNIRefsSavedFPField.getOffset(),
T0); // jniEnv.JNIRefsSavedFP <- index of top
// leave T0 with address pointing to the top of the frame for more push later
asm.emitADD_Reg_RegDisp(T0,
S0,
VM_Entrypoints.JNIRefsField.getOffset()); // recompute T0 <- address of top (not index)
}
/**
* Push a pointer value onto the JNIRefs array.
*
* <pre>
* Expect:
* -T0 pointing to the address of the valid top
* -the pointer value in register ebx
* -the space in the JNIRefs array has checked for overflow
* by startJNIrefForProlog()
* Perform these steps:
* -increment the JNIRefsTop index in ebx by 4
* -push a pointer value in ebx onto the top of the JNIRefs array
* -put the JNIRefsTop index into the sourceReg as the replacement for the pointer
* </pre>
* Note: jniEnv.JNIRefsTop is not updated yet
*/
static void pushJNIref(VM_Assembler asm) {
asm.emitADD_Reg_Imm(T0, WORDSIZE); // increment top address
asm.emitMOV_RegInd_Reg(T0, EBX); // store ref at top
asm.emitMOV_Reg_Reg(EBX, T0); // replace ref in ebx with top address
asm.emitSUB_Reg_RegDisp(EBX, S0, VM_Entrypoints.JNIRefsField.getOffset()); // subtract base address to get offset
}
/**
* Generate the code to pop the frame in JNIRefs array for this Java to C transition.
*
* <pre>
* Expect:
* -JTOC, PR registers are valid
* -S0 contains a pointer to the VM_Thread.jniEnv
* -EBX and T1 are available as scratch registers
* Perform these steps:
* -jniEnv.JNIRefsTop <- jniEnv.JNIRefsSavedFP - 4
* -jniEnv.JNIRefsSavedFP <- (jniEnv.JNIRefs + jniEnv.JNIRefsSavedFP)
* </pre>
*/
static void popJNIrefForEpilog(VM_Assembler asm) {
// on entry, S0 contains a pointer to the VM_Thread.jniEnv
// set TOP to point to entry below the last frame
asm.emitMOV_Reg_RegDisp(T1, S0, VM_Entrypoints.JNIRefsSavedFPField.getOffset()); // ebx <- JNIRefsSavedFP
asm.emitMOV_RegDisp_Reg(S0, VM_Entrypoints.JNIRefsTopField.getOffset(), T1); // JNIRefsTop <- ebx
asm.emitSUB_RegDisp_Imm(S0, VM_Entrypoints.JNIRefsTopField.getOffset(), WORDSIZE); // JNIRefsTop -= 4
// load savedFP with the index to the last frame
asm.emitMOV_Reg_RegDisp(EBX, S0, VM_Entrypoints.JNIRefsField.getOffset()); // ebx <- JNIRefs base
asm.emitMOV_Reg_RegIdx(EBX,
EBX,
T1,
VM_Assembler.BYTE,
Offset.zero()); // ebx <- (JNIRefs base + SavedFP index)
asm.emitMOV_RegDisp_Reg(S0, VM_Entrypoints.JNIRefsSavedFPField.getOffset(), EBX); // JNIRefsSavedFP <- ebx
}
/*****************************************************************
* Handle the C to Java transition: JNI methods in VM_JNIFunctions.java.
*
* <pre>
* NOTE:
* -We need PR to access Java environment; we can get it from the
* JNIEnv* (which is an interior pointer to the VM_JNIEnvironment)
* -Unlike the powerPC scheme which has a special prolog preceding
* the normal Java prolog, the Intel scheme replaces the Java prolog
* completely with the special prolog
*
* Stack on entry Stack at end of prolog after call
* high memory high memory
* | | | |
* EBP -> |saved FP | |saved FP |
* | ... | | ... |
* | | | |
* |arg n-1 | |arg n-1 |
* native | ... | | ... |
* caller |arg 0 | |arg 0 |
* ESP -> |return addr | |return addr |
* | | EBP -> |saved FP |
* | | |methodID | normal MethodID for JNI function
* | | |saved JavaFP| offset to preceeding java frame
* | | |saved edi | to be used for JTOC
* | | | " ebx | to be used for nonvolatile
* | | | " ecx | to be used for scrach
* | | | " esi | to be used for PR
* | | |arg 0 | copied in reverse order
* | | | ... |
* | | ESP -> |arg n-1 |
* | | | | normally compiled Java code continue
* | | | |
* | | | |
* | | | |
* low memory low memory
* </pre>
*/
public static void generateGlueCodeForJNIMethod(VM_Assembler asm, VM_NormalMethod method, int methodID) {
// set 2nd word of header = return address already pushed by CALL
// NOTE: C calling convention is that EBP contains the caller's framepointer.
// Therefore our C to Java transition frames must follow this protocol,
// not the RVM protocol in which the caller's framepointer is in
// pr.framePointer and EBP is a nonvolatile register.
asm.emitPUSH_Reg(EBP);
// start new frame: set FP to point to the new frame
asm.emitMOV_Reg_Reg(EBP, SP);
// set first word of header: method ID
asm.emitPUSH_Imm(methodID);
// buy space for the rest of the header (values stored later)
asm.emitSUB_Reg_Imm(SP, STACKFRAME_HEADER_SIZE - 2 * WORDSIZE);
// save registers that will be used in RVM, to be restored on return to C
// TODO: I don't think we need to do this: C has no nonvolatile registers on Linux/x86 --dave
// TODO: DAVE
asm.emitPUSH_Reg(JTOC);
asm.emitPUSH_Reg(EBX);
asm.emitPUSH_Reg(S0);
VM_ProcessorLocalState.emitPushProcessor(asm);
// Adjust first param from JNIEnv* to VM_JNIEnvironment.
asm.emitSUB_RegDisp_Imm(EBP,
Offset.fromIntSignExtend(2 * WORDSIZE),
VM_Entrypoints.JNIExternalFunctionsField.getOffset().toInt());
// copy the arguments in reverse order
VM_TypeReference[] types = method.getParameterTypes(); // does NOT include implicit this or class ptr
int numArguments = types.length; // number of arguments for this method
Offset argOffset = Offset.fromIntSignExtend(2 * WORDSIZE); // add 2 to get to arg area in caller frame
for (int i = 0; i < numArguments; i++) {
if (types[i].isLongType() || types[i].isDoubleType()) {
// handle 2-words case:
asm.emitMOV_Reg_RegDisp(EBX, EBP, argOffset.plus(WORDSIZE));
asm.emitPUSH_Reg(EBX);
asm.emitMOV_Reg_RegDisp(EBX, EBP, argOffset);
asm.emitPUSH_Reg(EBX);
argOffset = argOffset.plus(2 * WORDSIZE);
} else {
// Handle 1-word case:
asm.emitMOV_Reg_RegDisp(EBX, EBP, argOffset);
asm.emitPUSH_Reg(EBX);
argOffset = argOffset.plus(WORDSIZE);
}
}
// START of code sequence to atomically change processor status from IN_NATIVE
// to IN_JAVA, looping in a call to sysVirtualProcessorYield if BLOCKED_IN_NATIVE
int retryLabel = asm.getMachineCodeIndex(); // backward branch label
// Restore PR from VM_JNIEnvironment
asm.emitMOV_Reg_RegDisp(EBX, EBP, Offset.fromIntSignExtend(2 * WORDSIZE)); // pick up arg 0 (from callers frame)
VM_ProcessorLocalState.emitLoadProcessor(asm, EBX, VM_Entrypoints.JNIEnvSavedPRField.getOffset());
// reload JTOC from virtual processor
// NOTE: EDI saved in glue frame is just EDI (opt compiled code uses it as normal non-volatile)
VM_ProcessorLocalState.emitMoveFieldToReg(asm, JTOC, VM_ArchEntrypoints.jtocField.getOffset());
// T0 gets PR.statusField
VM_ProcessorLocalState.emitMoveFieldToReg(asm, T0, VM_Entrypoints.vpStatusField.getOffset());
asm.emitCMP_Reg_Imm(T0, VM_Processor.IN_NATIVE); // jmp if still IN_NATIVE
VM_ForwardReference fr = asm.forwardJcc(VM_Assembler.EQ); // if so, skip 3 instructions
// blocked in native, do pthread yield
asm.emitMOV_Reg_RegDisp(T0, JTOC, VM_Entrypoints.the_boot_recordField.getOffset()); // T0<-bootrecord addr
asm.emitCALL_RegDisp(T0, VM_Entrypoints.sysVirtualProcessorYieldIPField.getOffset());
asm.emitJMP_Imm(retryLabel); // retry from beginning
fr.resolve(asm); // branch here if IN_NATIVE, attempt to go to IN_JAVA
// T0 (EAX) contains "old value" (required for CMPXCNG instruction)
// S0 contains address of status word to be swapped
asm.emitMOV_Reg_Imm(T1, VM_Processor.IN_JAVA); // T1<-new value (IN_JAVA)
VM_ProcessorLocalState.emitCompareAndExchangeField(asm,
VM_Entrypoints.vpStatusField.getOffset(),
T1); // atomic compare-and-exchange
asm.emitJCC_Cond_Imm(VM_Assembler.NE, retryLabel);
// END of code sequence to change state from IN_NATIVE to IN_JAVA
// status is now IN_JAVA. GC can not occur while we execute on a processor
// in this state, so it is safe to access fields of objects.
// RVM JTOC and PR registers have been restored and EBX contains a pointer to
// the thread's VM_JNIEnvironment.
// done saving, bump SP to reserve room for the local variables
// SP should now be at the point normally marked as emptyStackOffset
int numLocalVariables = method.getLocalWords() - method.getParameterWords();
asm.emitSUB_Reg_Imm(SP, (numLocalVariables << LG_WORDSIZE));
// Retrieve -> preceeding "top" java FP from jniEnv and save in current
// frame of JNIFunction
asm.emitMOV_Reg_RegDisp(S0, EBX, VM_Entrypoints.JNITopJavaFPField.getOffset());
// get offset from current FP and save in hdr of current frame
asm.emitSUB_Reg_Reg(S0, EBP);
asm.emitMOV_RegDisp_Reg(EBP, SAVED_JAVA_FP_OFFSET, S0);
// put framePointer in VP following Jikes RVM conventions.
VM_ProcessorLocalState.emitMoveRegToField(asm, VM_ArchEntrypoints.framePointerField.getOffset(), EBP);
// at this point: JTOC and PR have been restored &
// processor status = IN_JAVA,
// arguments for the call have been setup, space on the stack for locals
// has been acquired.
// finally proceed with the normal Java compiled code
// skip the thread switch test for now, see VM_Compiler.genThreadSwitchTest(true)
asm.emitNOP(); // end of prologue marker
}
public static void generateEpilogForJNIMethod(VM_Assembler asm, VM_Method method) {
// assume RVM PR regs still valid. potentially T1 & T0 contain return
// values and should not be modified. we use regs saved in prolog and restored
// before return to do whatever needs to be done. does not assume JTOC is valid,
// and may use it as scratch reg.
// if returning long, switch the order of the hi/lo word in T0 and T1
if (method.getReturnType().isLongType()) {
asm.emitPUSH_Reg(T1);
asm.emitMOV_Reg_Reg(T1, T0);
asm.emitPOP_Reg(T0);
} else {
if (SSE2_FULL) {
// Marshall from XMM0 -> FP0
if (method.getReturnType().isDoubleType()) {
asm.emitMOVSD_RegDisp_Reg(PR, VM_Entrypoints.scratchStorageField.getOffset(), XMM0);
asm.emitFLD_Reg_RegDisp_Quad(FP0, PR, VM_Entrypoints.scratchStorageField.getOffset());
} else if (method.getReturnType().isFloatType()) {
asm.emitMOVSS_RegDisp_Reg(PR, VM_Entrypoints.scratchStorageField.getOffset(), XMM0);
asm.emitFLD_Reg_RegDisp(FP0, PR, VM_Entrypoints.scratchStorageField.getOffset());
}
}
}
// current processor status is IN_JAVA, so we only GC at yieldpoints
// S0 <- VM_JNIEnvironment
VM_ProcessorLocalState.emitMoveFieldToReg(asm, S0, VM_Entrypoints.activeThreadField.getOffset());
asm.emitMOV_Reg_RegDisp(S0, S0, VM_Entrypoints.jniEnvField.getOffset());
// set jniEnv TopJavaFP using value saved in frame in prolog
asm.emitMOV_Reg_RegDisp(EDI, EBP, SAVED_JAVA_FP_OFFSET); // JTOC<-saved TopJavaFP (offset)
asm.emitADD_Reg_Reg(EDI, EBP); // change offset from FP into address
asm.emitMOV_RegDisp_Reg(S0, VM_Entrypoints.JNITopJavaFPField.getOffset(), EDI); // jniEnv.TopJavaFP <- JTOC
// in case thread has migrated to different PR, reset saved PRs to current PR
VM_ProcessorLocalState.emitStoreProcessor(asm, S0, VM_Entrypoints.JNIEnvSavedPRField.getOffset());
// change current processor status to IN_NATIVE
VM_ProcessorLocalState.emitMoveImmToField(asm, VM_Entrypoints.vpStatusField.getOffset(), VM_Processor.IN_NATIVE);
// reload native/C nonvolatile regs - saved in prolog
// what about FPRs
// TODO: DAVE we really don't need to do this. C has no nonvols on Linux/x86
VM_ProcessorLocalState.emitPopProcessor(asm);
asm.emitPOP_Reg(S0);
asm.emitPOP_Reg(EBX);
asm.emitPOP_Reg(JTOC);
// NOTE: C expects the framepointer to be restored to EBP, so
// the epilogue for the C to Java glue code must follow that
// convention, not the RVM one!
// Also note that RVM treats EBP is a nonvolatile, so we don't
// explicitly save/restore it.
asm.emitMOV_Reg_Reg(SP, EBP); // discard current stack frame
asm.emitPOP_Reg(EBP);
asm.emitRET(); // return to caller
}
}