/* * Copyright (c) 2009, 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.io.*; import java.util.*; import java.util.regex.*; import com.sun.max.*; import com.sun.max.annotate.*; import com.sun.max.ide.*; import com.sun.max.io.*; import com.sun.max.program.*; /** * This class implements the {@linkplain #generate process} by which the source in {@link JniFunctionsSource JniFunctionsSource.java} * and {@link JmmFunctionsSource JmmFunctionsSource.java} is pre-processed to produce source in * {@link JniFunctions JniFunctions.java} and {@link JmmFunctions JmmFunctions.java}. * The generated source is delineated by the following lines: * <pre> * // START GENERATED CODE * * ... * * // END GENERATED CODE * </pre> */ @HOSTED_ONLY public class JniFunctionsGenerator { /** * Flag that controls if timing / counting code is inserted into the generated JNI functions. */ private static final boolean TIME_JNI_FUNCTIONS = false; private static final String VM_ENTRY_POINT_ANNOTATION = "@" + VM_ENTRY_POINT.class.getSimpleName(); static final int BEFORE_FIRST_VM_ENTRY_POINT_FUNCTION = -1; static final int BEFORE_VM_ENTRY_POINT_FUNCTION = 0; static final int BEFORE_PROLOGUE = 1; static ArrayList<String> logOperations; public static VmEntryFunctionDeclaration currentMethod; /** * An extension of {@link BufferedReader} that tracks the line number. */ static class LineReader extends BufferedReader { int lineNo; String line; final File file; public LineReader(File file) throws FileNotFoundException { super(new FileReader(file)); this.file = file; } @Override public String readLine() throws IOException { lineNo++; line = super.readLine(); return line; } public String where() { return file.getAbsolutePath() + ":" + lineNo; } public void check(boolean condition, String errorMessage) { if (!condition) { throw new InternalError(String.format("%n" + where() + ": " + errorMessage + "%n" + line)); } } } public static class VmEntryFunctionDeclaration { static Pattern PATTERN = Pattern.compile(" private static (native )?(\\w+(?:\\[\\])*) (\\w+)\\(([^)]*)\\).*"); String line; String returnType; String jniReturnType; boolean isNative; public String name; public String parameters; public String jniParameters; public String arguments; String sourcePos; static VmEntryFunctionDeclaration parse(String line, String sourcePos) { Matcher m = PATTERN.matcher(line); if (!m.matches()) { return null; } VmEntryFunctionDeclaration decl = new VmEntryFunctionDeclaration(); decl.line = line; decl.isNative = m.group(1) != null; decl.returnType = m.group(2); decl.name = m.group(3); decl.parameters = m.group(4); String[] parameters = decl.parameters.split(",\\s*"); StringBuilder jniParameters = new StringBuilder(); StringBuilder arguments = new StringBuilder(); for (int i = 0; i < parameters.length; ++i) { String parameter = parameters[i]; if (parameter.length() != 0) { if (arguments.length() != 0) { arguments.append(", "); jniParameters.append(", "); } String[] typeAndName = parameter.split("\\s+"); assert typeAndName.length == 2 : line; jniParameters.append(toJniType(typeAndName[0])).append(' ').append(typeAndName[1]); arguments.append(parameter.substring(parameter.lastIndexOf(' ') + 1)); } } decl.arguments = arguments.toString(); decl.sourcePos = sourcePos; decl.jniParameters = jniParameters.toString(); decl.jniReturnType = toJniType(decl.returnType); return decl; } static Map<String, String> jniTypes = Utils.addEntries(new HashMap<String, String>(), "boolean", "jboolean", "byte", "jbyte", "char", "jchar", "short", "jshort", "int", "jint", "long", "jlong", "float", "jfloat", "double", "jdouble", "void", "void", "MethodID", "jmethodID", "FieldID", "jfieldID", "JniHandle", "jobject", "Pointer", "void*", "Word", "void*", "Address", "void*", "Size", "size_t", "Offset", "off_t"); static String toJniType(String type) { String jniType = jniTypes.get(type); assert jniType != null : "Type cannot present in a native interface signature: " + type; return jniType; } public String declareHelper() { int index = line.indexOf('('); return line.substring(0, index) + '_' + line.substring(index); } public String callHelper() { return name + "_(" + arguments + ")"; } } public abstract static class Customizer { public String customizeBody(String line) { return line; } public void startFunction(VmEntryFunctionDeclaration decl) { currentMethod = decl; logOperations.add(decl.name); } public void close(PrintWriter writer) throws Exception { writer.println(" public static enum LogOperations {"); for (int i = 0; i < logOperations.size(); i++) { String methodName = logOperations.get(i); if (i > 0) { writer.println(","); } writer.printf(" /* %d */ %s", i, methodName); } customizeOperations(writer); writer.println(";\n"); writer.println(" }"); } public String customizeHandler(String returnStatement) { String result = " VmThread.fromJniEnv(env).setJniException(t);"; if (returnStatement != null) { result += "\n " + returnStatement; } return result; } public void customizeOperations(PrintWriter writer) { // op for user downcalls/invoke writer.printf(",\n // operation for logging native method down call\n"); writer.printf(" /* %d */ %s", logOperations.size(), "NativeMethodCall"); writer.printf(",\n // operation for logging reflective invocation\n"); writer.printf(" /* %d */ %s", logOperations.size() + 1, "ReflectiveInvocation"); writer.printf(",\n // operation for logging dynamic linking\n"); writer.printf(" /* %d */ %s", logOperations.size() + 2, "DynamicLink"); writer.printf(",\n // operation for logging native method registration\n"); writer.printf(" /* %d */ %s", logOperations.size() + 3, "RegisterNativeMethod"); } public abstract String customizeTracePrologue(VmEntryFunctionDeclaration decl); public abstract String customizeTraceEpilogue(VmEntryFunctionDeclaration decl); } public static class JniCustomizer extends Customizer { @Override public String customizeTracePrologue(VmEntryFunctionDeclaration decl) { return entryLogging(); } @Override public String customizeTraceEpilogue(VmEntryFunctionDeclaration decl) { return exitLogging(); } private static String entryLogging() { StringBuilder sb = new StringBuilder(); String[] args = getDefaultArgs(); sb.append(" if (logger.enabled()) {\n"); sb.append(" logger.log(LogOperations."); sb.append(currentMethod.name); sb.append('.'); sb.append("ordinal(), UPCALL_ENTRY, anchor"); for (int i = 0; i < args.length; i++) { String tag = args[i]; sb.append(", "); sb.append(tag); } sb.append(");\n"); sb.append(" }\n"); return sb.toString(); } private static String exitLogging() { StringBuilder sb = new StringBuilder(); sb.append(" if (logger.enabled()) {\n"); sb.append(" logger.log(LogOperations."); sb.append(currentMethod.name); sb.append('.'); sb.append("ordinal(), UPCALL_EXIT);\n"); sb.append(" }\n"); return sb.toString(); } } public static class VMCustomizer extends Customizer { private final boolean checkOnly; public VMCustomizer(boolean checkOnly) { this.checkOnly = checkOnly; } private ArrayList<VmEntryFunctionDeclaration> decls = new ArrayList<JniFunctionsGenerator.VmEntryFunctionDeclaration>(); @Override public void startFunction(VmEntryFunctionDeclaration decl) { super.startFunction(decl); decls.add(decl); } @Override public String customizeTracePrologue(VmEntryFunctionDeclaration decl) { return JniCustomizer.entryLogging(); } @Override public String customizeTraceEpilogue(VmEntryFunctionDeclaration decl) { return JniCustomizer.exitLogging(); } @Override public void customizeOperations(PrintWriter writer) { // no special operations } @Override public void close(PrintWriter writer) throws Exception { super.close(writer); final File vmHeaderFile = new File(new File(JavaProject.findWorkspace(), "com.oracle.max.vm.native/substrate/vm.h").getAbsolutePath()); ProgramError.check(vmHeaderFile.exists(), "JMM header file " + vmHeaderFile + " does not exist"); Writer vmDecls = new StringWriter(); PrintWriter out = new PrintWriter(vmDecls); for (VmEntryFunctionDeclaration decl : decls) { assert decl.jniParameters.startsWith("void* env") : "First parameter of a VM function must be 'void* env': " + decl.jniParameters; String parameters = decl.jniParameters.replace("void* env", "JNIEnv *env"); out.println(" " + decl.jniReturnType + " (JNICALL *" + decl.name + ") (" + parameters + ");"); } vmDecls.close(); if (Files.updateGeneratedContent(vmHeaderFile, ReadableSource.Static.fromString(vmDecls.toString()), "// START GENERATED CODE", "// END GENERATED CODE", checkOnly)) { System.out.println("Source for " + vmHeaderFile.getPath() + " was updated"); System.exit(1); } } } public static String[] getDefaultArgs() { String[] paramNames = currentMethod.arguments.split(",\\s"); String[] params = currentMethod.parameters.split(",\\s*"); for (int i = 0; i < paramNames.length; i++) { String paramType = params[i].split(" ")[0]; if (paramType.equals("int") || paramType.equals("short") || paramType.equals("char") || paramType.equals("byte")) { paramNames[i] = "Address.fromInt(" + paramNames[i] + ")"; } else if (paramType.equals("long")) { paramNames[i] = "Address.fromLong(" + paramNames[i] + ")"; } else if (paramType.equals("boolean")) { paramNames[i] = "Address.fromInt(" + paramNames[i] + " ? 1 : 0)"; } else if (paramType.equals("float")) { paramNames[i] = "Address.fromInt(Float.floatToRawIntBits(" + paramNames[i] + "))"; } else if (paramType.equals("double")) { paramNames[i] = "Address.fromLong(Double.doubleToRawLongBits(" + paramNames[i] + "))"; } } return paramNames; } public static boolean generate(boolean checkOnly, Class source, Class target) throws Exception { return generate(checkOnly, "com.oracle.max.vm", source, target, new JniCustomizer()); } /** * Inserts or updates generated source into {@code target}. The generated source is derived from * {@code source} and is delineated in {@code target} by the following lines: * <pre> * // START GENERATED CODE * * ... * * // END GENERATED CODE * </pre> * * @param checkOnly if {@code true}, then {@code target} is not updated; the value returned by this method indicates * whether it would have been updated were this argument {@code true} * @param project TODO * @return {@code true} if {@code target} was modified (or would have been if {@code checkOnly} was {@code false}); {@code false} otherwise */ public static boolean generate(boolean checkOnly, String project, Class source, Class target, Customizer customizer) throws Exception { File base = new File(JavaProject.findWorkspace(), project + File.separator + "src"); File inputFile = new File(base, source.getName().replace('.', File.separatorChar) + ".java").getAbsoluteFile(); File outputFile = new File(base, target.getName().replace('.', File.separatorChar) + ".java").getAbsoluteFile(); LineReader lr = new LineReader(inputFile); String line = null; int state = BEFORE_FIRST_VM_ENTRY_POINT_FUNCTION; Writer writer = new StringWriter(); PrintWriter out = new PrintWriter(writer); logOperations = new ArrayList<String>(); while ((line = lr.readLine()) != null) { // Stop once the closing brace for the source class is found if (line.equals("}")) { break; } if (line.trim().equals(VM_ENTRY_POINT_ANNOTATION)) { lr.check(state == BEFORE_VM_ENTRY_POINT_FUNCTION || state == BEFORE_FIRST_VM_ENTRY_POINT_FUNCTION, "Illegal state (" + state + ") when parsing @JNI_FUNCTION"); if (state == BEFORE_FIRST_VM_ENTRY_POINT_FUNCTION) { out.println(); out.println(" private static final boolean INSTRUMENTED = " + TIME_JNI_FUNCTIONS + ";"); out.println(); } state = BEFORE_PROLOGUE; out.println(line); continue; } if (state == BEFORE_PROLOGUE) { VmEntryFunctionDeclaration decl = VmEntryFunctionDeclaration.parse(line, inputFile.getName() + ":" + lr.lineNo); lr.check(decl != null, "JNI function declaration does not match pattern \"" + VmEntryFunctionDeclaration.PATTERN + "\""); out.println(line); out.println(" // Source: " + decl.sourcePos); if (!decl.isNative) { customizer.startFunction(decl); StringBuilder bodyBuffer = new StringBuilder(); String body = null; while ((line = lr.readLine()) != null) { if (line.equals(" }")) { body = bodyBuffer.toString(); break; } if (line.length() > 0) { bodyBuffer.append(" "); } bodyBuffer.append(customizer.customizeBody(line)).append("\n"); } if (body == null) { assert false; } if (decl.returnType.equals("void")) { generateVoidFunction(out, decl, body, customizer); } else { generateNonVoidFunction(out, decl, body, customizer); } } state = BEFORE_VM_ENTRY_POINT_FUNCTION; continue; } if (state == BEFORE_FIRST_VM_ENTRY_POINT_FUNCTION) { continue; } out.println(line); } customizer.close(out); writer.close(); return Files.updateGeneratedContent(outputFile, ReadableSource.Static.fromString(writer.toString()), "// START GENERATED CODE", "// END GENERATED CODE", checkOnly); } private static void generateNonVoidFunction(PrintWriter out, VmEntryFunctionDeclaration decl, String body, Customizer customizer) { final String errReturnValue; if (decl.returnType.equals("boolean")) { errReturnValue = "false"; } else if (decl.returnType.equals("char")) { errReturnValue = " (char) JNI_ERR"; } else if (decl.returnType.equals("int") || decl.returnType.equals("byte") || decl.returnType.equals("char") || decl.returnType.equals("short") || decl.returnType.equals("float") || decl.returnType.equals("long") || decl.returnType.equals("double")) { errReturnValue = "JNI_ERR"; } else { errReturnValue = "as" + decl.returnType + "(0)"; } generateFunction(out, decl, body, "return " + errReturnValue + ";", customizer); } private static void generateVoidFunction(PrintWriter out, VmEntryFunctionDeclaration decl, String body, Customizer customizer) { generateFunction(out, decl, body, null, customizer); } private static void generateFunction(PrintWriter out, VmEntryFunctionDeclaration decl, String body, String returnStatement, Customizer customizer) { boolean insertTimers = TIME_JNI_FUNCTIONS && decl.name != null; out.println(" Pointer anchor = prologue(env);"); out.println(customizer.customizeTracePrologue(decl)); if (insertTimers) { out.println(" long startTime = System.nanoTime();"); } out.println(" try {"); out.print(body); out.println(" } catch (Throwable t) {"); out.println(customizer.customizeHandler(returnStatement)); out.println(" } finally {"); if (insertTimers) { out.println(" TIMER_" + decl.name + " += System.nanoTime() - startTime;"); out.println(" COUNTER_" + decl.name + "++;"); } out.println(" epilogue(anchor);"); out.println(customizer.customizeTraceEpilogue(decl)); out.println(" }"); out.println(" }"); if (insertTimers) { out.println(" public static long COUNTER_" + decl.name + ";"); out.println(" public static long TIMER_" + decl.name + ";"); } } /** * Command line interface for running the source code generator. * If the generation process modifies the existing source, then the exit * code of the JVM process will be non-zero. */ public static void main(String[] args) throws Exception { boolean updated = false; if (generate(false, JniFunctionsSource.class, JniFunctions.class)) { System.out.println("Source for " + JniFunctions.class + " was updated"); updated = true; } if (generate(false, JmmFunctionsSource.class, JmmFunctions.class)) { System.out.println("Source for " + JmmFunctions.class + " was updated"); updated = true; } if (generate(false, "com.oracle.max.vm", VMFunctionsSource.class, VMFunctions.class, new VMCustomizer(false))) { System.out.println("Source for " + VMFunctions.class + " was updated"); updated = true; } if (updated) { System.exit(1); } } }