/*
* Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.max.vm.jni;
import java.lang.ref.*;
import com.sun.cri.ci.*;
import com.sun.max.annotate.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.thread.*;
import com.sun.max.vm.type.*;
/**
* JNI handles are allocated for one of the following reasons:
*
* 1. Passing an object from Java code to native code as a parameter of a native method
* 2. Returning an object from a {@linkplain JniFunctions JNI function} to native code
*
* The first type of handle is implemented as the address of an object on the thread's stack.
* The second type of handle is allocated from a pool of JNI handles. There is one pool of
* JNI handles per thread that is used to allocate local JNI references, a
* global pool per VM (or isolate?) for global references and
* another global pool for weak global references.
*
* This class implements a pool of JNI handles.
*
* In the Maxine VM, we need to take into account that objects may be allocated
* in a hardware object memory where one cannot take the address of an element or field within
* an object (i.e. there can be no pointers into the middle of such objects).
*
* This leaves us with these choices for implementing JNI handle pools:
*
* 1. Store object references inside malloc'ed (i.e. non-object) data structures.
* Pros: - JNI handles can be treated uniformly as the address of an object reference
* which allows for the fastest and simplest implementation of handle dereferencing.
* Cons: - Garbage collection needs to be specialized to include the handles as roots. Also
* - Any malloc'ed data structures must be manually deallocated.
* - Manipulating non-object data structures requires peeking and poking as opposed
* to normal Java array accessing.
*
* 2. Store object references inside standard Java objects (i.e. arrays).
* Pros: - No need to use malloc or specialize the garbage collectors.
* - No special code for deallocating apart from clearing references.
* - Manipulating objects can be expressed in standard Java code (i.e. array
* indexing expressions).
* Cons: - JNI handles require different formats to distinguish between an index into a
* pool and a double indirected pointer (i.e. a wrapped native method parameter).
* The format for an indexed JNI handle must further distinguish between the current
* thread's pool of local references, the global reference pool and the global
* weak reference pool. The code for dereferencing a JNI handle must take into
* account all of these formats.
*
* 3. Other choices?
*
* Given the above trade offs between 1 and 2 (and the current non-existence of 3!), we
* have decided to go with choice 2 until it proves too complex and/or inefficient.
*/
public final class JniHandles {
public static final class Tag {
public static final int STACK = 0;
public static final int LOCAL = 1;
public static final int GLOBAL = 2;
public static final int WEAK_GLOBAL = 3;
public static final int MASK = 3;
public static final int BITS = 2;
}
static final class Frame {
final int start;
final Frame previous;
public Frame(int start, Frame previous) {
this.start = start;
this.previous = previous;
}
}
public static final int INITIAL_NUMBER_OF_HANDLES = 32;
private static final JniHandles globalHandles = new JniHandles();
private static final JniHandles weakGlobalHandles = new JniHandles();
/**
* The objects exposed to native code via handles.
*
* Note that this representation simplifies handle allocation and dereferencing
* (in the context of a "JNI handle is an index" implementation)
* at the cost of expanding an array (allocation plus copy). It will be replaced
* with a better representation if this expansion cost proves to be too high.
*/
private Object[] handles = new Object[INITIAL_NUMBER_OF_HANDLES];
/**
* The frames pushed by {@link JniFunctions#PushLocalFrame}.
*/
private Frame frames;
/**
* Denotes the indexes of handles that have been {@linkplain #freeHandle(int) freed}.
* No bit will be set for a handle at an index >= {@link #top}.
*/
private final CiBitMap freedHandles = new CiBitMap(INITIAL_NUMBER_OF_HANDLES);
/**
* The index at which the next search for a free bit in {@link #freedHandles} starts.
* This optimizes allocation where the expected behavior of a native method
* that deletes local references is such that these references are deleted in the
* reverse order in which they were created.
*/
private int lastFreedIndex;
/**
* Number of handles allocated from this pool that are (potentially) still in use.
* This value also denotes the index of next unused handle in {@link #handles}.
* The name of this field also gives some indication of how handles can be allocated
* and freed in a stack like fashion.
*
* Invariant: All elements in {@link #handles} at an index greater than or equal to {@link #top} are null.
*/
private int top;
/**
* Return the "top" (i.e. current size) of this handle pool. This value can be given
* as the parameter to the {@link #resetTop(int)} method to free handles in a stack
* like fashion.
*/
@INLINE
public int top() {
return top;
}
/**
* Resets the "top" (i.e. current size) of this handle pool to a value
* equal to or less than its current size.
*/
@NO_SAFEPOINT_POLLS("cannot stop before pending exception in JNI stub has been processed")
public void resetTop(int newTop) {
if (newTop > this.top || newTop < 0) {
FatalError.unexpected("Cannot reset JNI handle stack to higher stack height");
}
if (newTop != this.top) {
for (int i = newTop; i != this.top; ++i) {
handles[i] = null;
}
int freeLength = freedHandles.length();
if (freeLength >= newTop) {
for (int i = freedHandles.nextSetBit(newTop, freeLength); i != -1; i = freedHandles.nextSetBit(i, freeLength)) {
freedHandles.clear(i);
}
}
lastFreedIndex = 0;
this.top = newTop;
}
}
/**
* Gets the handle at a given index.
*/
private Object get(int index) {
return handles[index];
}
/**
* Frees the JNI handle at a given index from this pool.
*
* @param index the index of the handle to free
*/
private void freeHandle(int index) {
handles[index] = null;
freedHandles.grow(index + 1);
freedHandles.set(index);
lastFreedIndex = index;
}
private static Object[] expandHandles(Object[] handles, int newLength) {
final int currentLength = handles.length;
final Object[] newHandles = new Object[newLength];
// Can't use System.arraycopy - it's a native method which may require allocating JNI handles!
for (int i = 0; i != currentLength; ++i) {
newHandles[i] = handles[i];
}
return newHandles;
}
private JniHandle allocateHandle(Object object, int tag) {
assert object != null;
// Try to get a handle from the logical end of the array
if (top < handles.length) {
assert handles[top] == null;
handles[top] = object;
return indexToJniHandle(top++, tag);
}
// Now look for a handle in the free set
int index = freedHandles.nextSetBit(lastFreedIndex);
if (index == -1 && lastFreedIndex != 0) {
// Wrap around and search from the beginning of the array
index = freedHandles.nextSetBit(0);
}
if (index != -1) {
assert handles[index] == null;
handles[index] = object;
freedHandles.clear(index);
return indexToJniHandle(index, tag);
}
// No space available, the handle array is expanded
// to be double its current size.
handles = expandHandles(handles, handles.length * 2);
// Retry - guaranteed to succeed
return allocateHandle(object, tag);
}
private static JniHandle indexToJniHandle(int index, int tag) {
return Address.fromInt(index << Tag.BITS | tag).asJniHandle();
}
private static int jniHandleToIndex(JniHandle jniHandle) {
return jniHandle.asOffset().toInt() >> Tag.BITS;
}
public static int tag(JniHandle jniHandle) {
return jniHandle.asOffset().toInt() & Tag.MASK;
}
private void pushFrame(int capacity) {
ensureCapacity(capacity);
frames = new Frame(top, frames);
}
private JniHandle popFrame(JniHandle result) {
final Object object = get(result);
// This test means PopLocalFrame will work even if there was
// not a corresponding call to PushLocalFrame
if (frames != null) {
resetTop(frames.start);
frames = frames.previous;
}
return (object != null) ? allocateHandle(object, Tag.LOCAL) : result;
}
/**
* Ensures that <i>at least</i> a given number of local references can be created in this pool of handles.
*/
private void ensureCapacity(int capacity) {
final int available = (handles.length - top) + freedHandles.cardinality();
final int extraNeeded = capacity - available;
if (extraNeeded > 0) {
handles = expandHandles(handles, handles.length + extraNeeded);
}
}
/**
* Extracts the object from a given JNI handle. This will return null if {@code jniHandle.isZero() == true}.
*/
public static Object get(JniHandle jniHandle) {
final int tag = tag(jniHandle);
if (tag == Tag.STACK) {
if (jniHandle.isZero()) {
return null;
}
return jniHandle.asPointer().getReference().toJava();
}
if (tag == Tag.LOCAL) {
JniHandles jniHandles = VmThread.current().jniHandles();
return jniHandles.get(jniHandleToIndex(jniHandle));
}
if (tag == Tag.GLOBAL) {
return globalHandles.get(jniHandleToIndex(jniHandle));
}
assert tag == Tag.WEAK_GLOBAL;
final WeakReference weakReference = (WeakReference) weakGlobalHandles.get(jniHandleToIndex(jniHandle));
return weakReference == null ? null : weakReference.get();
}
public static Address getAddress(JniHandle jniHandle) {
assert tag(jniHandle) == Tag.STACK;
return jniHandle.asPointer().getWord().asAddress();
}
/**
* Creates a thread-local JNI handle for a reference. The handle is valid only within the
* dynamic context of the native method that creates it, and only within that one invocation
* of the native method. All local references created during the execution of a native method
* will be freed once the native method returns.
*
* @param jniHandles the local handles for the current thread
* @param object the object to handlize
*/
public static JniHandle createLocalHandle(JniHandles jniHandles, Object object) {
return jniHandles.allocateHandle(object, Tag.LOCAL);
}
/**
* Creates a thread-local JNI handle for a reference. The handle is valid only within the
* dynamic context of the native method that creates it, and only within that one invocation
* of the native method. All local references created during the execution of a native method
* will be freed once the native method returns.
*
* @param object the object to handlize
*/
public static JniHandle createLocalHandle(Object object) {
if (object == null) {
return JniHandle.zero();
}
return VmThread.current().createLocalHandle(object);
}
public static JniHandle createGlobalHandle(Object object) {
if (object == null) {
return JniHandle.zero();
}
synchronized (globalHandles) {
return globalHandles.allocateHandle(object, Tag.GLOBAL);
}
}
public static JniHandle createWeakGlobalHandle(Object object) {
if (object == null) {
return JniHandle.zero();
}
synchronized (weakGlobalHandles) {
return weakGlobalHandles.allocateHandle(new WeakReference<Object>(object), Tag.WEAK_GLOBAL);
}
}
public static void destroyLocalHandle(JniHandle jniHandle) {
if (!jniHandle.isZero()) {
final int tag = tag(jniHandle);
if (tag != Tag.STACK) {
assert tag == Tag.LOCAL;
JniHandles jniHandles = VmThread.current().jniHandles();
if (jniHandles == null) {
throw new IllegalArgumentException("invalid JNI handle: " + jniHandle.to0xHexString());
}
jniHandles.freeHandle(jniHandleToIndex(jniHandle));
} else {
// Handles to references on a thread's stack are automatically freed
// as these handles are also on the stack
}
}
}
public static void destroyGlobalHandle(JniHandle jniHandle) {
if (!jniHandle.isZero()) {
assert tag(jniHandle) == Tag.GLOBAL;
synchronized (globalHandles) {
globalHandles.freeHandle(jniHandleToIndex(jniHandle));
}
}
}
public static void destroyWeakGlobalHandle(JniHandle jniHandle) {
if (!jniHandle.isZero()) {
assert tag(jniHandle) == Tag.WEAK_GLOBAL;
synchronized (weakGlobalHandles) {
weakGlobalHandles.freeHandle(jniHandleToIndex(jniHandle));
}
}
}
public static void ensureLocalHandleCapacity(int capacity) {
VmThread.current().makeJniHandles().ensureCapacity(capacity);
}
public static void pushLocalFrame(int capacity) {
VmThread.current().makeJniHandles().pushFrame(capacity);
}
public static JniHandle popLocalFrame(JniHandle result) {
JniHandles jniHandles = VmThread.current().jniHandles();
if (jniHandles == null) {
if (!result.isZero()) {
throw new IllegalArgumentException("invalid JNI handle: " + result.to0xHexString());
}
return JniHandle.zero();
}
return jniHandles.popFrame(result);
}
/**
* Gets the number of object parameters in a given signature.
*
* This method is compile-time evaluated so that the first parameter to
* {@link Intrinsics#alloca(int, boolean)} is a compile-time constant.
*/
@FOLD
public static int handlesCount(SignatureDescriptor sig) {
int res = 0;
for (int i = 0; i < sig.numberOfParameters(); i++) {
if (sig.parameterDescriptorAt(i).toKind().isReference) {
res++;
}
}
return res;
}
/**
* Gets a handle for an object.
*
* @param handles the address of a handles block (i.e. a block of memory containing object references)
* @param offset the offset of {@code value} in the handles block
* @param value an object value in the handles block
* @return if {@code value == null} then {@code 0} else {@code stackHandles.plus(offset)}
*/
@INLINE
public static Pointer getHandle(Pointer handles, int offset, Object value) {
return (value == null) ? Pointer.zero() : handles.plus(offset);
}
}