[Rcpp-commits] r3896 - in pkg/Rcpp: . R src

noreply at r-forge.r-project.org noreply at r-forge.r-project.org
Mon Nov 5 19:36:29 CET 2012


Author: jjallaire
Date: 2012-11-05 19:36:29 +0100 (Mon, 05 Nov 2012)
New Revision: 3896

Modified:
   pkg/Rcpp/ChangeLog
   pkg/Rcpp/R/Attributes.R
   pkg/Rcpp/src/Attributes.cpp
   pkg/Rcpp/src/AttributesParser.cpp
   pkg/Rcpp/src/AttributesParser.h
Log:
use modules for attribute code generation

Modified: pkg/Rcpp/ChangeLog
===================================================================
--- pkg/Rcpp/ChangeLog	2012-11-05 12:32:18 UTC (rev 3895)
+++ pkg/Rcpp/ChangeLog	2012-11-05 18:36:29 UTC (rev 3896)
@@ -1,3 +1,10 @@
+2012-11-05  JJ Allaire <jj at rstudio.org>
+
+        * R/Attributes.R: use modules for attribute code generation
+        * src/Attributes.cpp: use modules for attribute code generation
+        * src/AttributesParser.h: use modules for attribute code generation
+        * src/AttributesParser.cpp: use modules for attribute code generation
+
 2012-11-04  Dirk Eddelbuettel  <edd at debian.org>
 
 	* tests/doRUnit.R: In "development releases" (such as 0.9.15.5) we

Modified: pkg/Rcpp/R/Attributes.R
===================================================================
--- pkg/Rcpp/R/Attributes.R	2012-11-05 12:32:18 UTC (rev 3895)
+++ pkg/Rcpp/R/Attributes.R	2012-11-05 18:36:29 UTC (rev 3896)
@@ -35,7 +35,7 @@
     # get the context (does code generation as necessary)
     file <- normalizePath(file, winslash = "/")
     context <- .Call("sourceCppContext", PACKAGE="Rcpp", file, code, .Platform)
-     
+    
     # perform a build if necessary
     if (context$buildRequired || rebuild) {
     
@@ -123,9 +123,9 @@
                 "force a rebuild)\n\n", sep="")
     }
     
-    # source the R script
-    scriptPath <- file.path(context$buildDirectory, context$rSourceFilename) 
-    source(scriptPath, local = env)
+    # load the module
+    dll <- dyn.load(context$dynlibPath)
+    populate(Module(context$moduleName, PACKAGE = dll, mustStart = TRUE), env)
     
     # return (invisibly) a list of exported functions
     invisible(context$exportedFunctions)
@@ -166,6 +166,7 @@
         cat("\nGenerated code for function definition:",
             "\n--------------------------------------------------------\n\n")
         cat(code)
+        cat("\n")
     }
     
     # source cpp into specified environment. if env is set to NULL
@@ -214,6 +215,11 @@
     
     # get a list of all source files
     cppFiles <- list.files(srcDir, pattern=glob2rx("*.c*"))
+    
+    # derive base names (will be used for modules)
+    cppFileBasenames <- tools:::file_path_sans_ext(cppFiles)
+    
+    # expend them to their full paths
     cppFiles <- file.path(srcDir, cppFiles)
     cppFiles <- normalizePath(cppFiles, winslash = "/")
     
