/* * Base class for C code generator helper. * * Copyright (c) 2005-2009 The Regents of the University of California. All * rights reserved. Permission is hereby granted, without written agreement and * without license or royalty fees, to use, copy, modify, and distribute this * software and its documentation for any purpose, provided that the above * copyright notice and the following two paragraphs appear in all copies of * this software. * * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND * FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN * "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO PROVIDE * MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. * * PT_COPYRIGHT_VERSION_2 COPYRIGHTENDKEY * */ package ptolemy.codegen.c.kernel; import java.io.File; import java.io.FileFilter; import java.net.URL; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import ptolemy.actor.Actor; import ptolemy.actor.CompositeActor; import ptolemy.actor.TypedIOPort; import ptolemy.actor.util.DFUtilities; import ptolemy.codegen.kernel.CodeGenerator; import ptolemy.codegen.kernel.CodeGeneratorHelper; import ptolemy.codegen.kernel.ParseTreeCodeGenerator; import ptolemy.data.BooleanToken; import ptolemy.data.expr.Parameter; import ptolemy.data.type.BaseType; import ptolemy.data.type.Type; import ptolemy.domains.fsm.modal.ModalController; import ptolemy.kernel.util.IllegalActionException; import ptolemy.util.ExecuteCommands; import ptolemy.util.FileUtilities; import ptolemy.util.StreamExec; import ptolemy.util.StringUtilities; ////////////////////////////////////////////////////////////////////////// //// CCodeGeneratorHelper /** * Base class for C code generator helper. * * <p> * Actor helpers extend this class and optionally define the generateFireCode(), * generateInitializeCode(), generatePrefireCode(), generatePostfireCode(), * generatePreinitializeCode(), and generateWrapupCode() methods. * * <p> * In derived classes, these methods, if present, make actor specific changes to * the corresponding code. If these methods are not present, then the parent * class will automatically read the corresponding .c file and substitute in the * corresponding code block. For example, generateInitializeCode() reads the * <code>initBlock</code>, processes the macros and adds the resulting code * block to the output. * * <p> * For a complete list of methods to define, see * {@link ptolemy.codegen.kernel.CodeGeneratorHelper}. * * <p> * For further details, see <code>$PTII/ptolemy/codegen/README.html</code> * * @author Christopher Brooks, Edward Lee, Man-Kit Leung, Gang Zhou, Ye Zhou * @version $Id$ * @since Ptolemy II 6.0 o * @Pt.ProposedRating Yellow (cxh) * @Pt.AcceptedRating Red (cxh) */ public class CCodeGeneratorHelper extends CodeGeneratorHelper { /** * Create a new instance of the C code generator helper. * @param component The actor object for this helper. */ public CCodeGeneratorHelper(Object component) { super(component); } /////////////////////////////////////////////////////////////////// //// public methods //// /** * Return a new parse tree code generator to use with expressions. * @return the parse tree code generator to use with expressions. */ public ParseTreeCodeGenerator getParseTreeCodeGenerator() { // FIXME: We need to create new ParseTreeCodeGenerator each time // here or else we get lots of test failures. It would be better // if we could use the same CParseTreeCodeGenerator over and over. if (!(_parseTreeCodeGenerator instanceof CParseTreeCodeGenerator)) { _parseTreeCodeGenerator = new CParseTreeCodeGenerator( _codeGenerator); } return _parseTreeCodeGenerator; } /** * Generate variable declarations for inputs and outputs and parameters. * Append the declarations to the given string buffer. * @return code The generated code. * @exception IllegalActionException If the helper class for the model * director cannot be found. */ public String generateVariableDeclaration() throws IllegalActionException { StringBuffer code = new StringBuffer(); String name = CodeGeneratorHelper.generateName(getComponent()); // Generate variable declarations for referenced parameters. String referencedParameterDeclaration = _generateReferencedParameterDeclaration(); if (referencedParameterDeclaration.length() > 1) { code.append(_eol + _codeGenerator.comment(name + "'s referenced parameter declarations.")); code.append(referencedParameterDeclaration); } // Generate variable declarations for input ports. String inputVariableDeclaration = _generateInputVariableDeclaration(); if (inputVariableDeclaration.length() > 1) { code.append(_eol + _codeGenerator.comment(name + "'s input variable declarations.")); code.append(inputVariableDeclaration); } // Generate variable declarations for output ports. String outputVariableDeclaration = _generateOutputVariableDeclaration(); if (outputVariableDeclaration.length() > 1) { code.append(_eol + _codeGenerator.comment(name + "'s output variable declarations.")); code.append(outputVariableDeclaration); } // Generate type convert variable declarations. String typeConvertVariableDeclaration = _generateTypeConvertVariableDeclaration(); if (typeConvertVariableDeclaration.length() > 1) { code.append(_eol + _codeGenerator.comment(name + "'s type convert variable declarations.")); code.append(typeConvertVariableDeclaration); } return processCode(code.toString()); } /** * Get the code generator associated with this helper class. * @return The code generator associated with this helper class. * @see #setCodeGenerator(CodeGenerator) */ public CCodeGenerator getCodeGenerator() { return (CCodeGenerator) _codeGenerator; } /** * Get the files needed by the code generated from this helper class. This * base class returns an empty set. * @return A set of strings that are header files needed by the code * generated from this helper class. * @exception IllegalActionException Not Thrown in this base class. */ public Set getHeaderFiles() throws IllegalActionException { Set files = super.getHeaderFiles(); files.addAll(_includeFiles); return files; } /** * Get the header files needed to compile with the jvm library. * @return A set of strings that are names of the header files needed by the * code generated for jvm library * @exception IllegalActionException Not Thrown in this subclass. */ public Set getJVMHeaderFiles() throws IllegalActionException { String javaHome = StringUtilities.getProperty("java.home"); ExecuteCommands executeCommands = getCodeGenerator() .getExecuteCommands(); if (executeCommands == null) { executeCommands = new StreamExec(); } if (!_printedJVMWarning) { // We only print this once. _printedJVMWarning = true; executeCommands.stdout(_eol + _eol + "WARNING: This model uses an actor that " + "links with the jvm library." + _eol + " To properly run the executable, you must have jvm.dll" + " in your path." + _eol + " If you do not, then when you run the executable, " + "it will immediately exit" + _eol + " with no message!" + _eol + " For example, place " + javaHome + "\\bin\\client" + _eol + " in your path. If you are running Vergil from the " + "command line as " + _eol + " $PTII/bin/ptinvoke, " + "then this has been handled for you." + _eol + " If you are running via Eclipse, then you must update " + "your path by hand." + _eol + _eol + _eol); } String jreBinClientPath = javaHome + File.separator + "bin" + File.separator + "client"; executeCommands.stdout(_eol + _eol + "CCodeGeneratorHelper: appended to path " + jreBinClientPath); executeCommands.appendToPath(jreBinClientPath); javaHome = javaHome.replace('\\', '/'); if (javaHome.endsWith("/jre")) { javaHome = javaHome.substring(0, javaHome.length() - 4); } if (!new File(javaHome + "/include").isDirectory()) { // It could be that we are running under WebStart // or otherwise in a JRE, so we should look for the JDK. File potentialJavaHomeParentFile = new File(javaHome) .getParentFile(); // Loop through twice, once with the parent, once with // C:/Program Files/Java. This is lame, but easy for (int loop = 2; loop > 0; loop--) { // Get all the directories that have include/jni.h under them. File[] jdkFiles = potentialJavaHomeParentFile .listFiles(new FileFilter() { public boolean accept(File pathname) { return new File(pathname, "/include/jni.h") .canRead(); } }); if (jdkFiles != null && jdkFiles.length >= 1) { // Sort and get the last directory, which should // be the most recent JDK. java.util.Arrays.sort(jdkFiles); javaHome = jdkFiles[jdkFiles.length - 1].toString(); break; } else { // Not found, please try again. potentialJavaHomeParentFile = new File( "C:\\Program Files\\Java"); } } } getCodeGenerator().addInclude("-I\"" + javaHome + "/include\""); String osName = StringUtilities.getProperty("os.name"); String platform = "win32"; if (osName.startsWith("Linux")) { platform = "linux"; } else if (osName.startsWith("SunOS")) { platform = "solaris"; } else if (osName.startsWith("Mac OS X")) { platform = "Mac OS X"; } String jvmLoaderDirective = "-ljvm"; String libjvmAbsoluteDirectory = ""; if (platform.equals("win32")) { getCodeGenerator().addInclude( "-I\"" + javaHome + "/include/" + platform + "\""); // The directive we use to find jvm.dll, which is usually in // something like c:/Program Files/Java/jre1.6.0_04/bin/client/jvm.dll jvmLoaderDirective = "-ljvm"; String ptIIDir = StringUtilities.getProperty("ptolemy.ptII.dir") .replace('\\', '/'); String libjvmRelativeDirectory = "ptolemy/codegen/c/lib/win"; libjvmAbsoluteDirectory = ptIIDir + "/" + libjvmRelativeDirectory; String libjvmFileName = "libjvm.dll.a"; String libjvmPath = libjvmAbsoluteDirectory + "/" + libjvmFileName; if (!new File(libjvmPath).canRead()) { // If we are under WebStart or running from jar files, we // will need to copy libjvm.dll.a from the jar file // that gcc can find it. URL libjvmURL = Thread.currentThread().getContextClassLoader() .getResource( libjvmRelativeDirectory + "/" + libjvmFileName); if (libjvmURL != null) { String libjvmAbsolutePath = null; try { // Look for libjvm.dll.a in the codegen directory File libjvmFileCopy = new File( getCodeGenerator().codeDirectory.asFile(), "libjvm.dll.a"); if (!libjvmFileCopy.canRead()) { // Create libjvm.dll.a in the codegen directory FileUtilities.binaryCopyURLToFile(libjvmURL, libjvmFileCopy); } libjvmAbsolutePath = libjvmFileCopy.getAbsolutePath(); if (libjvmFileCopy.canRead()) { libjvmAbsolutePath = libjvmAbsolutePath.replace( '\\', '/'); libjvmAbsoluteDirectory = libjvmAbsolutePath .substring(0, libjvmAbsolutePath .lastIndexOf("/")); // Get rid of everything before the last /lib // and the .dll.a jvmLoaderDirective = "-l" + libjvmAbsolutePath.substring( libjvmAbsolutePath .lastIndexOf("/lib") + 4, libjvmAbsolutePath.length() - 6); } } catch (Exception ex) { throw new IllegalActionException(getComponent(), ex, "Could not copy \"" + libjvmURL + "\" to the file system, path was: \"" + libjvmAbsolutePath + "\""); } } } } else if (platform.equals("Mac OS X")) { if (javaHome != null) { libjvmAbsoluteDirectory = javaHome + "/../Libraries"; } } else { // Solaris, Linux etc. getCodeGenerator().addInclude( "-I\"" + javaHome + "/include/" + platform + "\""); } getCodeGenerator().addLibrary("-L\"" + libjvmAbsoluteDirectory + "\""); getCodeGenerator().addLibrary(jvmLoaderDirective); Set files = new HashSet(); files.add("<jni.h>"); return files; } /////////////////////////////////////////////////////////////////// //// protected methods //// /** * Generate input variable declarations. * @return a String that declares input variables. * @exception IllegalActionException If thrown while getting port * information. */ protected String _generateInputVariableDeclaration() throws IllegalActionException { boolean dynamicReferencesAllowed = ((BooleanToken) _codeGenerator.allowDynamicMultiportReference .getToken()).booleanValue(); StringBuffer code = new StringBuffer(); Iterator inputPorts = ((Actor) getComponent()).inputPortList() .iterator(); while (inputPorts.hasNext()) { TypedIOPort inputPort = (TypedIOPort) inputPorts.next(); if (!inputPort.isOutsideConnected()) { continue; } code.append("static " + targetType(inputPort.getType()) + " " + generateName(inputPort)); int bufferSize = getBufferSize(inputPort); if (inputPort.isMultiport()) { code.append("[" + inputPort.getWidth() + "]"); if (bufferSize > 1 || dynamicReferencesAllowed) { code.append("[" + bufferSize + "]"); } } else { if (bufferSize > 1) { code.append("[" + bufferSize + "]"); } } code.append(";" + _eol); } return code.toString(); } /** * Generate output variable declarations. * @return a String that declares output variables. * @exception IllegalActionException If thrown while getting port * information. */ protected String _generateOutputVariableDeclaration() throws IllegalActionException { StringBuffer code = new StringBuffer(); Iterator outputPorts = ((Actor) getComponent()).outputPortList() .iterator(); while (outputPorts.hasNext()) { TypedIOPort outputPort = (TypedIOPort) outputPorts.next(); // If either the output port is a dangling port or // the output port has inside receivers. if (!outputPort.isOutsideConnected() || outputPort.isInsideConnected()) { code.append("static " + targetType(outputPort.getType()) + " " + generateName(outputPort)); if (outputPort.isMultiport()) { code.append("[" + outputPort.getWidthInside() + "]"); } int bufferSize = getBufferSize(outputPort); if (bufferSize > 1) { code.append("[" + bufferSize + "]"); } code.append(";" + _eol); } } return code.toString(); } /** * Generate referenced parameter declarations. * @return a String that declares referenced parameters. * @exception IllegalActionException If thrown while getting modified * variable information. */ protected String _generateReferencedParameterDeclaration() throws IllegalActionException { StringBuffer code = new StringBuffer(); if (_referencedParameters != null) { Iterator parameters = _referencedParameters.iterator(); while (parameters.hasNext()) { Parameter parameter = (Parameter) parameters.next(); // avoid duplicate declaration. if (!_codeGenerator.getModifiedVariables().contains(parameter)) { code.append("static " + targetType(parameter.getType()) + " " + generateVariableName(parameter) + ";" + _eol); } } } return code.toString(); } /** * Generate the type conversion statement for the particular offset of the * two given channels. This assumes that the offset is the same for both * channel. Advancing the offset of one has to advance the offset of the * other. * @param source The given source channel. * @param sink The given sink channel. * @param offset The given offset. * @return The type convert statement for assigning the converted source * variable to the sink variable with the given offset. * @exception IllegalActionException If there is a problem getting the * helpers for the ports or if the conversion cannot be handled. */ protected String _generateTypeConvertStatement(Channel source, Channel sink, int offset) throws IllegalActionException { Type sourceType = ((TypedIOPort) source.port).getType(); Type sinkType = ((TypedIOPort) sink.port).getType(); // In a modal model, a refinement may have an output port which is // not connected inside, in this case the type of the port is // unknown and there is no need to generate type conversion code // because there is no token transferred from the port. if (sourceType == BaseType.UNKNOWN) { return ""; } // The references are associated with their own helper, so we need // to find the associated helper. String sourcePortChannel = generateSimpleName(source.port) + "#" + source.channelNumber + ", " + offset; String sourceRef = ((CodeGeneratorHelper) _getHelper(source.port .getContainer())).getReference(sourcePortChannel); String sinkPortChannel = generateSimpleName(sink.port) + "#" + sink.channelNumber + ", " + offset; // For composite actor, generate a variable corresponding to // the inside receiver of an output port. // FIXME: I think checking sink.port.isOutput() is enough here. if (sink.port.getContainer() instanceof CompositeActor && sink.port.isOutput()) { sinkPortChannel = "@" + sinkPortChannel; } String sinkRef = ((CodeGeneratorHelper) _getHelper(sink.port .getContainer())).getReference(sinkPortChannel, true); // When the sink port is contained by a modal controller, it is // possible that the port is both input and output port. we need // to pay special attention. Directly calling getReference() will // treat it as output port and this is not correct. // FIXME: what about offset? if (sink.port.getContainer() instanceof ModalController || sink.port.getContainer() instanceof ptolemy.domains.modal.modal.ModalController) { sinkRef = generateName(sink.port); if (sink.port.isMultiport()) { sinkRef = sinkRef + "[" + sink.channelNumber + "]"; } } try { sourceRef = _generateTypeConvertMethod(sourceRef, codeGenType(sinkType), codeGenType(sourceType)); } catch (IllegalActionException ex) { throw new IllegalActionException(getComponent(), ex, "Failed to generate " + "type convert method for " + sinkRef + " of type " + sinkType + " (converted to \"" + codeGenType(sinkType) + "\") = " + sourceRef + " of type " + sourceType + " (converted to \"" + codeGenType(sourceType) +"\")"); } return sinkRef + " = " + sourceRef + ";" + _eol; } /** * Generate type convert variable declarations. * @return a String that declares type convert variables. * @exception IllegalActionException If thrown while getting port * information. */ protected String _generateTypeConvertVariableDeclaration() throws IllegalActionException { StringBuffer code = new StringBuffer(); Iterator channels = _getTypeConvertChannels().iterator(); while (channels.hasNext()) { Channel channel = (Channel) channels.next(); Type portType = ((TypedIOPort) channel.port).getType(); if (isPrimitive(portType)) { code.append("static "); code.append(targetType(portType)); code.append(" " + _getTypeConvertReference(channel)); //int bufferSize = getBufferSize(channel.port); int bufferSize = Math.max(DFUtilities .getTokenProductionRate(channel.port), DFUtilities .getTokenConsumptionRate(channel.port)); if (bufferSize > 1) { code.append("[" + bufferSize + "]"); } code.append(";" + _eol); } } return code.toString(); } /** * Return the prototype for fire functions. * @return The string"(void)" so as to avoid the avr-gcc 3.4.6 warning: * "function declaration isn't a prototype" */ protected String _getFireFunctionArguments() { return "(void)"; } protected String _replaceMacro(String macro, String parameter) throws IllegalActionException { String result = super._replaceMacro(macro, parameter); if (result != null) { return result; } if (macro.equals("include")) { _includeFiles.add(parameter); return ""; } else if (macro.equals("refinePrimitiveType")) { TypedIOPort port = getPort(parameter); if (port == null) { throw new IllegalActionException( parameter + " is not a port. $refinePrimitiveType macro takes in a port."); } if (isPrimitive(port.getType())) { return ".payload." + codeGenType(port.getType()); } else { return ""; } } // We will assume that it is a call to a polymorphic // functions. //String[] call = macro.split("_"); getCodeGenerator().markFunctionCalled(macro, this); result = macro + "(" + parameter + ")"; return result; } /////////////////////////////////////////////////////////////////// //// private fields //// /** The set of header files that needed to be included. */ private final Set _includeFiles = new HashSet(); /** True if we have printed the JVM warning. */ private boolean _printedJVMWarning; }