[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