@@ -222,24 +228,19 @@
     
     # generate exports
     invisible(.Call("compileAttributes", PACKAGE="Rcpp", 
-                    pkgdir, pkgname, cppFiles, includes, verbose, .Platform))
+                    pkgdir, pkgname, cppFiles, cppFileBasenames, 
+                    includes, verbose, .Platform))
 }
 
 
 # Print verbose output
 .printVerboseOutput <- function(context) {
     
-    cat("\nGenerated extern \"C\" functions",
+    cat("\nGenerated Rcpp module declaration:",
         "\n--------------------------------------------------------\n")
     cat(context$generatedCpp, sep="")
     
-    cat("\nGenerated R .Call bindings",
-        "\n-------------------------------------------------------\n\n")
-    cat(readLines(file.path(context$buildDirectory, 
-        context$rSourceFilename)), 
-        sep="\n")
-    
-    cat("Building shared library", 
+    cat("\nBuilding shared library", 
         "\n--------------------------------------------------------\n",
         "\nDIR: ", context$buildDirectory, "\n\n", sep="")
 }

Modified: pkg/Rcpp/src/Attributes.cpp
===================================================================
--- pkg/Rcpp/src/Attributes.cpp	2012-11-05 12:32:18 UTC (rev 3895)
+++ pkg/Rcpp/src/Attributes.cpp	2012-11-05 18:36:29 UTC (rev 3896)
@@ -72,98 +72,20 @@
         time_t lastModified_;
     };
     
-    // Generate the preamble for a C++ file (headers and using directives)
-    std::string generateCppPreamble(const std::vector<std::string>& includes,
-                                    const std::vector<std::string>& namespaces){
+    // Generate the preamble for a C++ file (headers)
+    std::string generateCppPreamble(const std::vector<std::string>& includes){
+        
         std::ostringstream ostr;
         for (std::size_t i=0;i<includes.size(); i++)
             ostr << includes[i] << std::endl;
         
-        ostr << std::endl;
-        
-        for (std::size_t i=0;i<namespaces.size(); i++)
-            ostr << namespaces[i] << std::endl;
-        
         return ostr.str();
     }
     
-    // Generate the C++ code required to make [[Rcpp::export]] functions
-    // available as C symbols with SEXP parameters and return
-    std::string generateCpp(const SourceFileAttributes& attributes,
-                            bool includePrototype,
-                            const std::string& contextId,
-                            bool verbose = false) {
-      
-        // source code we will build up
-        std::ostringstream ostr;    
-      
-        // process each attribute
-        for(std::vector<Attribute>::const_iterator 
-            it = attributes.begin(); it != attributes.end(); ++it) {
-            
-            // alias the attribute (bail if not export)
-            const Attribute& attribute = *it;
-            if (attribute.name() != kExportAttribute)
-                continue;
-            
-            // alias the function (bail if empty)
-            const Function& function = attribute.function();
-            if (function.empty())
-                continue;
-               
-            // verbose output
-            if (verbose)
-                Rcpp::Rcout << "  " << function << std::endl;
-               
-            // include prototype if requested
-            if (includePrototype) {
-                ostr << "// " << function.name() << std::endl;
-                ostr << function << ";";
-            }
-               
-            // write the SEXP-based function
-            ostr << std::endl << "RcppExport SEXP ";
-            if (!contextId.empty())
-                ostr << contextId << "_";
-            ostr << function.name() << "(";
-            const std::vector<Argument>& arguments = function.arguments();
-            for (size_t i = 0; i<arguments.size(); i++) {
-                const Argument& argument = arguments[i];
-                ostr << "SEXP " << argument.name() << "SEXP";
-                if (i != (arguments.size()-1))
-                    ostr << ", ";
-            }
-            ostr << ") {" << std::endl;
-            ostr << "BEGIN_RCPP" << std::endl;
-            for (size_t i = 0; i<arguments.size(); i++) {
-                const Argument& argument = arguments[i];
-                
-                // Rcpp::as to c++ type
-                ostr << "    " << argument.type().name() << " " << argument.name() 
-                     << " = " << "Rcpp::as<"  << argument.type().name() << " >(" 
-                     << argument.name() << "SEXP);" << std::endl;
-            }
-            
-            ostr << "    ";
-            if (!function.type().isVoid())
-                ostr << function.type() << " result = ";
-            ostr << function.name() << "(";
-            for (size_t i = 0; i<arguments.size(); i++) {
-                const Argument& argument = arguments[i];
-                ostr << argument.name();
-                if (i != (arguments.size()-1))
-                    ostr << ", ";
-            }
-            ostr << ");" << std::endl;
-            
-            std::string res = function.type().isVoid() ? "R_NilValue" : 
-                                                         "Rcpp::wrap(result)";
-            ostr << "    return " << res << ";" << std::endl;
-            ostr << "END_RCPP" << std::endl;
-            ostr << "}" << std::endl << std::endl;
-        }
-        
-        return ostr.str();
+    // Check if the passed attribute represents an exported function
+    bool isExportedFunction(const Attribute& attribute) {
+        return (attribute.name() == kExportAttribute) &&
+               !attribute.function().empty();
     }
     
     // Determine the exported name for a function 
@@ -173,102 +95,56 @@
         else
             return attribute.function().name();
     }
-    
-    // Generate R functions from the passed attributes
-    std::string generateRFunctions(const SourceFileAttributes& attributes,
-                                   const std::string& contextId,
-                                   const std::string& dllInfo = std::string()) {
-     
-        // source code we will build up
+ 
+    // Generate a module declaration
+    std::string generateCppModule(const std::string& moduleName, 
+                                  const SourceFileAttributes& attributes,
+                                  bool includeTypeInfo,
+                                  bool verbose) {
+        
         std::ostringstream ostr;
-         
-        // process each attribute
-        for(std::vector<Attribute>::const_iterator 
-            it = attributes.begin(); it != attributes.end(); ++it) {
+           
+        // module header
+        ostr << std::endl << "// Module: " << moduleName << std::endl;  
+        
+        // include namespace imports and function prototypes if requested
+        if (includeTypeInfo) {
             
-            // alias the attribute (bail if not export)
-            const Attribute& attribute = *it;
-            if (attribute.name() != kExportAttribute)
-                continue;
-            
-            // alias the function (bail if empty)
-            const Function& function = attribute.function();
-            if (function.empty())
-                continue;
-                
-            // print roxygen lines
-            for (size_t i=0; i<attribute.roxygen().size(); i++)
-                ostr << attribute.roxygen()[i] << std::endl;
-                    
-            // build the parameter list 
-            std::ostringstream argsOstr;
-            const std::vector<Argument>& arguments = function.arguments();
-            for (size_t i = 0; i<arguments.size(); i++) {
-                const Argument& argument = arguments[i];
-                argsOstr << argument.name();
-                if (i != (arguments.size()-1))
-                    argsOstr << ", ";
+            if (!attributes.namespaces().empty()) {
+                for (std::size_t i=0;i<attributes.namespaces().size(); i++)
+                    ostr << attributes.namespaces()[i] << std::endl;
             }
-            std::string args = argsOstr.str();
-            
-            // determine the function name
-            std::string name = exportedName(attribute);
-                
-            // write the function - use contextId to ensure symbol uniqueness
-            ostr << name << " <- function(" << args << ") {" 
-                 << std::endl;
-            ostr << "    ";
-            if (function.type().isVoid())
-                ostr << "invisible(";
-            ostr << ".Call(";
-            
-            // Two .Call styles are suppported -- if dllInfo is provided then
-            // do a direct call to getNativeSymbolInfo; otherwise we assume that
-            // the contextId is a package name and use the PACKAGE argument
-            if (!dllInfo.empty()) {
-                ostr << "getNativeSymbolInfo('"
-                     <<  contextId << "_" << function.name() 
-                     << "', " << dllInfo << ")";
-            } 
-            else {
-                ostr << "'" << contextId << "_" << function.name() << "', "
-                     << "PACKAGE = '" << contextId << "'";
-            }
-            
-            // add arguments
-            if (!args.empty())
-                ostr << ", " << args;
-            ostr << ")";
-            if (function.type().isVoid())
-                ostr << ")";
-            ostr << std::endl;
         
-            ostr << "}" << std::endl << std::endl;
+            if (!attributes.prototypes().empty()) {
+                for (std::size_t i=0;i<attributes.prototypes().size(); i++)
+                    ostr << attributes.prototypes()[i] << ";" << std::endl;
+            }
         }
         
-        return ostr.str();                                
-    }
-    
-    
-    // Generate the R code used to .Call the exported C symbols
-    std::string generateR(const SourceFileAttributes& attributes,
-                          const std::string& contextId,
-                          const std::string& dynlibPath) {
+        // output the module
+        ostr << "RCPP_MODULE(" << moduleName  << ") {" << std::endl;
+        for(std::vector<Attribute>::const_iterator 
+                it = attributes.begin(); it != attributes.end(); ++it) {
             
-        // source code we will build up
-        std::ostringstream ostr;
+            // verify this is an exported function 
+            if (isExportedFunction(*it)) {
+                     
+                // verbose output
+                const Function& function = it->function();
+                if (verbose)
+                    Rcpp::Rcout << "  " << function << std::endl;
+              
+                // add module function export
+                ostr << "    Rcpp::function(\"" << exportedName(*it) << "\", &"
+                     << function.name() << ");" << std::endl;
+                      
+            } 
+        }
+        ostr << "}" << std::endl;
         
-        // DLLInfo - hide using . and ensure uniqueness using contextId
-        std::string dllInfo = "`." + contextId + "_DLLInfo`";
-        ostr << dllInfo << " <- dyn.load('" << dynlibPath << "')" 
-             << std::endl << std::endl;
-        
-        // Generate R functions and return
-        ostr << generateRFunctions(attributes, contextId, dllInfo);
-        return ostr.str();
-    }
+        return ostr.str();    
+    }    
     
-    
     // Class that manages generation of source code for the sourceCpp dynlib
     class SourceCppDynlib {
     public:
@@ -300,9 +176,11 @@
             Rcpp::Function dircreate = Rcpp::Environment::base_env()["dir.create"];
             dircreate(buildDirectory_);
             
-            // use the directory name as a unique context id (need this to uniquely
-            // name functions, DLLInfo objects, and the shared library itself)
-            contextId_ = Rcpp::as<std::string>(basename(buildDirectory_));
+            // generate a random module name
+            Rcpp::Function sample = Rcpp::Environment::base_env()["sample"];
+            std::ostringstream ostr;
+            ostr << "sourceCpp_" << Rcpp::as<int>(sample(100000, 1));
+            moduleName_ = ostr.str();
             
             // regenerate the source code
             regenerateSource();
@@ -312,12 +190,12 @@
         
         bool isBuilt() const { return FileInfo(dynlibPath()).exists(); };
                 
-        bool isSourceDirty() const {
-            
+        bool isSourceDirty() const {          
             // source file out of date means we're dirty
-            if (FileInfo(cppSourcePath_).lastModified() != cppSourceLastModified_)
+            if (FileInfo(cppSourcePath_).lastModified() > 
+                FileInfo(generatedCppSourcePath()).lastModified())
                 return true;
-                
+                     
             // no dynlib means we're dirty
             if (!FileInfo(dynlibPath()).exists())
                 return true;
@@ -335,25 +213,20 @@
             // parse attributes
             SourceFileAttributes sourceAttributes(cppSourcePath_);
         
-            // generate cpp for attributes and append them 
-            generatedCpp_ = generateCpp(sourceAttributes, false, contextId_);
+            // generate RCPP module
+            generatedCpp_ = generateCppModule(moduleName(), 
+                                              sourceAttributes, 
+                                              false,    // no typeinfo 
+                                              false);   // not verbose
+            
+            // open source file and append module
             std::ofstream cppOfs(generatedCppSourcePath().c_str(), 
                                  std::ofstream::out | std::ofstream::app);
             if (cppOfs.fail())
                 throw Rcpp::file_io_error(generatedCppSourcePath());
+            cppOfs << std::endl;
             cppOfs << generatedCpp_;
             cppOfs.close();
-        
-            // generate R for attributes and write it into the build directory
-            std::string rCode = generateR(sourceAttributes, 
-                                          contextId_, 
-                                          dynlibPath());
-            std::ofstream rOfs(generatedRSourcePath().c_str(), 
-                               std::ofstream::out | std::ofstream::trunc);
-             if (cppOfs.fail())
-                throw Rcpp::file_io_error(generatedRSourcePath());
-            rOfs << rCode;
-            rOfs.close();
             
             // enumerate exported functions and dependencies
             exportedFunctions_.clear();
@@ -370,6 +243,10 @@
             }
         }
         
+        const std::string& moduleName() const {
+            return moduleName_;
+        }
+        
         const std::string& cppSourcePath() const {
             return cppSourcePath_;
         }
@@ -386,12 +263,8 @@
             return cppSourceFilename_;
         }
          
-        std::string rSourceFilename() const {
-            return cppSourceFilename() + ".R";   
-        }
-        
         std::string dynlibFilename() const {
-            return contextId_ + dynlibExt_;
+            return moduleName() + dynlibExt_;
         }
         
         std::string dynlibPath() const {
@@ -410,16 +283,12 @@
            return buildDirectory_ + fileSep_ + cppSourceFilename(); 
         }
         
-        std::string generatedRSourcePath() const {
-           return buildDirectory_ + fileSep_ + rSourceFilename(); 
-        }
-        
     private:
         std::string cppSourcePath_;
         time_t cppSourceLastModified_;
         std::string generatedCpp_;
         std::string cppSourceFilename_;
-        std::string contextId_;
+        std::string moduleName_;
         std::string buildDirectory_;
         std::string fileSep_;
         std::string dynlibExt_;
@@ -622,7 +491,7 @@
     bool buildRequired = false;
     
     // if there is no dynlib in the cache then create one
-    if (dynlib.isEmpty()) {
+    if (dynlib.isEmpty()) {   
         buildRequired = true;
         dynlib = SourceCppDynlib(file, platform);
         if (!code.empty())
@@ -633,24 +502,24 @@
         
     // if the cached dynlib is dirty then regenerate the source
     else if (dynlib.isSourceDirty()) {
-        buildRequired = true;
+        buildRequired = true;    
         dynlib.regenerateSource();
     }
     
     // if the dynlib hasn't yet been built then note that
     else if (!dynlib.isBuilt()) {
-        buildRequired = true;
+        buildRequired = true; 
     }
     
     // return context as a list
     Rcpp::List context;
+    context["moduleName"] = dynlib.moduleName();
     context["cppSourcePath"] = dynlib.cppSourcePath();
     context["buildRequired"] = buildRequired;
     context["buildDirectory"] = dynlib.buildDirectory();
     context["generatedCpp"] = dynlib.generatedCpp();
     context["exportedFunctions"] = dynlib.exportedFunctions();
     context["cppSourceFilename"] = dynlib.cppSourceFilename();
-    context["rSourceFilename"] = dynlib.rSourceFilename();
     context["dynlibFilename"] = dynlib.dynlibFilename();
     context["dynlibPath"] = dynlib.dynlibPath();
     context["depends"] = dynlib.depends();
@@ -663,6 +532,7 @@
 RcppExport SEXP compileAttributes(SEXP sPackageDir, 
                                   SEXP sPackageName,
                                   SEXP sCppFiles,
+                                  SEXP sCppFileBasenames,
                                   SEXP sIncludes,
                                   SEXP sVerbose,
                                   SEXP sPlatform) {
@@ -672,23 +542,24 @@
     std::string packageName = Rcpp::as<std::string>(sPackageName);
     std::vector<std::string> cppFiles = 
                     Rcpp::as<std::vector<std::string> >(sCppFiles);
+    std::vector<std::string> cppFileBasenames = 
+                    Rcpp::as<std::vector<std::string> >(sCppFileBasenames);
     std::vector<std::string> includes = 
                     Rcpp::as<std::vector<std::string> >(sIncludes);
     bool verbose = Rcpp::as<bool>(sVerbose);
     Rcpp::List platform = Rcpp::as<Rcpp::List>(sPlatform);
      
-    // Establish stream and namespace list for CPP code
+    // Establish stream for Cpp code
     std::string fileSep = Rcpp::as<std::string>(platform["file.sep"]);
     std::string cppExportsFile = packageDir + fileSep + "src" + 
                                  fileSep + "RcppExports.cpp";
     CppExportsStream cppStream(cppExportsFile);
-    std::vector<std::string> namespaces;
     
     // Establish stream for R code
     std::string rExportsFile = packageDir + fileSep + "R" + 
                                fileSep + "RcppExports.R";
     RExportsStream rStream(rExportsFile);
-    
+     
     // Parse attributes from each file and generate code as required. 
     for (std::size_t i=0; i<cppFiles.size(); i++) {
         
@@ -702,35 +573,24 @@
         if (!attributes.empty() && verbose)
             Rcpp::Rcout << "Exports from " << cppFile << ":" << std::endl;
         
-        // generate C++ code -- use the package name as a context id to ensure
-        // unique symbol names for platforms where R can't do a local dyn.load
-        cppStream << generateCpp(attributes, true, packageName, verbose);
+        // generate C++ module
+        std::string moduleName = "RcppExports_" + cppFileBasenames[i];
+        cppStream << generateCppModule(moduleName, attributes, true, verbose);
         
-        // track namespaces
-        std::copy(attributes.namespaces().begin(),
-                  attributes.namespaces().end(),
-                  std::back_inserter(namespaces));
-        
-        // generate R code
-        rStream << generateRFunctions(attributes, packageName);
-        
+        // genereate R loadModule 
+        rStream << "loadModule(\"" << moduleName << "\", what = TRUE)" 
+                << std::endl;
+      
         // verbose if requested
         if (!attributes.empty() && verbose)
             Rcpp::Rcout << std::endl;
     }
-    
-    // eliminate namespace duplicates
-    std::sort(namespaces.begin(), namespaces.end());
-    std::vector<std::string>::const_iterator it = 
-                        std::unique(namespaces.begin(), namespaces.end());
-    namespaces.resize( it - namespaces.begin());
-                        
-    
+                           
     // commit the code
-    bool wroteCpp = cppStream.commit(generateCppPreamble(includes, namespaces));     
+    bool wroteCpp = cppStream.commit(generateCppPreamble(includes));     
     bool wroteR = rStream.commit();
     
-    // verbose output
+    // verbose outputs
     if (verbose) {
         Rcpp::Rcout << "Generating exports files:" << std::endl;
         Rcpp::Rcout << "  RcppExports.cpp (" <<  

Modified: pkg/Rcpp/src/AttributesParser.cpp
===================================================================
--- pkg/Rcpp/src/AttributesParser.cpp	2012-11-05 12:32:18 UTC (rev 3895)
+++ pkg/Rcpp/src/AttributesParser.cpp	2012-11-05 18:36:29 UTC (rev 3896)
@@ -299,8 +299,15 @@
              
             // parse the function (unless we are at the end of the file in
             // which case we print a warning)
-            if ((lineNumber + 1) < lines_.size())
+            if ((lineNumber + 1) < lines_.size()) {
                 function = parseFunction(lineNumber + 1);
+                if (!function.empty()) {
+                    std::ostringstream ostr;
+                    ostr << function;
+                    prototypes_.push_back(ostr.str());
+                }
+                
+            }
             else 
                 rcppExportWarning("No function found", lineNumber);    
         }  

Modified: pkg/Rcpp/src/AttributesParser.h
===================================================================
--- pkg/Rcpp/src/AttributesParser.h	2012-11-05 12:32:18 UTC (rev 3895)
+++ pkg/Rcpp/src/AttributesParser.h	2012-11-05 18:36:29 UTC (rev 3896)
@@ -181,6 +181,11 @@
         const std::vector<std::string>& namespaces() const {
             return namespaces_;
         }
+        
+        // function prototypes
+        const std::vector<std::string>& prototypes() const {
+            return prototypes_;
+        }
        
     private:
     
@@ -224,6 +229,7 @@
         CharacterVector lines_;
         std::vector<Attribute> attributes_;
         std::vector<std::string> namespaces_;
+        std::vector<std::string> prototypes_;
         std::vector<std::string> roxygenBuffer_;
     };
 



More information about the Rcpp-commits mailing list