/* * 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.jni.ia32; import org.jikesrvm.ArchitectureSpecific; import org.jikesrvm.VM; import org.jikesrvm.classloader.RVMMethod; import org.jikesrvm.classloader.NativeMethod; import org.jikesrvm.classloader.NormalMethod; import org.jikesrvm.classloader.TypeReference; import org.jikesrvm.compilers.common.CompiledMethod; import org.jikesrvm.compilers.common.CompiledMethods; import org.jikesrvm.compilers.common.assembler.ForwardReference; import org.jikesrvm.compilers.common.assembler.ia32.Assembler; import org.jikesrvm.ia32.BaselineConstants; import org.jikesrvm.ia32.MachineCode; import org.jikesrvm.ia32.ThreadLocalState; import org.jikesrvm.jni.JNICompiledMethod; import org.jikesrvm.objectmodel.ObjectModel; import org.jikesrvm.runtime.ArchEntrypoints; import org.jikesrvm.runtime.Entrypoints; import org.jikesrvm.runtime.Statics; import org.jikesrvm.runtime.Magic; import org.jikesrvm.scheduler.RVMThread; 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 for the 2 cases: * <ul> * <li>from Java to C: all user-defined native methods</li> * <li>C to Java: all JNI functions in {@link org.jikesrvm.jni.JNIFunctions}</li> * </ul> * When performing the transitions the values in registers and on the stack need * to be treated with care, but also when transitioning into Java we need to do * a spin-wait if a GC is underway. * * Transitioning from Java to C then back: * <ol> * <li>Set up stack frame and save non-volatile registers<li> * <li>Move all native method arguments on to stack (NB at this point all non-volatile state is saved)</li> * <li>Set up jniEnv</li> * <li>Record the frame pointer of the last Java frame (this) in the jniEnv</li> * <li>Set up JNI refs, a stack that holds references accessed via JNI</li> * <li>Call out to convert reference arguments to IDs</li> * <li>Set processor as being "in native"</li> * <li>Set up stack frame and registers for transition to C</li> * <li>Call out to C</li> * <li>Save result to stack</li> * <li>Transition back from "in native" to "in Java", take care that the * Processor isn't "blocked in native", ie other processors have decided to * start a GC and we're not permitted to execute Java code whilst this * occurs</li> * <li>Convert a reference result (currently a JNI ref) into a true reference</li> * <li>Release JNI refs</li> * <li>Restore stack and place result in register</li> * <ol> * * Prologue generation from C to Java: * <ol> * <li>Set up stack frame with C arguments in Jikes RVM convention</li> * <li>Set up extra stack frame entry that records the previous last top Java FP</li> * <li>Transition from "in native" to "in Java" taking care of blocked (in GC) case</li> * </ol> * * Epilogue generation from Java to C: * <ol> * <li>Restore record of the previous last top Java FP in the jniEnv</li> * <li>Transition from "in Java" to "in native"</li> * <li>Set up result in registers. NB. JNIFunctions don't return references but * JNI refs directly, so we don't need to transition these</li> * </ol> */ public abstract class JNICompiler implements BaselineConstants { /** Dummy field to force compilation of the exception deliverer */ private org.jikesrvm.jni.ia32.JNIExceptionDeliverer unused; /** Offset of external functions field in JNIEnvironment */ private static final int jniExternalFunctionsFieldOffset = Entrypoints.JNIExternalFunctionsField.getOffset().toInt(); // --- Java to C fields --- /** Location of non-volatile EDI register when saved to stack */ static final Offset EDI_SAVE_OFFSET = Offset.fromIntSignExtend(STACKFRAME_BODY_OFFSET); /** Location of non-volatile EBX register when saved to stack */ static final Offset EBX_SAVE_OFFSET = EDI_SAVE_OFFSET.minus(WORDSIZE); /** Location of non-volatile EBP register when saved to stack */ static final Offset EBP_SAVE_OFFSET = EBX_SAVE_OFFSET.minus(WORDSIZE); /** Location of an extra copy of the RVMThread.jniEnv when saved to stack */ private static final Offset JNI_ENV_OFFSET = EBP_SAVE_OFFSET.minus(WORDSIZE); /** Location of a saved version of the field JNIEnvironment.basePointerOnEntryToNative */ private static final Offset BP_ON_ENTRY_OFFSET = JNI_ENV_OFFSET.minus(WORDSIZE); // --- C to Java fields --- /** * Stack frame location for saved JNIEnvironment.JNITopJavaFP that * will be clobbered by a transition from Java to C. Only used in * the prologue & epilogue for JNIFunctions. */ private static final Offset SAVED_JAVA_FP_OFFSET = Offset.fromIntSignExtend(STACKFRAME_BODY_OFFSET); /** * The following is used in BaselineCompilerImpl to compute offset to first local. * Number of non-volatile GPRs and FPRs and then 1 slot for the SAVED_JAVA_FP. */ public static final int SAVED_GPRS_FOR_JNI = NATIVE_NONVOLATILE_GPRS.length + NATIVE_NONVOLATILE_FPRS.length + 1; /** * Compile a method to handle the Java to C transition and back * Transitioning from Java to C then back: * <ol> * <li>Set up stack frame and save non-volatile registers<li> * <li>Set up jniEnv - set up a register to hold JNIEnv and store * the Processor in the JNIEnv for easy access</li> * <li>Move all native method arguments on to stack (NB at this point all * non-volatile state is saved)</li> * <li>Record the frame pointer of the last Java frame (this) in the jniEnv</li> * <li>Call out to convert reference arguments to IDs</li> * <li>Set processor as being "in native"</li> * <li>Set up stack frame and registers for transition to C</li> * <li>Call out to C</li> * <li>Save result to stack</li> * <li>Transition back from "in native" to "in Java", take care that the * Processor isn't "blocked in native", ie other processors have decided to * start a GC and we're not permitted to execute Java code whilst this * occurs</li> * <li>Convert a reference result (currently a JNI ref) into a true reference</li> * <li>Release JNI refs</li> * <li>Restore stack and place result in register</li> * <ol> */ public static synchronized CompiledMethod compile(NativeMethod method) { // Meaning of constant offset into frame (assuming 4byte word size): // 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 | <- lastParameterOffset // +4 |returnAddr| |returnAddr| // 0 + + +saved FP + <- EBP/FP value in glue frame // -4 | | |methodID | // -8 | | |saved EDI | // -C | | |saved EBX | // -10 | | |saved EBP | // -14 | | |saved ENV | (JNIEnvironment) // -18 | | |arg n-1 | reordered args to native method // -1C | | | ... | ... // -20 | | |arg 1 | ... // -24 | | |arg 0 | ... // -28 | | |class/obj | required second arg to native method // -2C | | |jni funcs | required first arg to native method // -30 | | | | // | | | | // | | | | // low address low address // Register values: // EBP - after step 1 EBP holds a frame pointer allowing easy // access to both this and the proceeding frame // ESP - gradually floats down as the stack frame is initialized // S0/ECX - reference to the JNI environment after step 3 JNICompiledMethod cm = (JNICompiledMethod)CompiledMethods.createCompiledMethod(method, CompiledMethod.JNI); ArchitectureSpecific.Assembler asm = new ArchitectureSpecific.Assembler(100 /*, true*/); // some size for the instruction array Address nativeIP = method.getNativeIP(); final Offset lastParameterOffset = Offset.fromIntSignExtend(2*WORDSIZE); //final Offset firstParameterOffset = Offset.fromIntSignExtend(WORDSIZE+(method.getParameterWords() << LG_WORDSIZE)); final TypeReference[] args = method.getParameterTypes(); // (1) Set up stack frame and save non-volatile registers // 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 // set 2nd word of header = return address already pushed by CALL asm.emitPUSH_RegDisp(THREAD_REGISTER, ArchEntrypoints.framePointerField.getOffset()); // establish new frame ThreadLocalState.emitMoveRegToField(asm, ArchEntrypoints.framePointerField.getOffset(), SP); // set first word of header: method ID if (VM.VerifyAssertions) VM._assert(STACKFRAME_METHOD_ID_OFFSET == -WORDSIZE); asm.emitPUSH_Imm(cm.getId()); // save nonvolatile registrs: EDI, EBX, EBP if (VM.VerifyAssertions) VM._assert(EDI_SAVE_OFFSET.toInt() == -2*WORDSIZE); asm.emitPUSH_Reg(EDI); // save nonvolatile EDI register if (VM.VerifyAssertions) VM._assert(EBX_SAVE_OFFSET.toInt() == -3*WORDSIZE); asm.emitPUSH_Reg(EBX); // save nonvolatile EBX register if (VM.VerifyAssertions) VM._assert(EBP_SAVE_OFFSET.toInt() == -4*WORDSIZE); asm.emitPUSH_Reg(EBP); // save nonvolatile EBP register // Establish EBP as the framepointer for use in the rest of the glue frame asm.emitLEA_Reg_RegDisp(EBP, SP, Offset.fromIntSignExtend(4*WORDSIZE)); // (2) Set up jniEnv - set up a register to hold JNIEnv and store // the Processor in the JNIEnv for easy access // S0 = RVMThread.jniEnv if (VM.BuildFor32Addr) { asm.emitMOV_Reg_RegDisp(S0, THREAD_REGISTER, Entrypoints.jniEnvField.getOffset()); } else { asm.emitMOV_Reg_RegDisp_Quad(S0, THREAD_REGISTER, Entrypoints.jniEnvField.getOffset()); } if (VM.VerifyAssertions) VM._assert(JNI_ENV_OFFSET.toInt() == -5*WORDSIZE); asm.emitPUSH_Reg(S0); // save JNI Env for after call if (VM.VerifyAssertions) VM._assert(BP_ON_ENTRY_OFFSET.toInt() == -6*WORDSIZE); asm.emitPUSH_RegDisp(S0, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset()); // save BP into JNIEnv if (VM.BuildFor32Addr) { asm.emitMOV_RegDisp_Reg(S0, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset(), EBP); } else { asm.emitMOV_RegDisp_Reg_Quad(S0, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset(), EBP); } // (3) Move all native method arguments on to stack (NB at this // point all non-volatile state is saved) // (3.1) Count how many arguments could be passed in either FPRs or GPRs int numFprArgs=0; int numGprArgs=method.isStatic() ? 0 : 1; for (TypeReference arg : args) { if (arg.isFloatType() || arg.isDoubleType()) { numFprArgs++; } else if (VM.BuildFor32Addr && arg.isLongType()) { numGprArgs+=2; } else { numGprArgs++; } } // (3.2) Walk over arguments backwards pushing either from memory or registers Offset currentArg = lastParameterOffset; int argFpr=numFprArgs-1; int argGpr=numGprArgs-1; for (int i=args.length-1; i >= 0; i--) { TypeReference arg = args[i]; if (arg.isFloatType()) { if (argFpr < PARAMETER_FPRS.length) { asm.emitPUSH_Reg(T0); // make space if (SSE2_FULL) { asm.emitMOVSS_RegInd_Reg(SP, (XMM)PARAMETER_FPRS[argFpr]); } else { asm.emitFSTP_RegInd_Reg(SP, (FPR)PARAMETER_FPRS[argFpr]); } } else { asm.emitPUSH_RegDisp(EBP, currentArg); } argFpr--; } else if (arg.isDoubleType()) { if (VM.BuildFor32Addr) { if (argFpr < PARAMETER_FPRS.length) { asm.emitPUSH_Reg(T0); // make space asm.emitPUSH_Reg(T0); // need 2 slots with 32bit addresses if (SSE2_FULL) { asm.emitMOVSD_RegInd_Reg(SP, (XMM)PARAMETER_FPRS[argFpr]); } else { asm.emitFSTP_RegInd_Reg_Quad(SP, (FPR)PARAMETER_FPRS[argFpr]); } } else { asm.emitPUSH_RegDisp(EBP, currentArg.plus(WORDSIZE)); asm.emitPUSH_RegDisp(EBP, currentArg); // need 2 slots with 32bit addresses } } else { if (argFpr < PARAMETER_FPRS.length) { asm.emitPUSH_Reg(T0); // make space if (SSE2_FULL) { asm.emitMOVSD_RegInd_Reg(SP, (XMM)PARAMETER_FPRS[argFpr]); } else { asm.emitFSTP_RegInd_Reg_Quad(SP, (FPR)PARAMETER_FPRS[argFpr]); } } else { asm.emitPUSH_RegDisp(EBP, currentArg); } } argFpr--; currentArg = currentArg.plus(WORDSIZE); } else if (VM.BuildFor32Addr && arg.isLongType()) { if (argGpr < PARAMETER_GPRS.length) { asm.emitPUSH_Reg(PARAMETER_GPRS[argGpr-1]); asm.emitPUSH_Reg(PARAMETER_GPRS[argGpr]); } else if (argGpr - 1 < PARAMETER_GPRS.length) { asm.emitPUSH_Reg(PARAMETER_GPRS[argGpr-1]); asm.emitPUSH_RegDisp(EBP, currentArg); } else { asm.emitPUSH_RegDisp(EBP, currentArg.plus(WORDSIZE)); asm.emitPUSH_RegDisp(EBP, currentArg); } argGpr-=2; currentArg = currentArg.plus(WORDSIZE); } else { if (argGpr < PARAMETER_GPRS.length) { asm.emitPUSH_Reg(PARAMETER_GPRS[argGpr]); } else { asm.emitPUSH_RegDisp(EBP, currentArg); } argGpr--; if (VM.BuildFor64Addr && arg.isLongType()) { currentArg = currentArg.plus(WORDSIZE); } } currentArg = currentArg.plus(WORDSIZE); } // (3.3) push class or object argument if (method.isStatic()) { // push java.lang.Class object for klass Offset klassOffset = Offset.fromIntSignExtend( Statics.findOrCreateObjectLiteral(method.getDeclaringClass().getClassForType())); asm.emitPUSH_Abs(Magic.getTocPointer().plus(klassOffset)); } else { if (VM.VerifyAssertions) VM._assert(argGpr == 0); asm.emitPUSH_Reg(PARAMETER_GPRS[0]); } // (3.4) push a pointer to the JNI functions that will be // dereferenced in native code asm.emitPUSH_Reg(S0); if (jniExternalFunctionsFieldOffset != 0) { if (VM.BuildFor32Addr) { asm.emitADD_RegInd_Imm(ESP, jniExternalFunctionsFieldOffset); } else { asm.emitADD_RegInd_Imm_Quad(ESP, jniExternalFunctionsFieldOffset); } } // (4) Call out to convert reference arguments to IDs, set thread as // being "in native" and record the frame pointer of the last Java frame // (this) in the jniEnv // Encode reference arguments into a long int encodedReferenceOffsets=0; for (int i=0, pos=0; i < args.length; i++, pos++) { TypeReference arg = args[i]; if (arg.isReferenceType()) { if (VM.VerifyAssertions) VM._assert(pos < 32); encodedReferenceOffsets |= 1 << pos; } else if (arg.isLongType() || arg.isDoubleType()) { pos++; } } // Call out to JNI environment JNI entry if (VM.BuildFor32Addr) { asm.emitMOV_Reg_RegDisp(PARAMETER_GPRS[0], EBP, JNI_ENV_OFFSET); } else { asm.emitMOV_Reg_RegDisp_Quad(PARAMETER_GPRS[0], EBP, JNI_ENV_OFFSET); } asm.emitPUSH_Reg(PARAMETER_GPRS[0]); asm.emitMOV_Reg_Imm(PARAMETER_GPRS[1], encodedReferenceOffsets); asm.emitPUSH_Reg(PARAMETER_GPRS[1]); ObjectModel.baselineEmitLoadTIB(asm, S0.value(), PARAMETER_GPRS[0].value()); asm.emitCALL_RegDisp(S0, Entrypoints.jniEntry.getOffset()); // (5) Set up stack frame and registers for transition to C int argsPassedInRegister=0; if (VM.BuildFor64Addr) { int gpRegistersInUse=2; int fpRegistersInUse=0; boolean dataOnStack = false; asm.emitPOP_Reg(NATIVE_PARAMETER_GPRS[0]); // JNI env asm.emitPOP_Reg(NATIVE_PARAMETER_GPRS[1]); // Object/Class argsPassedInRegister+=2; for (TypeReference arg : method.getParameterTypes()) { if (arg.isFloatType()) { if (fpRegistersInUse < NATIVE_PARAMETER_FPRS.length) { // TODO: we can't have holes in the data that is on the stack, we need to shuffle it up if (dataOnStack) throw new Error("Unsupported native method parameter list"); asm.emitMOVSS_Reg_RegInd((XMM)NATIVE_PARAMETER_FPRS[fpRegistersInUse], SP); asm.emitPOP_Reg(T0); fpRegistersInUse++; argsPassedInRegister++; } else { // no register available so we have data on the stack dataOnStack = true; } } else if (arg.isDoubleType()) { if (fpRegistersInUse < NATIVE_PARAMETER_FPRS.length) { // TODO: we can't have holes in the data that is on the stack, we need to shuffle it up if (dataOnStack) throw new Error("Unsupported native method parameter list"); asm.emitMOVSD_Reg_RegInd((XMM)NATIVE_PARAMETER_FPRS[fpRegistersInUse], SP); asm.emitPOP_Reg(T0); asm.emitPOP_Reg(T0); fpRegistersInUse++; argsPassedInRegister+=2; } else { // no register available so we have data on the stack dataOnStack = true; } } else { if (gpRegistersInUse < NATIVE_PARAMETER_GPRS.length) { // TODO: we can't have holes in the data that is on the stack, we need to shuffle it up if (dataOnStack) throw new Error("Unsupported native method parameter list"); asm.emitPOP_Reg(NATIVE_PARAMETER_GPRS[gpRegistersInUse]); gpRegistersInUse++; argsPassedInRegister++; } else { // no register available so we have data on the stack dataOnStack = true; } } } } // (6) Call out to C // move address of native code to invoke into T0 if (VM.BuildFor32Addr) { asm.emitMOV_Reg_Imm(T0, nativeIP.toInt()); } else { asm.emitMOV_Reg_Imm_Quad(T0, nativeIP.toLong()); } // make the call to native code asm.emitCALL_Reg(T0); // (7) Discard parameters on stack // TODO: optimize stack adjustment if (VM.BuildFor32Addr) { // throw away args, class/this ptr and env int argsToThrowAway = method.getParameterWords()+2-argsPassedInRegister; if (argsToThrowAway != 0) { asm.emitADD_Reg_Imm(SP, argsToThrowAway << LG_WORDSIZE); } } else { // throw away args, class/this ptr and env int argsToThrowAway = args.length+2-argsPassedInRegister; if (argsToThrowAway != 0) { asm.emitADD_Reg_Imm_Quad(SP, argsToThrowAway << LG_WORDSIZE); } } // (8) Save result to stack final TypeReference returnType = method.getReturnType(); if (returnType.isVoidType()) { // Nothing to save } else if (returnType.isFloatType()) { asm.emitPUSH_Reg(T0); // adjust stack if (VM.BuildFor32Addr) { asm.emitFSTP_RegInd_Reg(ESP, FP0); } else { asm.emitMOVSS_RegInd_Reg(ESP, XMM0); } } else if (returnType.isDoubleType()) { asm.emitPUSH_Reg(T0); // adjust stack asm.emitPUSH_Reg(T0); // adjust stack if (VM.BuildFor32Addr) { asm.emitFSTP_RegInd_Reg_Quad(ESP, FP0); } else { asm.emitMOVSD_RegInd_Reg(ESP, XMM0); } } else if (VM.BuildFor32Addr && returnType.isLongType()) { asm.emitPUSH_Reg(T0); asm.emitPUSH_Reg(T1); } else { // Ensure sign-extension is correct if (returnType.isBooleanType()) { asm.emitMOVZX_Reg_Reg_Byte(T0, T0); } else if (returnType.isByteType()) { asm.emitMOVSX_Reg_Reg_Byte(T0, T0); } else if (returnType.isCharType()) { asm.emitMOVZX_Reg_Reg_Word(T0, T0); } else if (returnType.isShortType()) { asm.emitMOVSX_Reg_Reg_Word(T0, T0); } asm.emitPUSH_Reg(T0); } // (9) Recover RVM style frame // (9.1) reload JNIEnvironment from glue frame if (VM.BuildFor32Addr) { asm.emitMOV_Reg_RegDisp(S0, EBP, JNICompiler.JNI_ENV_OFFSET); } else { asm.emitMOV_Reg_RegDisp_Quad(S0, EBP, JNICompiler.JNI_ENV_OFFSET); } // (9.2) Reload thread register from JNIEnvironment ThreadLocalState.emitLoadThread(asm, S0, Entrypoints.JNIEnvSavedTRField.getOffset()); // (9.3) Establish frame pointer to this glue method ThreadLocalState.emitMoveRegToField(asm, ArchEntrypoints.framePointerField.getOffset(), EBP); // (10) Transition back from "in native" to "in Java", convert a reference // result (currently a JNI ref) into a true reference, release JNI refs asm.emitMOV_Reg_Reg(PARAMETER_GPRS[0], S0); // 1st arg is JNI Env if (returnType.isReferenceType()) { asm.emitPOP_Reg(PARAMETER_GPRS[1]); // 2nd arg is ref result } else { // place dummy (null) operand on stack asm.emitXOR_Reg_Reg(PARAMETER_GPRS[1], PARAMETER_GPRS[1]); } asm.emitPUSH_Reg(S0); // save JNIEnv asm.emitPUSH_Reg(S0); // push arg 1 asm.emitPUSH_Reg(PARAMETER_GPRS[1]); // push arg 2 // Do the call ObjectModel.baselineEmitLoadTIB(asm, S0.value(), S0.value()); asm.emitCALL_RegDisp(S0, Entrypoints.jniExit.getOffset()); asm.emitPOP_Reg(S0); // restore JNIEnv // (11) Restore stack and place result in register // place result in register if (returnType.isVoidType()) { // Nothing to save } else if (returnType.isReferenceType()) { // value already in register } else if (returnType.isFloatType()) { if (SSE2_FULL) { asm.emitMOVSS_Reg_RegInd(XMM0, ESP); } else { asm.emitFLD_Reg_RegInd(FP0, ESP); } asm.emitPOP_Reg(T0); // adjust stack } else if (returnType.isDoubleType()) { if (SSE2_FULL) { asm.emitMOVSD_Reg_RegInd(XMM0, ESP); } else { asm.emitFLD_Reg_RegInd_Quad(FP0, ESP); } asm.emitPOP_Reg(T0); // adjust stack asm.emitPOP_Reg(T0); // adjust stack } else if (VM.BuildFor32Addr && returnType.isLongType()) { asm.emitPOP_Reg(T0); asm.emitPOP_Reg(T1); } else { asm.emitPOP_Reg(T0); } asm.emitPOP_Reg(EBX); // saved previous native BP asm.emitMOV_RegDisp_Reg(S0, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset(), EBX); asm.emitPOP_Reg(EBX); // throw away JNI env asm.emitPOP_Reg(EBP); // restore non-volatile EBP asm.emitPOP_Reg(EBX); // restore non-volatile EBX asm.emitPOP_Reg(EDI); // restore non-volatile EDI asm.emitPOP_Reg(S0); // throw away cmid asm.emitPOP_RegDisp(THREAD_REGISTER, ArchEntrypoints.framePointerField.getOffset()); // (12) Return to caller // pop parameters from stack (Note that parameterWords does not include "this") if (method.isStatic()) { asm.emitRET_Imm(method.getParameterWords() << LG_WORDSIZE); } else { asm.emitRET_Imm((method.getParameterWords() + 1) << LG_WORDSIZE); } MachineCode machineCode = new ArchitectureSpecific.MachineCode(asm.getMachineCodes(), null); cm.compileComplete(machineCode.getInstructions()); return cm; } /** * Handle the C to Java transition: JNI methods in JNIFunctions.java. * Create a prologue for the baseline compiler. * <pre> * NOTE: * -We need THREAD_REGISTER to access Java environment; we can get it from * the JNIEnv* (which is an interior pointer to the 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 | JNIEnv* |arg 0 | JNIEnvironment * ESP -> |return addr | |return addr | * | | EBP -> |saved FP | outer most native frame pointer * | | |methodID | normal MethodID for JNI function * | | |saved JavaFP| offset to preceeding java frame * | | |saved nonvol| to be used for nonvolatile storage * | | | ... | including ebp on entry * | | |arg 0 | copied in reverse order (JNIEnvironment) * | | | ... | * | | ESP -> |arg n-1 | * | | | | normally compiled Java code continue * | | | | * | | | | * | | | | * low memory low memory * </pre> */ public static void generateGlueCodeForJNIMethod(Assembler asm, NormalMethod method, int methodID) { // Variable tracking the depth of the stack as we generate the prologue int stackDepth=0; // 1st word of header = return address already pushed by CALL // 2nd word of header = space for frame pointer if (VM.VerifyAssertions) VM._assert(STACKFRAME_FRAME_POINTER_OFFSET == stackDepth << LG_WORDSIZE); asm.emitPUSH_Reg(EBP); stackDepth--; // start new frame: set FP to point to the new frame if (VM.BuildFor32Addr) { asm.emitMOV_Reg_Reg(EBP, SP); } else { asm.emitMOV_Reg_Reg_Quad(EBP, SP); } // set 3rd word of header: method ID if (VM.VerifyAssertions) VM._assert(STACKFRAME_METHOD_ID_OFFSET == stackDepth << LG_WORDSIZE); asm.emitPUSH_Imm(methodID); stackDepth--; // buy space for the SAVED_JAVA_FP if (VM.VerifyAssertions) VM._assert(STACKFRAME_BODY_OFFSET == stackDepth << LG_WORDSIZE); asm.emitPUSH_Reg(T0); stackDepth--; // store non-volatiles for (GPR r : NATIVE_NONVOLATILE_GPRS) { if(r != EBP) { asm.emitPUSH_Reg(r); } else { asm.emitPUSH_RegInd(EBP); // save original EBP value } stackDepth--; } for (FloatingPointMachineRegister r : NATIVE_NONVOLATILE_FPRS) { // TODO: we assume non-volatile will hold at most a double asm.emitPUSH_Reg(T0); // adjust space for double asm.emitPUSH_Reg(T0); stackDepth-=2; if (r instanceof XMM) { asm.emitMOVSD_RegInd_Reg(SP, (XMM)r); } else { // NB this will fail for anything other than FPR0 asm.emitFST_RegInd_Reg_Quad(SP, (FPR)r); } } if (VM.VerifyAssertions) VM._assert(stackDepth << LG_WORDSIZE == STACKFRAME_BODY_OFFSET - (SAVED_GPRS_FOR_JNI << LG_WORDSIZE), "of2fp="+stackDepth+" sg4j="+SAVED_GPRS_FOR_JNI); // Adjust first param from JNIEnv* to JNIEnvironment. final Offset firstStackArgOffset = Offset.fromIntSignExtend(2 * WORDSIZE); if (jniExternalFunctionsFieldOffset != 0) { if (NATIVE_PARAMETER_GPRS.length > 0) { if (VM.BuildFor32Addr) { asm.emitSUB_Reg_Imm(NATIVE_PARAMETER_GPRS[0], jniExternalFunctionsFieldOffset); } else { asm.emitSUB_Reg_Imm_Quad(NATIVE_PARAMETER_GPRS[0], jniExternalFunctionsFieldOffset); } } else { if (VM.BuildFor32Addr) { asm.emitSUB_RegDisp_Imm(EBP, firstStackArgOffset, jniExternalFunctionsFieldOffset); } else { asm.emitSUB_RegDisp_Imm_Quad(EBP, firstStackArgOffset, jniExternalFunctionsFieldOffset); } } } // copy the arguments in reverse order final TypeReference[] argTypes = method.getParameterTypes(); // does NOT include implicit this or class ptr Offset stackArgOffset = firstStackArgOffset; final int startOfStackedArgs = stackDepth+1; // negative value relative to EBP int argGPR = 0; int argFPR = 0; for (TypeReference argType : argTypes) { if (argType.isFloatType()) { if (argFPR < NATIVE_PARAMETER_FPRS.length) { asm.emitPUSH_Reg(T0); // adjust stack if (VM.BuildForSSE2) { asm.emitMOVSS_RegInd_Reg(SP, (XMM)NATIVE_PARAMETER_FPRS[argFPR]); } else { asm.emitFSTP_RegInd_Reg(SP, (FPR)FP0); } argFPR++; } else { asm.emitPUSH_RegDisp(EBP, stackArgOffset); stackArgOffset = stackArgOffset.plus(WORDSIZE); } stackDepth--; } else if (argType.isDoubleType()) { if (argFPR < NATIVE_PARAMETER_FPRS.length) { asm.emitPUSH_Reg(T0); // adjust stack asm.emitPUSH_Reg(T0); if (VM.BuildForSSE2) { asm.emitMOVSD_RegInd_Reg(SP, (XMM)NATIVE_PARAMETER_FPRS[argGPR]); } else { asm.emitFSTP_RegInd_Reg_Quad(SP, FP0); } argFPR++; } else { if (VM.BuildFor32Addr) { asm.emitPUSH_RegDisp(EBP, stackArgOffset.plus(WORDSIZE)); asm.emitPUSH_RegDisp(EBP, stackArgOffset); stackArgOffset = stackArgOffset.plus(2*WORDSIZE); } else { asm.emitPUSH_Reg(T0); // adjust stack asm.emitPUSH_RegDisp(EBP, stackArgOffset); stackArgOffset = stackArgOffset.plus(WORDSIZE); } } stackDepth-=2; } else if (argType.isLongType()) { if (VM.BuildFor32Addr) { if (argGPR+1 < NATIVE_PARAMETER_GPRS.length) { asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR]); asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR+1]); argGPR+=2; } else if (argGPR < NATIVE_PARAMETER_GPRS.length) { asm.emitPUSH_RegDisp(EBP, stackArgOffset); asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR]); argGPR++; stackArgOffset = stackArgOffset.plus(WORDSIZE); } else { asm.emitPUSH_RegDisp(EBP, stackArgOffset.plus(WORDSIZE)); asm.emitPUSH_RegDisp(EBP, stackArgOffset); stackArgOffset = stackArgOffset.plus(WORDSIZE*2); } stackDepth-=2; } else { asm.emitPUSH_Reg(T0); // adjust stack if (argGPR < NATIVE_PARAMETER_GPRS.length) { asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR]); argGPR++; } else { asm.emitPUSH_RegDisp(EBP, stackArgOffset); stackDepth-=2; stackArgOffset = stackArgOffset.plus(WORDSIZE); } stackDepth-=2; } } else { // Reference, int or smaller type // NB we don't convert JNI references to true references (as // in the entry/exit to a JNI method) as our JNI functions // expect integer arguments if (argGPR < NATIVE_PARAMETER_GPRS.length) { asm.emitPUSH_Reg(NATIVE_PARAMETER_GPRS[argGPR]); argGPR++; } else { asm.emitPUSH_RegDisp(EBP, stackArgOffset); stackArgOffset = stackArgOffset.plus(WORDSIZE); } stackDepth--; } } // START of code sequence to atomically change thread status from // IN_JNI to IN_JAVA, looping in a call to // RVMThread.leaveJNIBlockedFromJNIFunctionCallMethod if // BLOCKED_IN_NATIVE int retryLabel = asm.getMachineCodeIndex(); // backward branch label // Restore THREAD_REGISTER from JNIEnvironment if (VM.BuildFor32Addr) { asm.emitMOV_Reg_RegDisp(EBX, EBP, Offset.fromIntSignExtend((startOfStackedArgs-1) * WORDSIZE)); // pick up arg 0 (from our frame) } else { asm.emitMOV_Reg_RegDisp_Quad(EBX, EBP, Offset.fromIntSignExtend((startOfStackedArgs-1) * WORDSIZE)); // pick up arg 0 (from our frame) } ThreadLocalState.emitLoadThread(asm, EBX, Entrypoints.JNIEnvSavedTRField.getOffset()); // what we need to keep in mind at this point: // - EBX has JNI env (but it's nonvolatile) // - EBP has the FP (but it's nonvolatile) // - stack has the args but not the locals // - TR has been restored // attempt to change the thread state to IN_JAVA asm.emitMOV_Reg_Imm(T0, RVMThread.IN_JNI); asm.emitMOV_Reg_Imm(T1, RVMThread.IN_JAVA); ThreadLocalState.emitCompareAndExchangeField( asm, Entrypoints.execStatusField.getOffset(), T1); // if we succeeded, move on, else go into slow path ForwardReference doneLeaveJNIRef = asm.forwardJcc(Assembler.EQ); // make the slow call asm.emitCALL_Abs( Magic.getTocPointer().plus( Entrypoints.leaveJNIBlockedFromJNIFunctionCallMethod.getOffset())); // arrive here when we've switched to IN_JAVA doneLeaveJNIRef.resolve(asm); // END of code sequence to change state from IN_JNI 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 TR register has been restored and EBX contains a pointer to // the thread's 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(); // TODO: optimize this space adjustment if (VM.BuildFor32Addr) { asm.emitSUB_Reg_Imm(SP, (numLocalVariables << LG_WORDSIZE)); } else { asm.emitSUB_Reg_Imm_Quad(SP, (numLocalVariables << LG_WORDSIZE)); } // Retrieve -> preceeding "top" java FP from jniEnv and save in current // frame of JNIFunction if (VM.BuildFor32Addr) { asm.emitMOV_Reg_RegDisp(S0, EBX, Entrypoints.JNITopJavaFPField.getOffset()); } else { asm.emitMOV_Reg_RegDisp_Quad(S0, EBX, Entrypoints.JNITopJavaFPField.getOffset()); } // get offset from current FP and save in hdr of current frame if (VM.BuildFor32Addr) { asm.emitSUB_Reg_Reg(S0, EBP); asm.emitMOV_RegDisp_Reg(EBP, SAVED_JAVA_FP_OFFSET, S0); } else { asm.emitSUB_Reg_Reg_Quad(S0, EBP); asm.emitMOV_RegDisp_Reg_Quad(EBP, SAVED_JAVA_FP_OFFSET, S0); } // clobber the saved frame pointer with that from the JNIEnvironment (work around for omit-frame-pointer) if (VM.BuildFor32Addr) { asm.emitMOV_Reg_RegDisp(S0, EBX, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset()); asm.emitMOV_RegInd_Reg(EBP, S0); } else { asm.emitMOV_Reg_RegDisp_Quad(S0, EBX, Entrypoints.JNIEnvBasePointerOnEntryToNative.getOffset()); asm.emitMOV_RegInd_Reg_Quad(EBP, S0); } // put framePointer in Thread following Jikes RVM conventions. ThreadLocalState.emitMoveRegToField(asm, ArchEntrypoints.framePointerField.getOffset(), EBP); // at this point: TR has 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 BaselineCompilerImpl.genThreadSwitchTest(true) //asm.emitNOP(1); // end of prologue marker } /** * Handle the C to Java transition: JNI methods in JNIFunctions.java. * Create an epilogue for the baseline compiler. */ public static void generateEpilogForJNIMethod(Assembler asm, RVMMethod method) { // assume RVM TR 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. if (VM.BuildFor32Addr) { // 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 && VM.BuildFor32Addr) { // Marshall from XMM0 -> FP0 if (method.getReturnType().isDoubleType()) { if (VM.VerifyAssertions) VM._assert(VM.BuildFor32Addr); asm.emitMOVSD_RegDisp_Reg(THREAD_REGISTER, Entrypoints.scratchStorageField.getOffset(), XMM0); asm.emitFLD_Reg_RegDisp_Quad(FP0, THREAD_REGISTER, Entrypoints.scratchStorageField.getOffset()); } else if (method.getReturnType().isFloatType()) { if (VM.VerifyAssertions) VM._assert(VM.BuildFor32Addr); asm.emitMOVSS_RegDisp_Reg(THREAD_REGISTER, Entrypoints.scratchStorageField.getOffset(), XMM0); asm.emitFLD_Reg_RegDisp(FP0, THREAD_REGISTER, Entrypoints.scratchStorageField.getOffset()); } } } } // current processor status is IN_JAVA, so we only GC at yieldpoints // S0 <- JNIEnvironment if (VM.BuildFor32Addr) { asm.emitMOV_Reg_RegDisp(S0, THREAD_REGISTER, Entrypoints.jniEnvField.getOffset()); } else { asm.emitMOV_Reg_RegDisp_Quad(S0, THREAD_REGISTER, Entrypoints.jniEnvField.getOffset()); } // set jniEnv TopJavaFP using value saved in frame in prolog if (VM.BuildFor32Addr) { asm.emitMOV_Reg_RegDisp(EDI, EBP, SAVED_JAVA_FP_OFFSET); // EDI<-saved TopJavaFP (offset) asm.emitADD_Reg_Reg(EDI, EBP); // change offset from FP into address asm.emitMOV_RegDisp_Reg(S0, Entrypoints.JNITopJavaFPField.getOffset(), EDI); // jniEnv.TopJavaFP <- EDI } else { asm.emitMOV_Reg_RegDisp_Quad(EDI, EBP, SAVED_JAVA_FP_OFFSET); // EDI<-saved TopJavaFP (offset) asm.emitADD_Reg_Reg_Quad(EDI, EBP); // change offset from FP into address asm.emitMOV_RegDisp_Reg_Quad(S0, Entrypoints.JNITopJavaFPField.getOffset(), EDI); // jniEnv.TopJavaFP <- EDI } // NOTE: we could save the TR in the JNI env, but no need, that would have // already been done. // what's going on here: // - SP and EBP have important stuff in them, but that's fine, since // a call will restore SP and EBP is non-volatile for RVM code // - TR still refers to the thread // save return values asm.emitPUSH_Reg(T0); asm.emitPUSH_Reg(T1); // attempt to change the thread state to IN_JNI asm.emitMOV_Reg_Imm(T0, RVMThread.IN_JAVA); asm.emitMOV_Reg_Imm(T1, RVMThread.IN_JNI); ThreadLocalState.emitCompareAndExchangeField( asm, Entrypoints.execStatusField.getOffset(), T1); // if success, skip the slow path call ForwardReference doneEnterJNIRef = asm.forwardJcc(Assembler.EQ); // fast path failed, make the call asm.emitCALL_Abs( Magic.getTocPointer().plus( Entrypoints.enterJNIBlockedFromJNIFunctionCallMethod.getOffset())); // OK - we reach here when we have set the state to IN_JNI doneEnterJNIRef.resolve(asm); // restore return values asm.emitPOP_Reg(T1); asm.emitPOP_Reg(T0); // reload native/C nonvolatile regs - saved in prolog for (FloatingPointMachineRegister r : NATIVE_NONVOLATILE_FPRS) { // TODO: we assume non-volatile will hold at most a double if (r instanceof XMM) { asm.emitMOVSD_Reg_RegInd((XMM)r, SP); } else { // NB this will fail for anything other than FPR0 asm.emitFLD_Reg_RegInd_Quad((FPR)r, SP); } asm.emitPOP_Reg(T0); // adjust space for double asm.emitPOP_Reg(T0); } // NB when EBP is restored it isn't our outer most EBP but rather than // nonvolatile push as the 1st instruction of the prologue for (int i=NATIVE_NONVOLATILE_GPRS.length-1; i >= 0; i--) { GPR r = NATIVE_NONVOLATILE_GPRS[i]; asm.emitPOP_Reg(r); } // Discard JNIEnv, CMID and outer most native frame pointer if (VM.BuildFor32Addr) { asm.emitADD_Reg_Imm(SP, 3*WORDSIZE); // discard current stack frame } else { asm.emitADD_Reg_Imm_Quad(SP, 3*WORDSIZE); // discard current stack frame } asm.emitRET(); // return to caller } }