Initial implementation of ModuleCache and dependency tracking of used modules

felipesanches-svg
Marius Kintel 2012-02-04 01:43:53 +01:00
parent 44ff853990
commit b9d218e137
17 changed files with 307 additions and 114 deletions

View File

@ -256,8 +256,13 @@ o Caching
- Test that caching is actually performed (speedup + same results)
- Test the modifier characters correctly influence the cache (also when
added/removed)
- Test the individual caches
- PolySetCache
- CGALCache
- nodecache
- ModuleCache
o other tests
- export
- cmd-line tests
- leaf nodes having children, e.g. cube() cylinder();
- dependency tracking
- dependency tracking (use and include)

View File

@ -188,6 +188,7 @@ HEADERS += src/parsersettings.h \
src/traverser.h \
src/nodecache.h \
src/nodedumper.h \
src/ModuleCache.h \
src/PolySetCache.h \
src/PolySetEvaluator.h \
src/CSGTermEvaluator.h \
@ -230,6 +231,7 @@ SOURCES += src/mathc99.cc \
src/nodedumper.cc \
src/traverser.cc \
src/PolySetEvaluator.cc \
src/ModuleCache.cc \
src/PolySetCache.cc \
src/Tree.cc \
\

View File

@ -30,7 +30,7 @@ public:
QString autoReloadInfo;
Context root_ctx;
AbstractModule *root_module; // Result of parsing
Module *root_module; // Result of parsing
ModuleInstantiation root_inst; // Top level instance
AbstractNode *absolute_root_node; // Result of tree evaluation
AbstractNode *root_node; // Root if the root modifier (!) is used

87
src/ModuleCache.cc Normal file
View File

@ -0,0 +1,87 @@
#include "ModuleCache.h"
#include "module.h"
#include "printutils.h"
#include "boosty.h"
#include "openscad.h"
#include <stdio.h>
#include <sstream>
#include <sys/stat.h>
ModuleCache *ModuleCache::inst = NULL;
Module *ModuleCache::evaluate(const std::string &filename)
{
Module *cached = NULL;
struct stat st;
memset(&st, 0, sizeof(struct stat));
stat(filename.c_str(), &st);
std::stringstream idstream;
idstream << std::hex << st.st_mtime << "." << st.st_size;
std::string cache_id = idstream.str();
if (this->entries.find(filename) != this->entries.end() &&
this->entries[filename].cache_id == cache_id) {
#ifdef DEBUG
PRINTB("Using cached library: %s (%s)", filename % cache_id);
#endif
PRINTB("%s", this->entries[filename].msg);
cached = &(*this->entries[filename].module);
}
if (cached) {
cached->handleDependencies();
return cached;
}
else {
if (this->entries.find(filename) != this->entries.end()) {
PRINTB("Recompiling cached library: %s (%s)", filename % cache_id);
}
else {
PRINTB("Compiling library '%s'.", filename);
}
}
FILE *fp = fopen(filename.c_str(), "rt");
if (!fp) {
fprintf(stderr, "WARNING: Can't open library file '%s'\n", filename.c_str());
return NULL;
}
std::stringstream text;
char buffer[513];
int ret;
while ((ret = fread(buffer, 1, 512, fp)) > 0) {
buffer[ret] = 0;
text << buffer;
}
fclose(fp);
print_messages_push();
cache_entry e = { NULL, cache_id, std::string("WARNING: Library `") + filename + "' tries to recursively use itself!" };
if (this->entries.find(filename) != this->entries.end())
delete this->entries[filename].module;
this->entries[filename] = e;
std::string pathname = boosty::stringy(fs::path(filename).parent_path());
Module *lib_mod = dynamic_cast<Module*>(parse(text.str().c_str(), pathname.c_str(), 0));
if (lib_mod) {
this->entries[filename].module = lib_mod;
this->entries[filename].msg = print_messages_stack.back();
} else {
this->entries.erase(filename);
}
print_messages_pop();
return lib_mod;
}
void ModuleCache::clear()
{
this->entries.clear();
}

