/* * Copyright (c) 2003 Sun Microsystems, Inc. All Rights Reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistribution of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistribution in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * Neither the name of Sun Microsystems, Inc. or the names of * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * This software is provided "AS IS," without a warranty of any kind. ALL * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, * INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A * PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN * MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR * ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR * DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR * ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR * DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE * DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, * ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF * SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. * * You acknowledge that this software is not designed or intended for use * in the design, construction, operation or maintenance of any nuclear * facility. * * Sun gratefully acknowledges that this software was originally authored * and developed by Kenneth Bradley Russell and Christopher John Kline. */ package com.sun.gluegen; import java.util.*; import java.io.*; import java.text.MessageFormat; import com.sun.gluegen.cgram.types.*; /** Emits the C-side component of the Java<->C JNI binding. */ public class CMethodBindingEmitter extends FunctionEmitter { protected static final CommentEmitter defaultCommentEmitter = new DefaultCommentEmitter(); protected static final String arrayResLength = "_array_res_length"; protected static final String arrayRes = "_array_res"; protected static final String arrayIdx = "_array_idx"; protected MethodBinding binding; /** Name of the package in which the corresponding Java method resides.*/ private String packageName; /** Name of the class in which the corresponding Java method resides.*/ private String className; /** * Whether or not the Java<->C JNI binding for this emitter's MethodBinding * is overloaded. */ private boolean isOverloadedBinding; /** * Whether or not the Java-side of the Java<->C JNI binding for this * emitter's MethodBinding is static. */ private boolean isJavaMethodStatic; // Flags which change various aspects of glue code generation protected boolean forImplementingMethodCall; protected boolean forIndirectBufferAndArrayImplementation; /** * Optional List of Strings containing temporary C variables to declare. */ private List/**/ temporaryCVariableDeclarations; /** * Optional List of Strings containing assignments to temporary C variables * to make after the call is completed. */ private List/**/ temporaryCVariableAssignments; /** * Capacity of the return value in the event that it is encapsulated in a * java.nio.Buffer. Is ignored if binding.getJavaReturnType().isNIOBuffer() * == false; */ private MessageFormat returnValueCapacityExpression = null; /** * Length of the returned array. Is ignored if * binding.getJavaReturnType().isArray() is false. */ private MessageFormat returnValueLengthExpression = null; // Note: the VC++ 6.0 compiler emits hundreds of warnings when the // (necessary) null-checking code is enabled. This appears to just // be a compiler bug, but would be good to track down exactly why it // is happening. When the null checking is enabled for just the // GetPrimitiveArrayCritical calls, there are five warnings // generated for several thousand new if tests added to the code. // Which ones are the ones at fault? The line numbers for the // warnings are incorrect. private static final boolean EMIT_NULL_CHECKS = true; /** * Constructs an emitter for the specified binding, and sets a default * comment emitter that will emit the signature of the C function that is * being bound. */ public CMethodBindingEmitter(MethodBinding binding, PrintWriter output, String javaPackageName, String javaClassName, boolean isOverloadedBinding, boolean isJavaMethodStatic, boolean forImplementingMethodCall, boolean forIndirectBufferAndArrayImplementation) { super(output); assert(binding != null); assert(javaClassName != null); assert(javaPackageName != null); this.binding = binding; this.packageName = javaPackageName; this.className = javaClassName; this.isOverloadedBinding = isOverloadedBinding; this.isJavaMethodStatic = isJavaMethodStatic; this.forImplementingMethodCall = forImplementingMethodCall; this.forIndirectBufferAndArrayImplementation = forIndirectBufferAndArrayImplementation; setCommentEmitter(defaultCommentEmitter); } public final MethodBinding getBinding() { return binding; } public String getName() { return binding.getRenamedMethodName(); } /** * Get the expression for the capacity of the returned java.nio.Buffer. */ public final MessageFormat getReturnValueCapacityExpression() { return returnValueCapacityExpression; } /** * If this function returns a void* encapsulated in a * java.nio.Buffer (or compound type wrapper), sets the expression * for the capacity of the returned Buffer. * * @param expression a MessageFormat which, when applied to an array * of type String[] that contains each of the arguments names of the * Java-side binding, returns an expression that will (when compiled * by a C compiler) evaluate to an integer-valued expression. The * value of this expression is the capacity of the java.nio.Buffer * returned from this method. * * @throws IllegalArgumentException if the * binding.getJavaReturnType().isNIOBuffer() == false and * binding.getJavaReturnType().isCompoundTypeWrapper() == false * */ public final void setReturnValueCapacityExpression(MessageFormat expression) { returnValueCapacityExpression = expression; if (!binding.getJavaReturnType().isNIOBuffer() && !binding.getJavaReturnType().isCompoundTypeWrapper()) { throw new IllegalArgumentException( "Cannot specify return value capacity for a method that does not " + "return java.nio.Buffer or a compound type wrapper: \"" + binding + "\""); } } /** * Get the expression for the length of the returned array */ public final MessageFormat getReturnValueLengthExpression() { return returnValueLengthExpression; } /** * If this function returns an array, sets the expression for the * length of the returned array. * * @param expression a MessageFormat which, when applied to an array * of type String[] that contains each of the arguments names of the * Java-side binding, returns an expression that will (when compiled * by a C compiler) evaluate to an integer-valued expression. The * value of this expression is the length of the array returned from * this method. * * @throws IllegalArgumentException if the * binding.getJavaReturnType().isNIOBuffer() == false * */ public final void setReturnValueLengthExpression(MessageFormat expression) { returnValueLengthExpression = expression; if (!binding.getJavaReturnType().isArray() && !binding.getJavaReturnType().isArrayOfCompoundTypeWrappers()) { throw new IllegalArgumentException( "Cannot specify return value length for a method that does not " + "return an array: \"" + binding + "\""); } } /** * Returns the List of Strings containing declarations for temporary * C variables to be assigned to after the underlying function call. */ public final List/**/ getTemporaryCVariableDeclarations() { return temporaryCVariableDeclarations; } /** * Sets up a List of Strings containing declarations for temporary C * variables to be assigned to after the underlying function call. A * null argument indicates that no manual declarations are to be made. */ public final void setTemporaryCVariableDeclarations(List/**/ arg) { temporaryCVariableDeclarations = arg; } /** * Returns the List of Strings containing assignments for temporary * C variables which are made after the underlying function call. A * null argument indicates that no manual assignments are to be * made. */ public final List/**/ getTemporaryCVariableAssignments() { return temporaryCVariableAssignments; } /** * Sets up a List of Strings containing assignments for temporary C * variables which are made after the underlying function call. A * null argument indicates that no manual assignments are to be made. */ public final void setTemporaryCVariableAssignments(List/**/ arg) { temporaryCVariableAssignments = arg; } /** * Get the name of the class in which the corresponding Java method * resides. */ public String getJavaPackageName() { return packageName; } /** * Get the name of the package in which the corresponding Java method * resides. */ public String getJavaClassName() { return className; } /** * Is the Java<->C JNI binding for this emitter's MethodBinding one of * several overloaded methods with the same name? */ public final boolean getIsOverloadedBinding() { return isOverloadedBinding; } /** * Is the Java side of the Java<->C JNI binding for this emitter's * MethodBinding a static method?. */ public final boolean getIsJavaMethodStatic() { return isJavaMethodStatic; } /** * Is this CMethodBindingEmitter implementing the case of an * indirect buffer or array being passed down to C code? */ public final boolean forIndirectBufferAndArrayImplementation() { return forIndirectBufferAndArrayImplementation; } protected void emitReturnType(PrintWriter writer) { writer.print("JNIEXPORT "); writer.print(binding.getJavaReturnType().jniTypeName()); writer.print(" JNICALL"); } protected void emitName(PrintWriter writer) { writer.println(); // start name on new line writer.print("Java_"); writer.print(jniMangle(getJavaPackageName())); writer.print("_"); writer.print(jniMangle(getJavaClassName())); writer.print("_"); if (isOverloadedBinding) { writer.print(jniMangle(binding)); //System.err.println("OVERLOADED MANGLING FOR " + getName() + // " = " + jniMangle(binding)); } else { writer.print(jniMangle(getName())); //System.err.println(" NORMAL MANGLING FOR " + binding.getName() + // " = " + jniMangle(getName())); } } protected String getImplSuffix() { if (forImplementingMethodCall) { if (forIndirectBufferAndArrayImplementation) { return "1"; } else { return "0"; } } return ""; } protected int emitArguments(PrintWriter writer) { writer.print("JNIEnv *env, "); int numEmitted = 1; // initially just the JNIEnv if (isJavaMethodStatic && !binding.hasContainingType()) { writer.print("jclass"); } else { writer.print("jobject"); } writer.print(" _unused"); ++numEmitted; if (binding.hasContainingType()) { // "this" argument always comes down in argument 0 as direct buffer writer.print(", jobject " + JavaMethodBindingEmitter.javaThisArgumentName()); } for (int i = 0; i < binding.getNumArguments(); i++) { JavaType javaArgType = binding.getJavaArgumentType(i); // Handle case where only param is void if (javaArgType.isVoid()) { // Make sure this is the only param to the method; if it isn't, // there's something wrong with our parsing of the headers. assert(binding.getNumArguments() == 1); continue; } if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } writer.print(", "); writer.print(javaArgType.jniTypeName()); writer.print(" "); writer.print(binding.getArgumentName(i)); ++numEmitted; if (javaArgType.isPrimitiveArray() || javaArgType.isNIOBuffer()) { writer.print(", jint " + byteOffsetArgName(i)); } else if (javaArgType.isNIOBufferArray()) { writer.print(", jintArray " + byteOffsetArrayArgName(i)); } } return numEmitted; } protected void emitBody(PrintWriter writer) { writer.println(" {"); emitBodyVariableDeclarations(writer); emitBodyUserVariableDeclarations(writer); emitBodyVariablePreCallSetup(writer, false); emitBodyVariablePreCallSetup(writer, true); emitBodyCallCFunction(writer); emitBodyUserVariableAssignments(writer); emitBodyVariablePostCallCleanup(writer, true); emitBodyVariablePostCallCleanup(writer, false); emitBodyReturnResult(writer); writer.println("}"); writer.println(); } protected void emitBodyVariableDeclarations(PrintWriter writer) { // Emit declarations for all pointer and String conversion variables if (binding.hasContainingType()) { emitPointerDeclaration(writer, binding.getContainingType(), binding.getContainingCType(), CMethodBindingEmitter.cThisArgumentName(), null); } boolean emittedDataCopyTemps = false; for (int i = 0; i < binding.getNumArguments(); i++) { JavaType type = binding.getJavaArgumentType(i); if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } if (type.isArray() || type.isNIOBuffer() || type.isCompoundTypeWrapper()) { String convName = pointerConversionArgumentName(i); // handle array/buffer argument types boolean needsDataCopy = emitPointerDeclaration(writer, binding.getJavaArgumentType(i), binding.getCArgumentType(i), convName, binding.getArgumentName(i)); if (needsDataCopy && !emittedDataCopyTemps) { // emit loop counter and array length variables used during data // copy writer.println(" jobject _tmpObj;"); writer.println(" int _copyIndex;"); writer.println(" jsize _tmpArrayLen;"); // Pointer to the data in the Buffer, taking the offset into account writer.println(" int * _offsetHandle = NULL;"); emittedDataCopyTemps = true; } } else if (type.isString()) { writer.print(" const char* _UTF8"); writer.print(binding.getArgumentName(i)); writer.println(" = NULL;"); } } // Emit declaration for return value if necessary Type cReturnType = binding.getCReturnType(); JavaType javaReturnType = binding.getJavaReturnType(); String capitalizedComponentType = null; if (!cReturnType.isVoid()) { writer.print(" "); // Note we must respect const/volatile for return argument writer.print(binding.getCSymbol().getReturnType().getName(true)); writer.println(" _res;"); if (javaReturnType.isNIOByteBufferArray() || javaReturnType.isArrayOfCompoundTypeWrappers()) { writer.print(" int "); writer.print(arrayResLength); writer.println(";"); writer.print(" int "); writer.print(arrayIdx); writer.println(";"); writer.print(" jobjectArray "); writer.print(arrayRes); writer.println(";"); } else if (javaReturnType.isArray()) { writer.print(" int "); writer.print(arrayResLength); writer.println(";"); Class componentType = javaReturnType.getJavaClass().getComponentType(); if (componentType.isArray()) { throw new RuntimeException("Multi-dimensional arrays not supported yet"); } String javaTypeName = componentType.getName(); capitalizedComponentType = "" + Character.toUpperCase(javaTypeName.charAt(0)) + javaTypeName.substring(1); String javaArrayTypeName = "j" + javaTypeName + "Array"; writer.print(" "); writer.print(javaArrayTypeName); writer.print(" "); writer.print(arrayRes); writer.println(";"); } } } /** Emits the user-defined C variable declarations from the TemporaryCVariableDeclarations directive in the .cfg file. */ protected void emitBodyUserVariableDeclarations(PrintWriter writer) { if (temporaryCVariableDeclarations != null) { for (Iterator iter = temporaryCVariableDeclarations.iterator(); iter.hasNext(); ) { String val = (String) iter.next(); writer.print(" "); writer.println(val); } } } /** Checks a type (expected to be pointer-to-pointer) for const-ness */ protected boolean isConstPtrPtr(Type type) { if (type.pointerDepth() != 2) { return false; } if (type.asPointer().getTargetType().asPointer().getTargetType().isConst()) { return true; } return false; } /** * Code to init the variables that were declared in * emitBodyVariableDeclarations(), PRIOR TO calling the actual C * function. */ protected void emitBodyVariablePreCallSetup(PrintWriter writer, boolean emittingPrimitiveArrayCritical) { if (!emittingPrimitiveArrayCritical) { // Convert all Buffers to pointers first so we don't have to // call ReleasePrimitiveArrayCritical for any arrays if any // incoming buffers aren't direct if (binding.hasContainingType()) { emitPointerConversion(writer, binding, binding.getContainingType(), binding.getContainingCType(), JavaMethodBindingEmitter.javaThisArgumentName(), CMethodBindingEmitter.cThisArgumentName(), null); } for (int i = 0; i < binding.getNumArguments(); i++) { JavaType type = binding.getJavaArgumentType(i); if (type.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } if (type.isCompoundTypeWrapper() || (type.isNIOBuffer() && !forIndirectBufferAndArrayImplementation)) { emitPointerConversion(writer, binding, type, binding.getCArgumentType(i), binding.getArgumentName(i), pointerConversionArgumentName(i), byteOffsetArgName(i)); } } } // Convert all arrays to pointers, and get UTF-8 versions of jstring args for (int i = 0; i < binding.getNumArguments(); i++) { JavaType javaArgType = binding.getJavaArgumentType(i); if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } if (javaArgType.isArray() || (javaArgType.isNIOBuffer() && forIndirectBufferAndArrayImplementation)) { boolean needsDataCopy = javaArgTypeNeedsDataCopy(javaArgType); // We only defer the emission of GetPrimitiveArrayCritical // calls that won't be matched up until after the function // we're calling if ((!needsDataCopy && !emittingPrimitiveArrayCritical) || (needsDataCopy && emittingPrimitiveArrayCritical)) { continue; } if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(binding.getArgumentName(i)); writer.println(" != NULL) {"); } Type cArgType = binding.getCArgumentType(i); String cArgTypeName = cArgType.getName(); String convName = pointerConversionArgumentName(i); if (!needsDataCopy) { writer.print(" "); writer.print(convName); writer.print(" = ("); if (javaArgType.isStringArray()) { // java-side type is String[] cArgTypeName = "jstring *"; } writer.print(cArgTypeName); writer.print(") (((char*) (*env)->GetPrimitiveArrayCritical(env, "); writer.print(binding.getArgumentName(i)); writer.println(", NULL)) + " + byteOffsetArgName(i) + ");"); //if(cargtypename is void*) // _ptrX = ((char*)convName + index1*sizeof(thisArgsJavaType)); } else { // Handle the case where the array elements are of a type that needs a // data copy operation to convert from the java memory model to the C // memory model (e.g., int[][], String[], etc) // // FIXME: should factor out this whole block of code into a separate // method for clarity and maintenance purposes if (!isConstPtrPtr(cArgType)) { // FIXME: if the arg type is non-const, the sematics might be that // the function modifies the argument -- we don't yet support // this. // // Note: the check for "const" in the CVAttributes string isn't // truly checking the constness of the target types at both // pointer depths. However, it's a quick approximation, and quite // often C code doesn't get the constness right anyhow. throw new RuntimeException( "Cannot copy data for ptr-to-ptr arg type \"" + cArgType + "\": support for non-const ptr-to-ptr types not implemented."); } writer.println(); writer.println(" /* Copy contents of " + binding.getArgumentName(i) + " into " + convName + "_copy */"); // get length of array being copied String arrayLenName = "_tmpArrayLen"; writer.print(" "); writer.print(arrayLenName); writer.print(" = (*env)->GetArrayLength(env, "); writer.print(binding.getArgumentName(i)); writer.println(");"); // allocate an array to hold each element if (cArgType.pointerDepth() != 2) { throw new RuntimeException( "Could not copy data for type \"" + cArgType + "\"; copying only supported for types of the form " + "ptr-to-ptr-to-type."); } PointerType cArgPtrType = cArgType.asPointer(); if (cArgPtrType == null) { throw new RuntimeException( "Could not copy data for type \"" + cArgType + "\"; currently only pointer types supported."); } PointerType cArgElementType = cArgPtrType.getTargetType().asPointer(); emitMalloc( writer, convName+"_copy", cArgElementType.getName(), arrayLenName, "Could not allocate buffer for copying data in argument \\\""+binding.getArgumentName(i)+"\\\""); // Get the handle for the byte offset array sent down for Buffers // FIXME: not 100% sure this is correct with respect to the // JNI spec because it may be illegal to call // GetObjectArrayElement while in a critical section. May // need to do another loop and add in the offsets. if (javaArgType.isNIOBufferArray()) { writer.println (" _offsetHandle = (int *) (*env)->GetPrimitiveArrayCritical(env, " + byteOffsetArrayArgName(i) + ", NULL);"); } // process each element in the array writer.println(" for (_copyIndex = 0; _copyIndex < "+arrayLenName+"; ++_copyIndex) {"); // get each array element writer.println(" /* get each element of the array argument \"" + binding.getArgumentName(i) + "\" */"); writer.print(" _tmpObj = (*env)->GetObjectArrayElement(env, "); writer.print(binding.getArgumentName(i)); writer.println(", _copyIndex);"); if (javaArgType.isStringArray()) { writer.print(" "); emitGetStringUTFChars(writer, "(jstring) _tmpObj", convName+"_copy[_copyIndex]", true); } else if (javaArgType.isNIOBufferArray()) { /* We always assume an integer "byte offset" argument follows any Buffer in the method binding. */ emitGetDirectBufferAddress(writer, "_tmpObj", cArgElementType.getName(), convName + "_copy[_copyIndex]", "_offsetHandle[_copyIndex]", true); } else { // Question: do we always need to copy the sub-arrays, or just // GetPrimitiveArrayCritical on each jobjectarray element and // assign it to the appropriate elements at pointer depth 1? // Probably depends on const-ness of the argument. // Malloc enough space to hold a copy of each sub-array writer.print(" "); emitMalloc( writer, convName+"_copy[_copyIndex]", cArgElementType.getTargetType().getName(), // assumes cArgPtrType is ptr-to-ptr-to-primitive !! "(*env)->GetArrayLength(env, _tmpObj)", "Could not allocate buffer during copying of data in argument \\\""+binding.getArgumentName(i)+"\\\""); // FIXME: copy the data (use matched Get/ReleasePrimitiveArrayCritical() calls) if (true) throw new RuntimeException( "Cannot yet handle type \"" + cArgType.getName() + "\"; need to add support for copying ptr-to-ptr-to-primitiveType subarrays"); } writer.println(" }"); if (javaArgType.isNIOBufferArray()) { writer.println (" (*env)->ReleasePrimitiveArrayCritical(env, " + byteOffsetArrayArgName(i) + ", _offsetHandle, JNI_ABORT);"); } writer.println(); } // end of data copy if (EMIT_NULL_CHECKS) { writer.println(" }"); } } else if (javaArgType.isString()) { if (!emittingPrimitiveArrayCritical) { continue; } if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(binding.getArgumentName(i)); writer.println(" != NULL) {"); } emitGetStringUTFChars(writer, binding.getArgumentName(i), "_UTF8" + binding.getArgumentName(i), false); if (EMIT_NULL_CHECKS) { writer.println(" }"); } } else if (javaArgType.isArrayOfCompoundTypeWrappers()) { // FIXME throw new RuntimeException("Outgoing arrays of StructAccessors not yet implemented"); } } } /** * Code to clean up any variables that were declared in * emitBodyVariableDeclarations(), AFTER calling the actual C function. */ protected void emitBodyVariablePostCallCleanup(PrintWriter writer, boolean emittingPrimitiveArrayCritical) { // Release primitive arrays and temporary UTF8 strings if necessary for (int i = 0; i < binding.getNumArguments(); i++) { JavaType javaArgType = binding.getJavaArgumentType(i); if (javaArgType.isJNIEnv() || binding.isArgumentThisPointer(i)) { continue; } if (javaArgType.isArray() || (javaArgType.isNIOBuffer() && forIndirectBufferAndArrayImplementation)) { boolean needsDataCopy = javaArgTypeNeedsDataCopy(javaArgType); if ((!needsDataCopy && !emittingPrimitiveArrayCritical) || (needsDataCopy && emittingPrimitiveArrayCritical)) { continue; } if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(binding.getArgumentName(i)); writer.println(" != NULL) {"); } String convName = pointerConversionArgumentName(i); if (!needsDataCopy) { // Release array writer.print(" (*env)->ReleasePrimitiveArrayCritical(env, "); writer.print(binding.getArgumentName(i)); writer.print(", "); writer.print(convName); writer.println(", 0);"); } else { // clean up the case where the array elements are of a type that needed // a data copy operation to convert from the java memory model to the // C memory model (e.g., int[][], String[], etc) // // FIXME: should factor out this whole block of code into a separate // method for clarity and maintenance purposes Type cArgType = binding.getCArgumentType(i); String cArgTypeName = cArgType.getName(); if (cArgType.toString().indexOf("const") == -1) { // FIXME: handle any cleanup from treatment of non-const args, // assuming they were treated differently in // emitBodyVariablePreCallSetup() (see the similar section in that // method for details). throw new RuntimeException( "Cannot clean up copied data for ptr-to-ptr arg type \"" + cArgType + "\": support for cleaning up non-const ptr-to-ptr types not implemented."); } writer.println(" /* Clean up " + convName + "_copy */"); // Only need to perform cleanup for individual array // elements if they are not direct buffers if (!javaArgType.isNIOBufferArray()) { // Re-fetch length of array that was copied String arrayLenName = "_tmpArrayLen"; writer.print(" "); writer.print(arrayLenName); writer.print(" = (*env)->GetArrayLength(env, "); writer.print(binding.getArgumentName(i)); writer.println(");"); // free each element PointerType cArgPtrType = cArgType.asPointer(); if (cArgPtrType == null) { throw new RuntimeException( "Could not copy data for type \"" + cArgType + "\"; currently only pointer types supported."); } PointerType cArgElementType = cArgPtrType.getTargetType().asPointer(); // process each element in the array writer.println(" for (_copyIndex = 0; _copyIndex < " + arrayLenName +"; ++_copyIndex) {"); // get each array element writer.println(" /* free each element of " +convName +"_copy */"); writer.print(" _tmpObj = (*env)->GetObjectArrayElement(env, "); writer.print(binding.getArgumentName(i)); writer.println(", _copyIndex);"); if (javaArgType.isStringArray()) { writer.print(" (*env)->ReleaseStringUTFChars(env, "); writer.print("(jstring) _tmpObj"); writer.print(", "); writer.print(convName+"_copy[_copyIndex]"); writer.println(");"); } else { if (true) throw new RuntimeException( "Cannot yet handle type \"" + cArgType.getName() + "\"; need to add support for cleaning up copied ptr-to-ptr-to-primitiveType subarrays"); } writer.println(" }"); } // free the main array writer.print(" free((void*) "); writer.print(convName+"_copy"); writer.println(");"); } // end of cleaning up copied data if (EMIT_NULL_CHECKS) { writer.println(" }"); } } else if (javaArgType.isString()) { if (emittingPrimitiveArrayCritical) { continue; } if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(binding.getArgumentName(i)); writer.println(" != NULL) {"); } writer.print(" (*env)->ReleaseStringUTFChars(env, "); writer.print(binding.getArgumentName(i)); writer.print(", _UTF8"); writer.print(binding.getArgumentName(i)); writer.println(");"); if (EMIT_NULL_CHECKS) { writer.println(" }"); } } else if (javaArgType.isArrayOfCompoundTypeWrappers()) { // FIXME throw new RuntimeException("Outgoing arrays of StructAccessors not yet implemented"); } } } /** Returns the number of arguments passed so calling code knows whether to print a comma */ protected int emitBodyPassCArguments(PrintWriter writer) { for (int i = 0; i < binding.getNumArguments(); i++) { if (i != 0) { writer.print(", "); } JavaType javaArgType = binding.getJavaArgumentType(i); // Handle case where only param is void. if (javaArgType.isVoid()) { // Make sure this is the only param to the method; if it isn't, // there's something wrong with our parsing of the headers. assert(binding.getNumArguments() == 1); continue; } if (javaArgType.isJNIEnv()) { writer.print("env"); } else if (binding.isArgumentThisPointer(i)) { writer.print(CMethodBindingEmitter.cThisArgumentName()); } else { writer.print("("); Type cArgType = binding.getCSymbol().getArgumentType(i); writer.print(cArgType.getName()); writer.print(") "); if (binding.getCArgumentType(i).isPointer() && binding.getJavaArgumentType(i).isPrimitive()) { writer.print("(intptr_t) "); } if (javaArgType.isArray() || javaArgType.isNIOBuffer() || javaArgType.isCompoundTypeWrapper()) { writer.print(pointerConversionArgumentName(i)); if (javaArgTypeNeedsDataCopy(javaArgType)) { writer.print("_copy"); } } else { if (javaArgType.isString()) { writer.print("_UTF8"); } writer.print(binding.getArgumentName(i)); } } } return binding.getNumArguments(); } protected void emitBodyCallCFunction(PrintWriter writer) { // Make the call to the actual C function writer.print(" "); // WARNING: this code assumes that the return type has already been // typedef-resolved. Type cReturnType = binding.getCReturnType(); if (!cReturnType.isVoid()) { writer.print("_res = "); } if (binding.hasContainingType()) { // Call through function pointer writer.print(CMethodBindingEmitter.cThisArgumentName() + "->"); } writer.print(binding.getCSymbol().getName()); writer.print("("); emitBodyPassCArguments(writer); writer.println(");"); } /** Emits the user-defined C variable assignments from the TemporaryCVariableAssignments directive in the .cfg file. */ protected void emitBodyUserVariableAssignments(PrintWriter writer) { if (temporaryCVariableAssignments != null) { for (Iterator iter = temporaryCVariableAssignments.iterator(); iter.hasNext(); ) { String val = (String) iter.next(); writer.print(" "); writer.println(val); } } } protected void emitBodyReturnResult(PrintWriter writer) { // WARNING: this code assumes that the return type has already been // typedef-resolved. Type cReturnType = binding.getCReturnType(); // Return result if necessary if (!cReturnType.isVoid()) { JavaType javaReturnType = binding.getJavaReturnType(); if (javaReturnType.isPrimitive()) { writer.print(" return "); if (cReturnType.isPointer()) { // Pointer being converted to int or long: cast this result // (through intptr_t to avoid compiler warnings with gcc) writer.print("(" + javaReturnType.jniTypeName() + ") (intptr_t) "); } writer.println("_res;"); } else if (javaReturnType.isNIOBuffer() || javaReturnType.isCompoundTypeWrapper()) { writer.println(" if (_res == NULL) return NULL;"); writer.print(" return (*env)->NewDirectByteBuffer(env, _res, "); // See whether capacity has been specified if (returnValueCapacityExpression != null) { String[] argumentNames = new String[binding.getNumArguments()]; for (int i = 0; i < binding.getNumArguments(); i++) { argumentNames[i] = binding.getArgumentName(i); } writer.print( returnValueCapacityExpression.format(argumentNames)); } else { if (cReturnType.isPointer() && cReturnType.asPointer().getTargetType().isCompound()) { if (cReturnType.asPointer().getTargetType().getSize() == null) { throw new RuntimeException( "Error emitting code for compound return type "+ "for function \"" + binding + "\": " + "Structs to be emitted should have been laid out by this point " + "(type " + cReturnType.asPointer().getTargetType().getName() + " / " + cReturnType.asPointer().getTargetType() + " was not)" ); } } writer.print("sizeof(" + cReturnType.getName() + ")"); System.err.println( "WARNING: No capacity specified for java.nio.Buffer return " + "value for function \"" + binding + "\";" + " assuming size of equivalent C return type (sizeof(" + cReturnType.getName() + ")): " + binding); } writer.println(");"); } else if (javaReturnType.isString()) { writer.print(" if (_res == NULL) return NULL;"); writer.println(" return (*env)->NewStringUTF(env, _res);"); } else if (javaReturnType.isArrayOfCompoundTypeWrappers() || (javaReturnType.isArray() && javaReturnType.isNIOByteBufferArray())) { writer.println(" if (_res == NULL) return NULL;"); if (returnValueLengthExpression == null) { throw new RuntimeException("Error while generating C code: no length specified for array returned from function " + binding); } String[] argumentNames = new String[binding.getNumArguments()]; for (int i = 0; i < binding.getNumArguments(); i++) { argumentNames[i] = binding.getArgumentName(i); } writer.println(" " + arrayResLength + " = " + returnValueLengthExpression.format(argumentNames) + ";"); writer.println(" " + arrayRes + " = (*env)->NewObjectArray(env, " + arrayResLength + ", (*env)->FindClass(env, \"java/nio/ByteBuffer\"), NULL);"); writer.println(" for (" + arrayIdx + " = 0; " + arrayIdx + " < " + arrayResLength + "; " + arrayIdx + "++) {"); Type retType = binding.getCSymbol().getReturnType(); Type pointerType; if (retType.isPointer()) { pointerType = retType.asPointer().getTargetType(); } else { pointerType = retType.asArray().getElementType(); } Type baseType = pointerType.asPointer().getTargetType(); writer.println(" (*env)->SetObjectArrayElement(env, " + arrayRes + ", " + arrayIdx + ", (*env)->NewDirectByteBuffer(env, _res[" + arrayIdx + "], sizeof(" + pointerType.getName() + ")));"); writer.println(" }"); writer.println(" return " + arrayRes + ";"); } else if (javaReturnType.isArray()) { // FIXME: must have user provide length of array in .cfg file // by providing a constant value, input parameter, or // expression which computes the array size (already present // as ReturnValueCapacity, not yet implemented / tested here) throw new RuntimeException( "Could not emit native code for function \"" + binding + "\": array return values for non-char types not implemented yet"); // FIXME: This is approximately what will be required here // //writer.print(" "); //writer.print(arrayRes); //writer.print(" = (*env)->New"); //writer.print(capitalizedComponentType); //writer.print("Array(env, "); //writer.print(arrayResLength); //writer.println(");"); //writer.print(" (*env)->Set"); //writer.print(capitalizedComponentType); //writer.print("ArrayRegion(env, "); //writer.print(arrayRes); //writer.print(", 0, "); //writer.print(arrayResLength); //writer.println(", _res);"); //writer.print(" return "); //writer.print(arrayRes); //writer.println(";"); } else { System.err.print("Unhandled return type: "); javaReturnType.dump(); throw new RuntimeException("Unhandled return type"); } } } protected static String cThisArgumentName() { return "this0"; } // Mangle a class, package or function name protected String jniMangle(String name) { return name.replaceAll("_", "_1").replace('.', '_'); } protected String jniMangle(MethodBinding binding) { StringBuffer buf = new StringBuffer(); buf.append(jniMangle(getName())); buf.append(getImplSuffix()); buf.append("__"); if (binding.hasContainingType()) { // "this" argument always comes down in argument 0 as direct buffer jniMangle(java.nio.ByteBuffer.class, buf, true); } for (int i = 0; i < binding.getNumArguments(); i++) { if (binding.isArgumentThisPointer(i)) { continue; } JavaType type = binding.getJavaArgumentType(i); if (type.isVoid()) { // We should only see "void" as the first argument of a 1-argument function // FIXME: should normalize this in the parser if ((i != 0) || (binding.getNumArguments() > 1)) { throw new RuntimeException("Saw illegal \"void\" argument while emitting \"" + getName() + "\""); } } else { Class c = type.getJavaClass(); if (c != null) { jniMangle(c, buf, false); // If Buffer offset arguments were added, we need to mangle the JNI for the // extra arguments if (type.isNIOBuffer()) { jniMangle(Integer.TYPE, buf, false); } else if (type.isNIOBufferArray()) { int[] intArrayType = new int[0]; c = intArrayType.getClass(); jniMangle(c , buf, true); } if (type.isPrimitiveArray()) { jniMangle(Integer.TYPE, buf, false); } } else if (type.isCompoundTypeWrapper()) { // Mangle wrappers for C structs as ByteBuffer jniMangle(java.nio.ByteBuffer.class, buf, true); } else if (type.isJNIEnv()) { // These are not exposed at the Java level } else { // FIXME: add support for char* -> String conversion throw new RuntimeException("Unknown kind of JavaType: name="+type.getName()); } } } return buf.toString(); } protected void jniMangle(Class c, StringBuffer res, boolean syntheticArgument) { if (c.isPrimitive()) { if (c == Boolean.TYPE) res.append("Z"); else if (c == Byte.TYPE) res.append("B"); else if (c == Character.TYPE) res.append("C"); else if (c == Short.TYPE) res.append("S"); else if (c == Integer.TYPE) res.append("I"); else if (c == Long.TYPE) res.append("J"); else if (c == Float.TYPE) res.append("F"); else if (c == Double.TYPE) res.append("D"); else throw new RuntimeException("Illegal primitive type \"" + c.getName() + "\""); } else { // Arrays and NIO Buffers are always passed down as java.lang.Object. // The only arrays that show up as true arrays in the signature // are the synthetic byte offset arrays created when passing // down arrays of direct Buffers. Compound type wrappers are // passed down as ByteBuffers (no good reason, just to avoid // accidental conflation) so we mangle them differently. if (syntheticArgument) { if (c.isArray()) { res.append("_3"); jniMangle(c.getComponentType(), res, false); } else { res.append("L"); res.append(c.getName().replace('.', '_')); res.append("_2"); } } else { if (c.isArray()) { res.append("_3"); jniMangle(c.getComponentType(), res, false); } else if (c == java.lang.String.class) { res.append("L"); res.append(c.getName().replace('.', '_')); res.append("_2"); } else { res.append("L"); res.append("java_lang_Object"); res.append("_2"); } } } } private void emitOutOfMemoryCheck(PrintWriter writer, String varName, String errorMessage) { writer.print(" if ("); writer.print(varName); writer.println(" == NULL) {"); writer.println(" (*env)->ThrowNew(env, (*env)->FindClass(env, \"java/lang/OutOfMemoryError\"),"); writer.print(" \"" + errorMessage); writer.print(" in native dispatcher for \\\""); writer.print(getName()); writer.println("\\\"\");"); writer.print(" return"); if (!binding.getJavaReturnType().isVoid()) { writer.print(" 0"); } writer.println(";"); writer.println(" }"); } private void emitMalloc(PrintWriter writer, String targetVarName, String elementTypeString, String numElementsExpression, String mallocFailureErrorString) { writer.print(" "); writer.print(targetVarName); writer.print(" = ("); writer.print(elementTypeString); writer.print(" *) malloc("); writer.print(numElementsExpression); writer.print(" * sizeof("); writer.print(elementTypeString); writer.println("));"); // Catch memory allocation failure if (EMIT_NULL_CHECKS) { emitOutOfMemoryCheck( writer, targetVarName, mallocFailureErrorString); } } private void emitGetStringUTFChars(PrintWriter writer, String sourceVarName, String receivingVarName, boolean emitElseClause) { if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(sourceVarName); writer.println(" != NULL) {"); } writer.print(" "); writer.print(receivingVarName); writer.print(" = (*env)->GetStringUTFChars(env, "); writer.print(sourceVarName); writer.println(", (jboolean*)NULL);"); // Catch memory allocation failure in the event that the VM didn't pin // the String and failed to allocate a copy if (EMIT_NULL_CHECKS) { emitOutOfMemoryCheck( writer, receivingVarName, "Failed to get UTF-8 chars for argument \\\""+sourceVarName+"\\\""); } if (EMIT_NULL_CHECKS) { writer.print(" }"); if (emitElseClause) { writer.print(" else {"); writer.print(" "); writer.print(receivingVarName); writer.println(" = NULL;"); writer.println(" }"); } else { writer.println(); } } } private void emitGetDirectBufferAddress(PrintWriter writer, String sourceVarName, String receivingVarTypeString, String receivingVarName, String byteOffsetVarName, boolean emitElseClause) { if (EMIT_NULL_CHECKS) { writer.print(" if ("); writer.print(sourceVarName); writer.println(" != NULL) {"); writer.print(" "); } writer.print(" "); writer.print(receivingVarName); writer.print(" = ("); writer.print(receivingVarTypeString); writer.print(") (((char*) (*env)->GetDirectBufferAddress(env, "); writer.print(sourceVarName); writer.println(")) + " + ((byteOffsetVarName != null) ? byteOffsetVarName : "0") + ");"); if (EMIT_NULL_CHECKS) { writer.print(" }"); if (emitElseClause) { writer.println(" else {"); writer.print(" "); writer.print(receivingVarName); writer.println(" = NULL;"); writer.println(" }"); } else { writer.println(); } } } // Note: if the data in the Type needs to be converted from the Java memory // model to the C memory model prior to calling any C-side functions, then // an extra variable named XXX_copy (where XXX is the value of the // cVariableName argument) will be emitted and TRUE will be returned. private boolean emitPointerDeclaration(PrintWriter writer, JavaType javaType, Type cType, String cVariableName, String javaArgumentName) { String ptrTypeString = null; boolean needsDataCopy = false; // Emit declaration for the pointer variable. // // Note that we don't need to obey const/volatile for outgoing arguments // if (javaType.isNIOBuffer()) { ptrTypeString = cType.getName(); } else if (javaType.isArray()) { needsDataCopy = javaArgTypeNeedsDataCopy(javaType); if (javaType.isPrimitiveArray() || javaType.isNIOBufferArray()) { ptrTypeString = cType.getName(); } else if (!javaType.isStringArray()) { Class elementType = javaType.getJavaClass().getComponentType(); if (elementType.isArray()) { Class subElementType = elementType.getComponentType(); if (subElementType.isPrimitive()) { // type is pointer to pointer to primitive ptrTypeString = cType.getName(); } else { // type is pointer to pointer of some type we don't support (maybe // it's an array of pointers to structs?) throw new RuntimeException("Unsupported pointer type: \"" + cType.getName() + "\""); } } else { // type is pointer to pointer of some type we don't support (maybe // it's an array of pointers to structs?) throw new RuntimeException("Unsupported pointer type: \"" + cType.getName() + "\""); } } } else if (javaType.isArrayOfCompoundTypeWrappers()) { // FIXME throw new RuntimeException("Outgoing arrays of StructAccessors not yet implemented"); } else { ptrTypeString = cType.getName(); } if (!needsDataCopy) { // declare the pointer variable writer.print(" "); writer.print(ptrTypeString); writer.print(" "); writer.print(cVariableName); writer.println(" = NULL;"); } else { // Declare a variable to hold a copy of the argument data in which the // incoming data has been properly laid out in memory to match the C // memory model Class elementType = javaType.getJavaClass().getComponentType(); if (javaType.isStringArray()) { writer.print(" const char **"); } else { writer.print(" " + ptrTypeString); } writer.print(" "); writer.print(cVariableName); writer.print("_copy = NULL; /* copy of data in "); writer.print(javaArgumentName); writer.println(", laid out according to C memory model */"); } return needsDataCopy; } private void emitPointerConversion(PrintWriter writer, MethodBinding binding, JavaType type, Type cType, String incomingArgumentName, String cVariableName, String byteOffsetVarName) { // Compound type wrappers do not get byte offsets added on if (type.isCompoundTypeWrapper()) { byteOffsetVarName = null; } emitGetDirectBufferAddress(writer, incomingArgumentName, cType.getName(), cVariableName, byteOffsetVarName, false); } protected String byteOffsetArgName(int i) { return byteOffsetArgName(binding.getArgumentName(i)); } protected String byteOffsetArgName(String s) { return s + "_byte_offset"; } protected String byteOffsetArrayArgName(int i) { return binding.getArgumentName(i) + "_byte_offset_array"; } protected String pointerConversionArgumentName(int i) { return "_ptr" + i; } /** * Class that emits a generic comment for CMethodBindingEmitters; the comment * includes the C signature of the native method that is being bound by the * emitter java method. */ protected static class DefaultCommentEmitter implements CommentEmitter { public void emit(FunctionEmitter emitter, PrintWriter writer) { emitBeginning((CMethodBindingEmitter)emitter, writer); emitEnding((CMethodBindingEmitter)emitter, writer); } protected void emitBeginning(CMethodBindingEmitter emitter, PrintWriter writer) { writer.println(" Java->C glue code:"); writer.print(" * Java package: "); writer.print(emitter.getJavaPackageName()); writer.print("."); writer.println(emitter.getJavaClassName()); writer.print(" * Java method: "); MethodBinding binding = emitter.getBinding(); writer.println(binding); writer.println(" * C function: " + binding.getCSymbol()); } protected void emitEnding(CMethodBindingEmitter emitter, PrintWriter writer) { } } protected boolean javaArgTypeNeedsDataCopy(JavaType javaArgType) { if (javaArgType.isArray()) { return (javaArgType.isNIOBufferArray() || javaArgType.isStringArray() || javaArgType.getJavaClass().getComponentType().isArray()); } return false; } }