/*
* 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 static com.sun.max.vm.classfile.constant.PoolConstantFactory.*;
import static com.sun.max.vm.classfile.constant.SymbolTable.*;
import com.sun.max.io.*;
import com.sun.max.program.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.holder.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.bytecode.graft.*;
import com.sun.max.vm.classfile.*;
import com.sun.max.vm.classfile.constant.*;
import com.sun.max.vm.log.*;
import com.sun.max.vm.runtime.*;
import com.sun.max.vm.thread.*;
import com.sun.max.vm.type.*;
import com.sun.max.vm.jni.JniFunctions.JxxFunctionsLogger;
/**
* A utility class for generating bytecode that implements the transition
* from Java to native code. Most of these transitions will made for calling a native function via JNI.
* However, faster transitions to MaxineVM specific native code is also supported.
* The steps performed by a generated stub are:
* <p>
* <ol>
* <li>Record the {@linkplain JniHandles#top() top} of {@linkplain VmThread#jniHandles() the current thread's JNI handle stack}.</li>
* <li>Push the pointer to the {@linkplain VmThread#jniEnv() current thread's native JNI environment data structure}.</li>
* <li>If the native method is static, handlize and push the class reference
* otherwise handlize and push the receiver reference.</li>
* <li>Push the remaining parameters, handlizing non-null references before they are pushed.</li>
* <li>Save last Java frame info (stack, frame and instruction pointers) from thread local storage (TLS) to
* local variables and then update the TLS info to reflect the frame of the native stub.
* <li>Invoke the native function via a Maxine VM specific bytecode which also handles resolving the native function.
* The native function symbol is generated by {@linkplain Mangle mangling} the name and signature of the native method appropriately.</li>
* <li>Set the last Java instruction pointer in TLS to zero to indicate transition back into Java code.
* <li>If the native method returns a reference, {@linkplain JniHandle#unhand() unwrap} the returned handle.</li>
* <li>Restore the JNI frame as recorded in the first step.</li>
* <li>Throw any {@linkplain VmThread#throwJniException() pending exception} (if any) for the current thread.</li>
* <li>Return the result to the caller.</li>
* </ol>
* <p>
*/
public final class NativeStubGenerator extends BytecodeAssembler {
public NativeStubGenerator(ConstantPoolEditor constantPoolEditor, ClassMethodActor classMethodActor) {
super(constantPoolEditor);
this.classMethodActor = classMethodActor;
allocateParameters(classMethodActor.isStatic(), classMethodActor.descriptor());
generateCode(classMethodActor.isCFunction(), classMethodActor.isStatic(), classMethodActor.holder(), classMethodActor.descriptor());
}
private final SeekableByteArrayOutputStream codeStream = new SeekableByteArrayOutputStream();
private final ClassMethodActor classMethodActor;
@Override
public void writeByte(byte b) {
codeStream.write(b);
}
@Override
protected void setWriteBCI(int bci) {
codeStream.seek(bci);
}
@Override
public byte[] code() {
fixup();
return codeStream.toByteArray();
}
public CodeAttribute codeAttribute() {
return new CodeAttribute(constantPool(),
code(),
(char) maxStack(),
(char) maxLocals(),
CodeAttribute.NO_EXCEPTION_HANDLER_TABLE,
LineNumberTable.EMPTY,
LocalVariableTable.EMPTY,
null);
}
/**
* These methods may be called from a generated native stub.
*/
private static final ClassMethodRefConstant jniEnv = createClassMethodConstant(VmThread.class, makeSymbol("jniEnv"));
private static final ClassMethodRefConstant currentThread = createClassMethodConstant(VmThread.class, makeSymbol("current"));
private static final ClassMethodRefConstant handlesCount = createClassMethodConstant(JniHandles.class, makeSymbol("handlesCount"), SignatureDescriptor.class);
private static final ClassMethodRefConstant throwJniException = createClassMethodConstant(VmThread.class, makeSymbol("throwJniException"));
private static final ClassMethodRefConstant getHandle = createClassMethodConstant(JniHandles.class, makeSymbol("getHandle"), Pointer.class, int.class, Object.class);
private static final ClassMethodRefConstant alloca = createClassMethodConstant(Intrinsics.class, makeSymbol("alloca"), int.class, boolean.class);
private static final ClassMethodRefConstant unhandHandle = createClassMethodConstant(JniHandle.class, makeSymbol("unhand"));
private static final ClassMethodRefConstant handlesTop = createClassMethodConstant(VmThread.class, makeSymbol("jniHandlesTop"));
private static final ClassMethodRefConstant resetHandlesTop = createClassMethodConstant(VmThread.class, makeSymbol("resetJniHandlesTop"), int.class);
private static final FieldRefConstant jniLogger = createFieldConstant(JniFunctions.class, makeSymbol("logger"));
private static final FieldRefConstant downCallEntry = createFieldConstant(JxxFunctionsLogger.class, makeSymbol("DOWNCALL_ENTRY"));
private static final FieldRefConstant downCallExit = createFieldConstant(JxxFunctionsLogger.class, makeSymbol("DOWNCALL_EXIT"));
private static final ClassMethodRefConstant toWord = createClassMethodConstant(Address.class, makeSymbol("fromInt"), int.class);
private static final ClassMethodRefConstant log2 = createClassMethodConstant(VMLogger.class, makeSymbol("log"), int.class, Word.class, Word.class);
private static final ClassMethodRefConstant enabled = createClassMethodConstant(VMLogger.class, makeSymbol("enabled"));
private static final ClassMethodRefConstant link = createClassMethodConstant(NativeFunction.class, makeSymbol("link"));
private static final ClassMethodRefConstant nativeCallPrologue = createClassMethodConstant(Snippets.class, makeSymbol("nativeCallPrologue"), NativeFunction.class);
private static final ClassMethodRefConstant nativeCallPrologueForC = createClassMethodConstant(Snippets.class, makeSymbol("nativeCallPrologueForC"), NativeFunction.class);
private static final ClassMethodRefConstant nativeCallEpilogue = createClassMethodConstant(Snippets.class, makeSymbol("nativeCallEpilogue"));
private static final ClassMethodRefConstant nativeCallEpilogueForC = createClassMethodConstant(Snippets.class, makeSymbol("nativeCallEpilogueForC"));
private static final ClassMethodRefConstant writeObject = createClassMethodConstant(Pointer.class, makeSymbol("writeObject"), int.class, Object.class);
private int methodIDAsInt;
/**
* Allocates a block of handles and copies the object arguments into the block.
*
* @param sig the signature determining how many object handles are needed
* @return the index of the local variable holding the address of the handles block
*/
private int initializeHandles(SignatureDescriptor sig, boolean isStatic) {
int handles = allocateLocal(Kind.WORD);
int handleOffset = 0;
ldc(createObjectConstant(sig));
invokestatic(handlesCount, 1, 1);
iconst(1); // add 1 for the receiver/class argument
iadd();
iconst(Word.size()); // Multiply by the size of a word
imul();
iconst(1);
invokestatic(alloca, 2, 1);
astore(handles);
aload(handles);
iconst(handleOffset);
if (isStatic) {
// Push the class for a static method
ldc(createClassConstant(classMethodActor.holder().toJava()));
} else {
// Push the receiver for a non-static method
aload(0);
}
invokevirtual(writeObject, 3, 0);
handleOffset += Word.size();
// Store the reference parameters in JNI handles
int javaIndex = isStatic ? 0 : 1;
for (int i = 0; i < sig.numberOfParameters(); i++) {
final TypeDescriptor parameterDescriptor = sig.parameterDescriptorAt(i);
Kind kind = parameterDescriptor.toKind();
if (kind.isReference) {
aload(handles);
iconst(handleOffset);
aload(javaIndex);
invokevirtual(writeObject, 3, 0);
handleOffset += Word.size();
}
javaIndex += kind.stackSlots;
}
return handles;
}
private void generateCode(boolean isCFunction, boolean isStatic, ClassActor holder, SignatureDescriptor sig) {
final TypeDescriptor resultDescriptor = sig.resultDescriptor();
final Kind resultKind = resultDescriptor.toKind();
final StringBuilder nativeFunctionDescriptor = new StringBuilder("(");
int nativeFunctionArgSlots = 0;
final TypeDescriptor nativeResultDescriptor = resultKind.isReference ? JavaTypeDescriptor.JNI_HANDLE : resultDescriptor;
int top = 0;
int currentThread = -1;
int handles = -1;
int handleOffset = 0;
if (!isCFunction) {
handles = initializeHandles(sig, isStatic);
// Cache current thread in a local variable
invokestatic(NativeStubGenerator.currentThread, 0, 1);
currentThread = allocateLocal(Kind.REFERENCE);
astore(currentThread);
methodIDAsInt = MethodID.fromMethodActor(classMethodActor).asAddress().toInt();
logJniEntry();
// Save current JNI frame.
top = allocateLocal(Kind.INT);
aload(currentThread);
invokevirtual(handlesTop, 1, 1);
istore(top);
// Push the JNI environment variable
invokestatic(jniEnv, 0, 1);
final TypeDescriptor jniEnvDescriptor = jniEnv.signature(constantPool()).resultDescriptor();
nativeFunctionDescriptor.append(jniEnvDescriptor);
nativeFunctionArgSlots += jniEnvDescriptor.toKind().stackSlots;
// Push the handle for the receiver/class
assert handleOffset == 0;
aload(handles);
handleOffset += Word.size();
nativeFunctionDescriptor.append(JavaTypeDescriptor.WORD);
nativeFunctionArgSlots += Kind.WORD.stackSlots;
} else {
assert isStatic;
}
// Push the remaining parameters, wrapping reference parameters in JNI handles
int parameterLocalIndex = isStatic ? 0 : 1;
for (int i = 0; i < sig.numberOfParameters(); i++) {
final TypeDescriptor parameterDescriptor = sig.parameterDescriptorAt(i);
TypeDescriptor nativeParameterDescriptor = parameterDescriptor;
switch (parameterDescriptor.toKind().asEnum) {
case BYTE:
case BOOLEAN:
case SHORT:
case CHAR:
case INT: {
iload(parameterLocalIndex);
break;
}
case FLOAT: {
fload(parameterLocalIndex);
break;
}
case LONG: {
lload(parameterLocalIndex);
++parameterLocalIndex;
break;
}
case DOUBLE: {
dload(parameterLocalIndex);
++parameterLocalIndex;
break;
}
case WORD: {
aload(parameterLocalIndex);
break;
}
case REFERENCE: {
assert !isCFunction;
aload(handles);
iconst(handleOffset);
aload(parameterLocalIndex);
invokestatic(getHandle, 3, 1);
handleOffset += Word.size();
nativeParameterDescriptor = JavaTypeDescriptor.JNI_HANDLE;
break;
}
case VOID: {
throw ProgramError.unexpected();
}
}
nativeFunctionDescriptor.append(nativeParameterDescriptor);
nativeFunctionArgSlots += nativeParameterDescriptor.toKind().stackSlots;
++parameterLocalIndex;
}
// Link native function
ObjectConstant nf = createObjectConstant(classMethodActor.nativeFunction);
ldc(nf);
invokevirtual(link, 1, 1);
if (NativeInterfaces.needsPrologueAndEpilogue(classMethodActor)) {
ldc(nf);
invokestatic(!isCFunction ? nativeCallPrologue : nativeCallPrologueForC, 1, 0);
}
// Invoke the native function
callnative(SignatureDescriptor.create(nativeFunctionDescriptor.append(')').append(nativeResultDescriptor).toString()), nativeFunctionArgSlots, nativeResultDescriptor.toKind().stackSlots);
if (NativeInterfaces.needsPrologueAndEpilogue(classMethodActor)) {
invokestatic(!isCFunction ? nativeCallEpilogue : nativeCallEpilogueForC, 0, 0);
}
if (!isCFunction) {
// Unwrap a reference result from its enclosing JNI handle. This must be done
// *before* the JNI frame is restored.
if (resultKind.isReference) {
invokevirtual(unhandHandle, 1, 1);
}
// Restore JNI frame.
aload(currentThread);
iload(top);
invokevirtual(resetHandlesTop, 2, 0);
logJniExit();
// throw (and clear) any pending exception
aload(currentThread);
invokevirtual(throwJniException, 1, 0);
}
// Return result
if (resultKind.isReference) {
assert !isCFunction;
// Insert cast if return type is not java.lang.Object
if (resultDescriptor != JavaTypeDescriptor.OBJECT) {
checkcast(createClassConstant(resultDescriptor));
}
}
return_(resultKind);
}
private void logJni(FieldRefConstant callType) {
getstatic(jniLogger);
invokevirtual(enabled, 1, 1);
final Label noTracing = newLabel();
ifeq(noTracing);
getstatic(jniLogger);
ldc(PoolConstantFactory.createIntegerConstant(JniFunctions.LogOperations.NativeMethodCall.ordinal()));
getstatic(callType);
ldc(PoolConstantFactory.createIntegerConstant(methodIDAsInt));
invokestatic(toWord, 1, 1);
invokevirtual(log2, 4, 0);
noTracing.bind();
}
private void logJniEntry() {
logJni(downCallEntry);
}
private void logJniExit() {
logJni(downCallExit);
}
}