/* * 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; import org.jikesrvm.VM; import org.jikesrvm.SizeConstants; import org.jikesrvm.mm.mminterface.MemoryManager; import org.jikesrvm.classloader.RVMMethod; import org.jikesrvm.compilers.common.CompiledMethods; import org.jikesrvm.runtime.Magic; import org.jikesrvm.runtime.RuntimeEntrypoints; import org.jikesrvm.scheduler.RVMThread; import org.vmmagic.pragma.Entrypoint; import org.vmmagic.pragma.Inline; import org.vmmagic.pragma.NoInline; import org.vmmagic.pragma.Uninterruptible; import org.vmmagic.pragma.Unpreemptible; import org.vmmagic.pragma.Untraced; import org.vmmagic.unboxed.Address; import org.vmmagic.unboxed.AddressArray; import org.vmmagic.unboxed.ObjectReference; import org.vmmagic.unboxed.Offset; /** * A JNIEnvironment is created for each Java thread. */ public final class JNIEnvironment implements SizeConstants { /** * initial size for JNI refs, later grow as needed */ protected static final int JNIREFS_ARRAY_LENGTH = 100; /** * sometimes we put stuff onto the jnirefs array bypassing the code * that makes sure that it does not overflow (evil assembly code in the * jni stubs that would be painful to fix). So, we keep some space * between the max value in JNIRefsMax and the actual size of the * array. How much is governed by this field. */ protected static final int JNIREFS_FUDGE_LENGTH = 50; /** * This is the shared JNI function table used by native code * to invoke methods in @link{JNIFunctions}. */ public static FunctionTable JNIFunctions; /** * For the PowerOpenABI we need a linkage triple instead of just * a function pointer. * This is an array of such triples that matches JNIFunctions. */ public static LinkageTripletTable LinkageTriplets; /** * This is the pointer to the shared JNIFunction table. * When we invoke a native method, we adjust the pointer we * pass to the native code such that this field is at offset 0. * In other words, we turn a JNIEnvironment into a JNIEnv* * by handing the native code an interior pointer to * this object that points directly to this field. */ @SuppressWarnings({"unused", "UnusedDeclaration"}) // used by native code @Entrypoint private final Address externalJNIFunctions = VM.BuildForPowerOpenABI ? Magic.objectAsAddress(LinkageTriplets) : Magic.objectAsAddress(JNIFunctions); /** * For saving processor register on entry to native, * to be restored on JNI call from native */ @Entrypoint @Untraced protected RVMThread savedTRreg; /** * For saving JTOC register on entry to native, * to be restored on JNI call from native (only used on PowerPC) */ @Entrypoint @Untraced private final Address savedJTOC = VM.BuildForPowerPC ? Magic.getTocPointer() : Address.zero(); /** * When native code doesn't maintain a base pointer we can't chain * through the base pointers when walking the stack. This field * holds the basePointer on entry to the native code in such a case, * and is pushed onto the stack if we re-enter Java code (e.g. to * handle a JNI function). This field is currently only used on IA32. */ @Entrypoint private Address basePointerOnEntryToNative = Address.fromIntSignExtend(0xF00BAAA1); /** * When transitioning between Java and C and back, we may want to stop a thread * returning into Java and executing mutator code when a GC is in progress. * When in C code, the C code may never return. In these situations we need a * frame pointer at which to begin scanning the stack. This field holds this * value. NB. these fields don't chain together on the stack as we walk through * native frames by knowing their return addresses are outside of our heaps */ @Entrypoint protected Address JNITopJavaFP; /** * Currently pending exception (null if none) */ @Entrypoint @Untraced protected Throwable pendingException; /** * We allocate JNIEnvironments in the immortal heap (so we * can hand them directly to C code). Therefore, we must do some * kind of pooling of JNIEnvironment instances. * This is the free list of unused instances. */ protected JNIEnvironment next; /** * Pool of available JNIEnvironments. */ protected static JNIEnvironment pool; /** * true if the bottom stack frame is native, * such as thread for CreateJVM or AttachCurrentThread */ protected boolean alwaysHasNativeFrame; /** * references passed to native code */ @Entrypoint @Untraced public AddressArray JNIRefs; /** * Offset of current top ref in JNIRefs array */ @Entrypoint public int JNIRefsTop; /** * Offset of end (last entry) of JNIRefs array */ @Entrypoint protected int JNIRefsMax; /** * Previous frame boundary in JNIRefs array. * NB unused on IA32 */ @Entrypoint public int JNIRefsSavedFP; /** * Initialize a thread specific JNI environment. */ protected void initializeState() { JNIRefs = AddressArray.create(JNIREFS_ARRAY_LENGTH + JNIREFS_FUDGE_LENGTH); JNIRefsTop = 0; JNIRefsSavedFP = 0; JNIRefsMax = (JNIREFS_ARRAY_LENGTH - 1) << LOG_BYTES_IN_ADDRESS; alwaysHasNativeFrame = false; } /* * Allocation and pooling */ /** * Create a thread specific JNI environment. */ public static synchronized JNIEnvironment allocateEnvironment() { JNIEnvironment env; if (pool != null) { env = pool; pool = pool.next; } else { env = new JNIEnvironment(); } env.initializeState(); return env; } /** * Return a thread-specific JNI environment; must be called as part of * terminating a thread that has a JNI environment allocated to it. * @param env the JNIEnvironment to deallocate */ @Unpreemptible("Deallocate environment but may contend with environment being allocated") public static synchronized void deallocateEnvironment(JNIEnvironment env) { env.savedTRreg = null; /* make sure that we don't have a reference back to the thread, once the thread has died. */ env.next = pool; pool = env; } /* * accessor methods */ @Uninterruptible public boolean hasNativeStackFrame() { return alwaysHasNativeFrame || JNIRefsTop != 0; } @Uninterruptible public Address topJavaFP() { return JNITopJavaFP; } @Uninterruptible public AddressArray refsArray() { return JNIRefs; } @Uninterruptible public int refsTop() { return JNIRefsTop; } @Uninterruptible public int savedRefsFP() { return JNIRefsSavedFP; } /** * Check push of reference can succeed * @param ref object to be pushed * @param canGrow can the JNI reference array be grown? */ @Uninterruptible("May be called from uninterruptible code") @NoInline private void checkPush(Object ref, boolean canGrow) { final boolean debug=true; VM._assert(MemoryManager.validRef(ObjectReference.fromObject(ref))); if (JNIRefsTop < 0) { if (debug) { VM.sysWriteln("JNIRefsTop=", JNIRefsTop); VM.sysWriteln("JNIRefs.length=", JNIRefs.length()); } VM.sysFail("unchecked push to negative offset!"); } if ((JNIRefsTop >> LOG_BYTES_IN_ADDRESS) >= JNIRefs.length()) { if (debug) { VM.sysWriteln("JNIRefsTop=", JNIRefsTop); VM.sysWriteln("JNIRefs.length=", JNIRefs.length()); } VM.sysFail("unchecked pushes exceeded fudge length!"); } if (!canGrow) { if ((JNIRefsTop+BYTES_IN_ADDRESS) >= JNIRefsMax) { if (debug) { VM.sysWriteln("JNIRefsTop=", JNIRefsTop); VM.sysWriteln("JNIRefsMax=", JNIRefsMax); } VM.sysFail("unchecked push can't grow JNI refs!"); } } } /** * Push a reference onto thread local JNIRefs stack. * To be used by JNI functions when returning a reference * back to JNI native C code. * @param ref the object to put on stack * @return offset of entry in JNIRefs stack */ public int pushJNIRef(Object ref) { if (ref == null) { return 0; } else { if (VM.VerifyAssertions) checkPush(ref, true); JNIRefsTop += BYTES_IN_ADDRESS; if (JNIRefsTop >= JNIRefsMax) { JNIRefsMax *= 2; AddressArray newrefs = AddressArray.create((JNIRefsMax >> LOG_BYTES_IN_ADDRESS) + JNIREFS_FUDGE_LENGTH); for (int i = 0; i < JNIRefs.length(); i++) { newrefs.set(i, JNIRefs.get(i)); } JNIRefs = newrefs; } JNIRefs.set(JNIRefsTop >> LOG_BYTES_IN_ADDRESS, Magic.objectAsAddress(ref)); return JNIRefsTop; } } /** * Push a JNI ref, used on entry to JNI * NB only used for Intel * @param ref reference to place on stack or value of saved frame pointer * @param isRef false if the reference isn't a frame pointer */ @Uninterruptible("Encoding arguments on stack that won't be seen by GC") @Inline private int uninterruptiblePushJNIRef(Address ref, boolean isRef) { if (isRef && ref.isZero()) { return 0; } else { if (VM.VerifyAssertions) checkPush(isRef ? Magic.addressAsObject(ref) : null, false); // we count all slots so that releasing them is straight forward JNIRefsTop += BYTES_IN_ADDRESS; // ensure null is always seen as slot zero JNIRefs.set(JNIRefsTop >> LOG_BYTES_IN_ADDRESS, Magic.objectAsAddress(ref)); return JNIRefsTop; } } /** * Save data and perform necessary conversions for entry into JNI. * NB only used for Intel. * * @param encodedReferenceOffsets * bit mask marking which elements on the stack hold objects that need * encoding as JNI ref identifiers */ @Uninterruptible("Objects on the stack won't be recognized by GC, therefore don't allow GC") @Entrypoint public void entryToJNI(int encodedReferenceOffsets) { // Save processor savedTRreg = Magic.getThreadRegister(); // Save frame pointer of calling routine, once so that native stack frames // are skipped and once for use by GC Address callersFP = Magic.getCallerFramePointer(Magic.getFramePointer()); basePointerOnEntryToNative = callersFP; // NB old value saved on call stack JNITopJavaFP = callersFP; if (VM.traceJNI) { RVMMethod m= CompiledMethods.getCompiledMethod( Magic.getCompiledMethodID(callersFP)).getMethod(); VM.sysWrite("calling JNI from "); VM.sysWrite(m.getDeclaringClass().getDescriptor()); VM.sysWrite(" "); VM.sysWrite(m.getName()); VM.sysWrite(m.getDescriptor()); VM.sysWriteln(); } // Save current JNI ref stack pointer if (JNIRefsTop > 0) { uninterruptiblePushJNIRef(Address.fromIntSignExtend(JNIRefsSavedFP), false); JNIRefsSavedFP = JNIRefsTop; } // Convert arguments on stack from objects to JNI references Address fp = Magic.getFramePointer(); Offset argOffset = Offset.fromIntSignExtend(5*BYTES_IN_ADDRESS); fp.store(uninterruptiblePushJNIRef(fp.loadAddress(argOffset),true), argOffset); while (encodedReferenceOffsets != 0) { argOffset = argOffset.plus(BYTES_IN_ADDRESS); if ((encodedReferenceOffsets & 1) != 0) { fp.store(uninterruptiblePushJNIRef(fp.loadAddress(argOffset), true), argOffset); } encodedReferenceOffsets >>>= 1; } // Transition processor from IN_JAVA to IN_JNI RVMThread.enterJNIFromCallIntoNative(); } /** * Restore data, throw pending exceptions or convert return value for exit * from JNI. NB only used for Intel. * * @param offset * offset into JNI reference tables of result * @return Object encoded by offset or null if offset is 0 */ @Unpreemptible("Don't allow preemption when we're not in a sane state. " + "Code can throw exceptions so not uninterruptible.") @Entrypoint public Object exitFromJNI(int offset) { // Transition processor from IN_JNI to IN_JAVA RVMThread.leaveJNIFromCallIntoNative(); // Restore JNI ref top and saved frame pointer JNIRefsTop = 0; if (JNIRefsSavedFP > 0) { JNIRefsTop = JNIRefsSavedFP - BYTES_IN_ADDRESS; JNIRefsSavedFP = JNIRefs.get(JNIRefsSavedFP >> LOG_BYTES_IN_ADDRESS).toInt(); } // Throw and clear any pending exceptions Throwable pe = pendingException; if (pe != null) { pendingException = null; RuntimeEntrypoints.athrow(pe); // NB. we will never reach here } // Lookup result Object result; if (offset == 0) { result = null; } else if (offset < 0) { result = JNIGlobalRefTable.ref(offset); } else { result = Magic.addressAsObject(JNIRefs.get(offset >> LOG_BYTES_IN_ADDRESS)); } return result; } /** * Get a reference from the JNIRefs stack. * @param offset in JNIRefs stack * @return reference at that offset */ public Object getJNIRef(int offset) { if (offset > JNIRefsTop) { VM.sysWrite("JNI ERROR: getJNIRef for illegal offset > TOP, "); VM.sysWrite(offset); VM.sysWrite("(top is "); VM.sysWrite(JNIRefsTop); VM.sysWrite(")\n"); RVMThread.dumpStack(); return null; } if (offset < 0) { return JNIGlobalRefTable.ref(offset); } else { return Magic.addressAsObject(JNIRefs.get(offset >> LOG_BYTES_IN_ADDRESS)); } } /** * Remove a reference from the JNIRefs stack. * @param offset in JNIRefs stack */ public void deleteJNIRef(int offset) { if (offset > JNIRefsTop) { VM.sysWrite("JNI ERROR: getJNIRef for illegal offset > TOP, "); VM.sysWrite(offset); VM.sysWrite("(top is "); VM.sysWrite(JNIRefsTop); VM.sysWrite(")\n"); } JNIRefs.set(offset >> LOG_BYTES_IN_ADDRESS, Address.zero()); if (offset == JNIRefsTop) JNIRefsTop -= BYTES_IN_ADDRESS; } /** * Dump the JNIRefs stack to the sysWrite stream */ @Uninterruptible public void dumpJniRefsStack() { int jniRefOffset = JNIRefsTop; VM.sysWrite("\n* * dump of JNIEnvironment JniRefs Stack * *\n"); VM.sysWrite("* JNIRefs = "); VM.sysWrite(Magic.objectAsAddress(JNIRefs)); VM.sysWrite(" * JNIRefsTop = "); VM.sysWrite(JNIRefsTop); VM.sysWrite(" * JNIRefsSavedFP = "); VM.sysWrite(JNIRefsSavedFP); VM.sysWrite(".\n*\n"); while (jniRefOffset >= 0) { VM.sysWrite(jniRefOffset); VM.sysWrite(" "); VM.sysWrite(Magic.objectAsAddress(JNIRefs).plus(jniRefOffset)); VM.sysWrite(" "); MemoryManager.dumpRef(JNIRefs.get(jniRefOffset >> LOG_BYTES_IN_ADDRESS).toObjectReference()); jniRefOffset -= BYTES_IN_ADDRESS; } VM.sysWrite("\n* * end of dump * *\n"); } /** * Record an exception as pending so that it will be delivered on the return * to the Java caller; clear the exception by recording null * @param e An exception or error */ public void recordException(Throwable e) { // don't overwrite the first exception except to clear it if (pendingException == null || e == null) { pendingException = e; } } /** * @return the pending exception */ public Throwable getException() { return pendingException; } /** * Initialize the array of JNI functions. * This function is called during bootimage writing. */ public static void initFunctionTable(FunctionTable functions) { JNIFunctions = functions; if (VM.BuildForPowerOpenABI) { // Allocate the linkage triplets in the bootimage too (so they won't move) LinkageTriplets = LinkageTripletTable.allocate(functions.length()); for (int i = 0; i < functions.length(); i++) { LinkageTriplets.set(i, AddressArray.create(3)); } } } /** * Initialization required during VM booting; only does something if * we are on a platform that needs linkage triplets. */ public static void boot() { if (VM.BuildForPowerOpenABI) { // fill in the TOC and IP entries for each linkage triplet for (int i = 0; i < JNIFunctions.length(); i++) { AddressArray triplet = LinkageTriplets.get(i); triplet.set(1, Magic.getTocPointer()); triplet.set(0, Magic.objectAsAddress(JNIFunctions.get(i))); } } } }