/*
* 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;
import org.jikesrvm.ArchitectureSpecific.VM_CodeArray;
import org.jikesrvm.VM;
import org.jikesrvm.VM_SizeConstants;
import org.jikesrvm.memorymanagers.mminterface.MM_Interface;
import org.jikesrvm.runtime.VM_Magic;
import org.jikesrvm.scheduler.VM_Processor;
import org.vmmagic.pragma.Entrypoint;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.AddressArray;
import org.vmmagic.unboxed.LocalAddress;
import org.vmmagic.unboxed.ObjectReference;
/**
* A JNIEnvironment is created for each Java thread.
*/
public class VM_JNIEnvironment implements VM_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{VM_JNIFunctions}.
*/
private static VM_CodeArray[] 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.
*/
private static AddressArray[] LinkageTriplets;
/**
* Stash the JTOC somewhere we can find it later
* when we are making a C => Java transition.
* We mainly need this for OSX/Linux but it is also nice to have on AIX.
*/
@SuppressWarnings({"unused", "UnusedDeclaration"})
// used by native code
private final Address savedJTOC = VM.BuildForPowerPC ? VM_Magic.getTocPointer() : Address.zero();
/**
* 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 VM_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 ? VM_Magic.objectAsAddress(LinkageTriplets) : VM_Magic.objectAsAddress(JNIFunctions);
/**
* For saving processor register on entry to native,
* to be restored on JNI call from native
*/
@Entrypoint
protected VM_Processor savedPRreg;
/**
* true if the bottom stack frame is native,
* such as thread for CreateJVM or AttachCurrentThread
*/
protected boolean alwaysHasNativeFrame;
/**
* references passed to native code
*/
@Entrypoint
public AddressArray JNIRefs;
/**
* address 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
*/
@Entrypoint
public int JNIRefsSavedFP;
/**
* Top java frame when in C frames on top of the stack
*/
@Entrypoint
protected LocalAddress JNITopJavaFP;
/**
* Currently pending exception (null if none)
*/
@Entrypoint
protected Throwable pendingException;
/**
* We allocate VM_JNIEnvironments in the immortal heap (so we
* can hand them directly to C code). Therefore, we must do some
* kind of pooling of VM_JNIEnvironment instances.
* This is the free list of unused instances.
*/
protected VM_JNIEnvironment next;
/**
* Pool of available VM_JNIEnvironments.
*/
protected static VM_JNIEnvironment pool;
/**
* 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 VM_JNIEnvironment allocateEnvironment() {
VM_JNIEnvironment env;
if (pool != null) {
env = pool;
pool = pool.next;
} else {
env = new VM_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 VM_JNIEnvironment to deallocate
*/
public static synchronized void deallocateEnvironment(VM_JNIEnvironment env) {
env.next = pool;
pool = env;
}
/*
* accessor methods
*/
@Uninterruptible
public final boolean hasNativeStackFrame() {
return alwaysHasNativeFrame || JNIRefsTop != 0;
}
@Uninterruptible
public final LocalAddress topJavaFP() {
return JNITopJavaFP;
}
@Uninterruptible
public final AddressArray refsArray() {
return JNIRefs;
}
@Uninterruptible
public final int refsTop() {
return JNIRefsTop;
}
@Uninterruptible
public final int savedRefsFP() {
return JNIRefsSavedFP;
}
/**
* 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 final int pushJNIRef(Object ref) {
if (ref == null) {
return 0;
}
if (VM.VerifyAssertions) {
VM._assert(MM_Interface.validRef(ObjectReference.fromObject(ref)));
}
if ((JNIRefsTop >>> LOG_BYTES_IN_ADDRESS) >= JNIRefs.length()) {
VM.sysFail("unchecked pushes exceeded fudge length!");
}
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, VM_Magic.objectAsAddress(ref));
return JNIRefsTop;
}
/**
* Get a reference from the JNIRefs stack.
* @param offset in JNIRefs stack
* @return reference at that offset
*/
public final 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");
return null;
}
if (offset < 0) {
return VM_JNIGlobalRefTable.ref(offset);
} else {
return VM_Magic.addressAsObject(JNIRefs.get(offset >>> LOG_BYTES_IN_ADDRESS));
}
}
/**
* Remove a reference from the JNIRefs stack.
* @param offset in JNIRefs stack
*/
public final 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 final void dumpJniRefsStack() {
int jniRefOffset = JNIRefsTop;
VM.sysWrite("\n* * dump of JNIEnvironment JniRefs Stack * *\n");
VM.sysWrite("* JNIRefs = ");
VM.sysWrite(VM_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(VM_Magic.objectAsAddress(JNIRefs).plus(jniRefOffset));
VM.sysWrite(" ");
MM_Interface.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 final 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 final Throwable getException() {
return pendingException;
}
/**
* Initialize the array of JNI functions.
* This function is called during bootimage writing.
*/
public static void initFunctionTable(VM_CodeArray[] functions) {
JNIFunctions = functions;
if (VM.BuildForPowerOpenABI) {
// Allocate the linkage triplets in the bootimage too (so they won't move)
LinkageTriplets = new AddressArray[functions.length];
for (int i = 0; i < functions.length; i++) {
LinkageTriplets[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[i];
triplet.set(1, VM_Magic.getTocPointer());
triplet.set(0, VM_Magic.objectAsAddress(JNIFunctions[i]));
}
}
}
}