/*
* 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 static org.jikesrvm.runtime.UnboxedSizeConstants.BYTES_IN_ADDRESS;
import static org.jikesrvm.runtime.UnboxedSizeConstants.LOG_BYTES_IN_ADDRESS;
import org.jikesrvm.VM;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.jikesrvm.mm.mminterface.MemoryManager;
import org.jikesrvm.runtime.BootRecord;
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.NonMoving;
import org.vmmagic.pragma.NonMovingAllocation;
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.
*/
@NonMoving
public final class JNIEnvironment {
/**
* initial size for JNI refs, later grow as needed
*/
protected static final int JNIREFS_ARRAY_LENGTH = 100;
/**
* Sometimes we put stuff onto the {@link #JNIRefs} array bypassing the code
* that makes sure that it does not overflow (evil assembly code in the
* PowerPC 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 = VM.BuildForPowerPC ? 50 : 0;
/**
* This is the shared JNI function table used by native code
* to invoke methods in @link{JNIFunctions}.
*/
@Untraced // because bootloader code must be able to access it
public static FunctionTable JNIFunctions;
/**
* For the 64-bit PowerPC ELF ABI 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(fieldMayBeFinal = true)
private final Address externalJNIFunctions =
VM.BuildForPower64ELF_ABI ? 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 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
private Address JNITopJavaFP;
/**
* Currently pending exception (null if none)
*/
private Throwable pendingException;
@SuppressWarnings("unused")
@Entrypoint
private int hasPendingException;
/**
* true if the bottom stack frame is native,
* such as thread for CreateJVM or AttachCurrentThread
*/
private final boolean alwaysHasNativeFrame;
/**
* references passed to native code
*/
@Entrypoint
@Untraced
private AddressArray JNIRefs;
private AddressArray JNIRefsShadow;
/**
* Offset of current top ref in JNIRefs array
*/
@Entrypoint
private int JNIRefsTop;
/**
* Offset of end (last entry) of JNIRefs array
*/
@Entrypoint
private int JNIRefsMax;
/**
* Previous frame boundary in JNIRefs array.
* NB unused on IA32
*/
@Entrypoint
private int JNIRefsSavedFP;
/**
* Initialize a thread specific JNI environment.
*/
public JNIEnvironment() {
JNIRefs = JNIRefsShadow = createArrayForJNIRefs(JNIREFS_ARRAY_LENGTH);
JNIRefsTop = 0;
JNIRefsSavedFP = 0;
adjustJNIRefsMaxForNewArrayLength();
alwaysHasNativeFrame = false;
}
/**
* Creates an address array for use in {@link #JNIRefs}.
* <p>
* This has to be in a separate method to ensure that the address array
* is allocated into a non-moving space. The array has to be non-movable
* because it is accessed directly from native code.
*
* @param arrayLengthWithoutFudge the desired array length (excluding
* the fudge length {@link #JNIREFS_FUDGE_LENGTH})
* @return an address array to be used for JNI references
*/
@NonMovingAllocation
private AddressArray createArrayForJNIRefs(int arrayLengthWithoutFudge) {
return AddressArray.create(arrayLengthWithoutFudge + JNIREFS_FUDGE_LENGTH);
}
private void adjustJNIRefsMaxForNewArrayLength() {
JNIRefsMax = (JNIRefs.length() - JNIREFS_FUDGE_LENGTH - 1) << LOG_BYTES_IN_ADDRESS;
}
/*
* 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;
if (VM.VerifyAssertions) {
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) {
int arrayLengthWithoutFudge = 2 * (JNIRefs.length() - JNIREFS_FUDGE_LENGTH);
replaceJNIRefs(createArrayForJNIRefs(arrayLengthWithoutFudge));
adjustJNIRefsMaxForNewArrayLength();
}
JNIRefs.set(JNIRefsTop >> LOG_BYTES_IN_ADDRESS, Magic.objectAsAddress(ref));
return JNIRefsTop;
}
}
/**
* Atomically copies and installs a new JNIRefArray.
*
* @param newrefs the new JNIRefArray
*/
@Uninterruptible
private void replaceJNIRefs(AddressArray newrefs) {
for (int i = 0; i < JNIRefs.length(); i++) {
newrefs.set(i, JNIRefs.get(i));
}
JNIRefs = JNIRefsShadow = newrefs;
}
/**
* 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
* @return new offset of current top in JNIRefs array or 0 if
* the reference is zero and a framepointer
*/
@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, 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
if (pendingException != null) {
throwPendingException();
}
// 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 == 0) {
return null;
} else if (offset < 0) {
return JNIGlobalRefTable.ref(offset);
} else {
if (offset > JNIRefsTop) {
VM.sysWrite("JNI ERROR: getJNIRef for illegal offset > TOP, ");
VM.sysWrite(offset);
VM.sysWrite("(top is ");
VM.sysWrite(JNIRefsTop);
VM.sysWriteln(")");
if (VM.VerifyAssertions) {
VM.sysFail("getJNIRef called with illegal offset > TOP (see above)");
} else {
RVMThread.dumpStack();
}
return null;
}
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.sysWriteln(")");
}
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.sysWriteln();
VM.sysWriteln("* * dump of JNIEnvironment JniRefs Stack * *");
VM.sysWrite("* JNIRefs = ");
VM.sysWrite(Magic.objectAsAddress(JNIRefs));
VM.sysWrite(" * JNIRefsTop = ");
VM.sysWrite(JNIRefsTop);
VM.sysWrite(" * JNIRefsSavedFP = ");
VM.sysWrite(JNIRefsSavedFP);
VM.sysWriteln(".");
VM.sysWriteln("*");
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.sysWriteln();
VM.sysWriteln("* * end of dump * *");
}
/**
* 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;
hasPendingException = (e != null) ? 1 : 0;
}
}
/**
* Return and clear the (known to be non-null) pending exception.
*/
@Entrypoint
@Unpreemptible
public static void throwPendingException() {
JNIEnvironment me = RVMThread.getCurrentThread().getJNIEnv();
if (VM.VerifyAssertions) VM._assert(me.pendingException != null);
Throwable pe = me.pendingException;
me.pendingException = null;
me.hasPendingException = 0;
RuntimeEntrypoints.athrow(pe);
}
/**
* @return the pending exception
*/
public Throwable getException() {
return pendingException;
}
/**
* Initialize the array of JNI functions.
* This function is called during bootimage writing.
*
* @param functions the function table to initialize
*/
public static void initFunctionTable(FunctionTable functions) {
JNIFunctions = functions;
BootRecord.the_boot_record.JNIFunctions = functions;
if (VM.BuildForPower64ELF_ABI) {
// 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.BuildForPower64ELF_ABI) {
// 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)));
}
}
}
}