22
src/ModuleCache.h Normal file
View File

@ -0,0 +1,22 @@
#include <string>
#include <boost/unordered_map.hpp>
class ModuleCache
{
public:
static ModuleCache *instance() { if (!inst) inst = new ModuleCache; return inst; }
class Module *evaluate(const std::string &filename);
void clear();
private:
ModuleCache() {}
~ModuleCache() {}
static ModuleCache *inst;
struct cache_entry {
class Module *module;
std::string cache_id, msg;
};
boost::unordered_map<std::string, cache_entry> entries;
};

View File

@ -162,6 +162,7 @@ AbstractNode *Context::evaluate_module(const ModuleInstantiation &inst) const
}
if (this->usedlibs_p) {
BOOST_FOREACH(const ModuleContainer::value_type &m, *this->usedlibs_p) {
assert(m.second);
if (m.second->modules.find(inst.name()) != m.second->modules.end()) {
Context ctx(this->parent, m.second);
return m.second->modules[inst.name()]->evaluate(&ctx, &inst);

View File

@ -52,7 +52,7 @@ int lexerget_lineno(void);
static void yyunput(int, char*) __attribute__((unused));
#endif
extern const char *parser_input_buffer;
extern const char *parser_source_path;
extern std::string parser_source_path;
#define YY_INPUT(buf,result,max_size) { \
if (yyin && yyin != stdin) { \
@ -108,6 +108,7 @@ use[ \t\r\n>]*"<" { BEGIN(cond_use); }
<cond_use>{
[^\t\r\n>]+ { filename = yytext; }
">" {
PRINTB("USE: %s", filename);
BEGIN(INITIAL);
fs::path usepath;
if (boosty::is_absolute(fs::path(filename))) {

View File

@ -25,6 +25,7 @@
*/
#include "PolySetCache.h"
#include "ModuleCache.h"
#include "MainWindow.h"
#include "openscad.h" // examplesdir
#include "parsersettings.h"
@ -695,6 +696,8 @@ void MainWindow::compile(bool procevents)
goto fail;
}
this->root_module->handleDependencies();
// Evaluate CSG tree
PRINT("Compiling design (CSG Tree generation)...");
if (procevents)
@ -1449,7 +1452,7 @@ void MainWindow::actionFlushCaches()
#endif
dxf_dim_cache.clear();
dxf_cross_cache.clear();
Module::clear_library_cache();
ModuleCache::instance()->clear();
}
void MainWindow::viewModeActionsUncheck()
@ -1797,7 +1800,7 @@ void MainWindow::consoleOutput(const std::string &msg, void *userdata)
void MainWindow::setCurrentOutput()
{
set_output_handler(&MainWindow::consoleOutput, this);
// set_output_handler(&MainWindow::consoleOutput, this);
}
void MainWindow::clearCurrentOutput()

View File

@ -25,11 +25,13 @@
*/
#include "module.h"
#include "ModuleCache.h"
#include "node.h"
#include "context.h"
#include "expression.h"
#include "function.h"
#include "printutils.h"
#include <boost/foreach.hpp>
#include <sstream>
@ -201,7 +203,18 @@ std::string Module::dump(const std::string &indent, const std::string &name) con
return dump.str();
}
void Module::clear_library_cache()
void Module::handleDependencies()
{
Module::libs_cache.clear();
PRINTB_NOCACHE("Module::handleDependencies(): %p (%d libs %p)", this % this->usedlibs.size() % &this->usedlibs);
// Iterating manually since we want to modify the container while iterating
Module::ModuleContainer::iterator iter = this->usedlibs.begin();
while (iter != this->usedlibs.end()) {
Module::ModuleContainer::iterator curr = iter++;
curr->second = ModuleCache::instance()->evaluate(curr->first);
PRINTB_NOCACHE(" %s: %p", curr->first % curr->second);
if (!curr->second) {
PRINTB_NOCACHE("WARNING: Failed to compile library '%s'.", curr->first);
this->usedlibs.erase(curr);
}
}
}

View File

@ -65,11 +65,9 @@ public:
void addChild(ModuleInstantiation *ch) { this->children.push_back(ch); }
static Module *compile_library(const std::string &filename);
static void clear_library_cache();
typedef boost::unordered_map<std::string, class Module*> ModuleContainer;
ModuleContainer usedlibs;
void handleDependencies();
std::vector<std::string> assignments_var;
std::vector<Expression*> assignments_expr;
@ -87,11 +85,6 @@ public:
protected:
private:
struct libs_cache_ent {
Module *mod;
std::string cache_id, msg;
};
static boost::unordered_map<std::string, libs_cache_ent> libs_cache;
};
#endif

View File

@ -255,7 +255,7 @@ int main(int argc, char **argv)
Context root_ctx;
register_builtin(root_ctx);
AbstractModule *root_module;
Module *root_module;
ModuleInstantiation root_inst;
AbstractNode *root_node;
@ -278,6 +278,7 @@ int main(int argc, char **argv)
std::string fpath = boosty::stringy(abspath.parent_path());
root_module = parse(text.str().c_str(), fpath.c_str(), false);
if (!root_module) exit(1);
root_module->handleDependencies();
}
fs::path fpath = boosty::absolute( fs::path(filename) );

View File

@ -27,7 +27,7 @@
#ifndef OPENSCAD_H
#define OPENSCAD_H
extern class AbstractModule *parse(const char *text, const char *path, int debug);
extern class Module *parse(const char *text, const char *path, int debug);
extern int get_fragments_from_r(double r, double fn, double fs, double fa);
#include <string>

View File

@ -43,7 +43,7 @@
#include <boost/foreach.hpp>
#include <boost/filesystem.hpp>
using namespace boost::filesystem;
namespace fs = boost::filesystem;
#include "boosty.h"
int parser_error_pos = -1;
@ -56,7 +56,7 @@ int lexerlex_destroy(void);
int lexerlex(void);
std::vector<Module*> module_stack;
Module *module;
Module *currmodule;
class ArgContainer {
public:
@ -133,7 +133,7 @@ public:
input:
/* empty */ |
TOK_USE { module->usedlibs[$1] = NULL; } input |
TOK_USE { currmodule->usedlibs[$1] = NULL; } input |
statement input ;
inner_input:
@ -145,37 +145,38 @@ statement:
'{' inner_input '}' |
module_instantiation {
if ($1) {
module->addChild($1);
currmodule->addChild($1);
} else {
delete $1;
}
} |
TOK_ID '=' expr ';' {
bool add_new_assignment = true;
for (size_t i = 0; i < module->assignments_var.size(); i++) {
if (module->assignments_var[i] != $1)
for (size_t i = 0; i < currmodule->assignments_var.size(); i++) {
if (currmodule->assignments_var[i] != $1)
continue;
delete module->assignments_expr[i];
module->assignments_expr[i] = $3;
delete currmodule->assignments_expr[i];
currmodule->assignments_expr[i] = $3;
add_new_assignment = false;
}
if (add_new_assignment) {
module->assignments_var.push_back($1);
module->assignments_expr.push_back($3);
currmodule->assignments_var.push_back($1);
currmodule->assignments_expr.push_back($3);
free($1);
}
} |
TOK_MODULE TOK_ID '(' arguments_decl optional_commas ')' {
Module *p = module;
module_stack.push_back(module);
module = new Module();
p->modules[$2] = module;
module->argnames = $4->argnames;
module->argexpr = $4->argexpr;
Module *p = currmodule;
module_stack.push_back(currmodule);
currmodule = new Module();
PRINTB_NOCACHE("New module: %s %p", $2 % currmodule);
p->modules[$2] = currmodule;
currmodule->argnames = $4->argnames;
currmodule->argexpr = $4->argexpr;
free($2);
delete $4;
} statement {
module = module_stack.back();
currmodule = module_stack.back();
module_stack.pop_back();
} |
TOK_FUNCTION TOK_ID '(' arguments_decl optional_commas ')' '=' expr {
@ -183,7 +184,7 @@ statement:
func->argnames = $4->argnames;
func->argexpr = $4->argexpr;
func->expr = $8;
module->functions[$2] = func;
currmodule->functions[$2] = func;
free($2);
delete $4;
} ';' ;
@ -560,101 +561,34 @@ void yyerror (char const *s)
{
// FIXME: We leak memory on parser errors...
PRINTB("Parser error in line %d: %s\n", lexerget_lineno() % s);
module = NULL;
currmodule = NULL;
}
extern void lexerdestroy();
extern FILE *lexerin;
extern const char *parser_input_buffer;
const char *parser_input_buffer;
const char *parser_source_path;
std::string parser_source_path;
AbstractModule *parse(const char *text, const char *path, int debug)
Module *parse(const char *text, const char *path, int debug)
{
PRINT_NOCACHE("New parser");
lexerin = NULL;
parser_error_pos = -1;
parser_input_buffer = text;
parser_source_path = path;
parser_source_path = std::string(path);
module_stack.clear();
module = new Module();
Module *rootmodule = currmodule = new Module();
PRINTB_NOCACHE("New module: %s %p", "root" % rootmodule);
parserdebug = debug;
parserparse();
lexerdestroy();
lexerlex_destroy();
if (!module)
return NULL;
// Iterating manually since we want to modify the container while iterating
Module::ModuleContainer::iterator iter = module->usedlibs.begin();
while (iter != module->usedlibs.end()) {
Module::ModuleContainer::iterator curr = iter++;
curr->second = Module::compile_library(curr->first);
if (!curr->second) {
PRINTB("WARNING: Failed to compile library '%s'.", curr->first);
module->usedlibs.erase(curr);
}
}
if (!rootmodule) return NULL;
parser_error_pos = -1;
return module;
return rootmodule;
}
boost::unordered_map<std::string, Module::libs_cache_ent> Module::libs_cache;
Module *Module::compile_library(const std::string &filename)
{
struct stat st;
memset(&st, 0, sizeof(struct stat));
stat(filename.c_str(), &st);
std::stringstream idstream;
idstream << std::hex << st.st_mtime << "." << st.st_size;
std::string cache_id = idstream.str();
if (libs_cache.find(filename) != libs_cache.end() && libs_cache[filename].cache_id == cache_id) {
PRINTB("%s", libs_cache[filename].msg);
return &(*libs_cache[filename].mod);
}
FILE *fp = fopen(filename.c_str(), "rt");
if (!fp) {
fprintf(stderr, "WARNING: Can't open library file '%s'\n", filename.c_str());
return NULL;
}
std::stringstream text;
char buffer[513];
int ret;
while ((ret = fread(buffer, 1, 512, fp)) > 0) {
buffer[ret] = 0;
text << buffer;
}
fclose(fp);
print_messages_push();
PRINTB("Compiling library '%s'.", filename);
libs_cache_ent e = { NULL, cache_id, std::string("WARNING: Library `") + filename + "' tries to recursively use itself!" };
if (libs_cache.find(filename) != libs_cache.end())
delete libs_cache[filename].mod;
libs_cache[filename] = e;
Module *backup_mod = module;
std::string pathname = boosty::stringy( fs::path(filename).parent_path() );
Module *lib_mod = dynamic_cast<Module*>(parse(text.str().c_str(), pathname.c_str(), 0));
module = backup_mod;
if (lib_mod) {
libs_cache[filename].mod = lib_mod;
libs_cache[filename].msg = print_messages_stack.back();
} else {
libs_cache.erase(filename);
}
print_messages_pop();
return lib_mod;
}

View File

@ -309,6 +309,7 @@ set(CORE_SOURCES
../src/expr.cc
../src/func.cc
../src/module.cc
../src/ModuleCache.cc
../src/node.cc
../src/context.cc
../src/csgterm.cc
@ -402,6 +403,12 @@ target_link_libraries(echotest tests-nocgal tests-core ${QT_LIBRARIES} ${OPENGL_
add_executable(dumptest dumptest.cc)
target_link_libraries(dumptest tests-common tests-nocgal ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${Boost_LIBRARIES})
#
# modulecachetest
#
add_executable(modulecachetest modulecachetest.cc)
target_link_libraries(modulecachetest tests-common tests-nocgal ${QT_LIBRARIES} ${OPENGL_LIBRARY} ${Boost_LIBRARIES})
#
# csgtexttest
#

121
tests/modulecachetest.cc Normal file
View File

@ -0,0 +1,121 @@
/*
* OpenSCAD (www.openscad.org)
* Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
* Marius Kintel <marius@kintel.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* As a special exception, you have permission to link this program
* with the CGAL library and distribute executables, as long as you
* follow the requirements of the GNU GPL in regard to all of the
* software in the executable aside from CGAL.
*
* This program 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 for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include "tests-common.h"
#include "openscad.h"
#include "parsersettings.h"
#include "node.h"
#include "module.h"
#include "context.h"
#include "value.h"
#include "export.h"
#include "builtin.h"
#include "Tree.h"
#include <QCoreApplication>
#ifndef _MSC_VER
#include <getopt.h>
#endif
#include <assert.h>
#include <iostream>
#include <sstream>
#include <fstream>
#include <boost/filesystem.hpp>
namespace fs = boost::filesystem;
#include "boosty.h"
std::string commandline_commands;
std::string currentdir;
QString examplesdir;
using std::string;
int main(int argc, char **argv)
{
#ifdef _MSC_VER
_set_output_format(_TWO_DIGIT_EXPONENT);
#endif
if (argc != 3) {
fprintf(stderr, "Usage: %s <file.scad> <output.txt>\n", argv[0]);
exit(1);
}
const char *filename = argv[1];
const char *outfilename = argv[2];
int rc = 0;
Builtins::instance()->initialize();
QCoreApplication app(argc, argv);
fs::path original_path = fs::current_path();
currentdir = boosty::stringy( fs::current_path() );
parser_init(QCoreApplication::instance()->applicationDirPath().toStdString());
set_librarydir(boosty::stringy(fs::path(QCoreApplication::instance()->applicationDirPath().toStdString()) / "../libraries"));
Context root_ctx;
register_builtin(root_ctx);
AbstractModule *root_module;
ModuleInstantiation root_inst;
AbstractNode *root_node;
root_module = parsefile(filename);
if (!root_module) {
fprintf(stderr, "Error: Unable to parse input file\n");
exit(1);
}
fs::current_path(fs::path(filename).parent_path());
AbstractNode::resetIndexCounter();
root_node = root_module->evaluate(&root_ctx, &root_inst);
delete root_node;
delete root_module;
fs::current_path(original_path);
fprintf(stderr, "Second parse\n");
root_module = parsefile(filename);
if (!root_module) {
fprintf(stderr, "Error: Unable to parse second time\n");
exit(1);
}
AbstractNode::resetIndexCounter();
root_node = root_module->evaluate(&root_ctx, &root_inst);
delete root_node;
delete root_module;
Builtins::instance(true);
return rc;
}

View File

@ -7,9 +7,9 @@
#include <QFileInfo>
#include <sstream>
AbstractModule *parsefile(const char *filename)
Module *parsefile(const char *filename)
{
AbstractModule *root_module = NULL;
Module *root_module = NULL;
QFileInfo fileInfo(filename);
handle_dep(filename);
@ -27,6 +27,9 @@ AbstractModule *parsefile(const char *filename)
fclose(fp);
text << "\n" << commandline_commands;
root_module = parse(text.str().c_str(), fileInfo.absolutePath().toLocal8Bit(), false);
if (root_module) {
root_module->handleDependencies();
}
}
return root_module;
}

View File

@ -1,6 +1,6 @@
#ifndef TESTS_COMMON_H_
#define TESTS_COMMON_H_
class AbstractModule *parsefile(const char *filename);
class Module *parsefile(const char *filename);
#endif