Commit a5258c49 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Optimize LivenessAnalysis

We were caching certain liveness results (only crawled each BB once
looking for name use/defs), but we were still traversing the entire
CFG for every liveness query.
Now, each query does a single full CFG scan, and computes the results
for all BBs and stores them.

Reduces the time to 'import re' from about 13s to under 2s.
parent 141ac41f
...@@ -33,27 +33,31 @@ namespace pyston { ...@@ -33,27 +33,31 @@ namespace pyston {
class LivenessBBVisitor : public NoopASTVisitor { class LivenessBBVisitor : public NoopASTVisitor {
public: public:
typedef llvm::SmallSet<std::string, 4> StrSet; enum Status {
NONE,
USED,
KILLED,
};
private: private:
StrSet _loads; std::unordered_map<int, Status> statuses;
StrSet _stores; LivenessAnalysis* analysis;
void _doLoad(const std::string& name) { void _doLoad(const std::string& name) {
if (_stores.count(name)) Status& status = statuses[analysis->getStringIndex(name)];
return; if (status == NONE)
_loads.insert(name); status = USED;
} }
void _doStore(const std::string& name) { void _doStore(const std::string& name) {
if (_loads.count(name)) Status& status = statuses[analysis->getStringIndex(name)];
return; if (status == NONE)
_stores.insert(name); status = KILLED;
} }
public: public:
LivenessBBVisitor() {} LivenessBBVisitor(LivenessAnalysis* analysis) : analysis(analysis) {}
const StrSet& loads() { return _loads; }
const StrSet& stores() { return _stores; } Status nameStatus(int idx) { return statuses[idx]; }
bool visit_classdef(AST_ClassDef* node) { bool visit_classdef(AST_ClassDef* node) {
_doStore(node->name); _doStore(node->name);
...@@ -94,6 +98,24 @@ public: ...@@ -94,6 +98,24 @@ public:
} }
}; };
int LivenessAnalysis::getStringIndex(const std::string& s) {
int& r = string_index_map[s];
if (r == 0) {
r = string_index_map.size(); // includes the '0' entry we just put in there
}
return r;
}
LivenessAnalysis::LivenessAnalysis(CFG* cfg) : cfg(cfg) {
for (CFGBlock* b : cfg->blocks) {
auto visitor = new LivenessBBVisitor(this); // livenessCache unique_ptr will delete it.
for (AST_stmt* stmt : b->body) {
stmt->accept(visitor);
}
liveness_cache.insert(std::make_pair(b, std::unique_ptr<LivenessBBVisitor>(visitor)));
}
}
bool LivenessAnalysis::isLiveAtEnd(const std::string& name, CFGBlock* block) { bool LivenessAnalysis::isLiveAtEnd(const std::string& name, CFGBlock* block) {
if (name[0] != '#') if (name[0] != '#')
return true; return true;
...@@ -101,50 +123,43 @@ bool LivenessAnalysis::isLiveAtEnd(const std::string& name, CFGBlock* block) { ...@@ -101,50 +123,43 @@ bool LivenessAnalysis::isLiveAtEnd(const std::string& name, CFGBlock* block) {
if (block->successors.size() == 0) if (block->successors.size() == 0)
return false; return false;
// Very inefficient liveness analysis: int idx = getStringIndex(name);
// for each query, trace forward through all possible control flow paths. if (!result_cache.count(idx)) {
// if we hit a store to the name, stop tracing that path std::unordered_map<CFGBlock*, bool>& map = result_cache[idx];
// if we hit a load to the name, return true.
// to improve performance we cache the liveness result of every visited BB. // Approach:
llvm::SmallPtrSet<CFGBlock*, 1> visited; // - Find all uses (blocks where the status is USED)
// - Trace backwards, marking all blocks as live-at-end
// - If we hit a block that is KILLED, stop
for (CFGBlock* b : cfg->blocks) {
auto status = liveness_cache[b]->nameStatus(idx);
if (status != LivenessBBVisitor::USED)
continue;
std::deque<CFGBlock*> q; std::deque<CFGBlock*> q;
for (CFGBlock* successor : block->successors) { for (CFGBlock* pred : b->predecessors) {
q.push_back(successor); q.push_back(pred);
} }
while (q.size()) { while (q.size()) {
CFGBlock* thisblock = q.front(); CFGBlock* thisblock = q.front();
q.pop_front(); q.pop_front();
if (visited.count(thisblock))
if (map[thisblock])
continue; continue;
LivenessBBVisitor* visitor = nullptr; map[thisblock] = true;
LivenessCacheMap::iterator it = livenessCache.find(thisblock); if (liveness_cache[thisblock]->nameStatus(idx) != LivenessBBVisitor::KILLED) {
if (it != livenessCache.end()) { for (CFGBlock* pred : thisblock->predecessors) {
visitor = it->second.get(); q.push_back(pred);
} else {
visitor = new LivenessBBVisitor; // livenessCache unique_ptr will delete it.
for (AST_stmt* stmt : thisblock->body) {
stmt->accept(visitor);
} }
livenessCache.insert(std::make_pair(thisblock, std::unique_ptr<LivenessBBVisitor>(visitor)));
} }
visited.insert(thisblock);
if (visitor->loads().count(name)) {
assert(!visitor->stores().count(name));
return true;
}
if (!visitor->stores().count(name)) {
assert(!visitor->loads().count(name));
for (CFGBlock* successor : thisblock->successors) {
q.push_back(successor);
} }
} }
} }
return false; return result_cache[idx][block];
} }
class DefinednessBBAnalyzer : public BBAnalyzer<DefinednessAnalysis::DefinitionLevel> { class DefinednessBBAnalyzer : public BBAnalyzer<DefinednessAnalysis::DefinitionLevel> {
...@@ -375,8 +390,8 @@ bool PhiAnalysis::isPotentiallyUndefinedAfter(const std::string& name, CFGBlock* ...@@ -375,8 +390,8 @@ bool PhiAnalysis::isPotentiallyUndefinedAfter(const std::string& name, CFGBlock*
return false; return false;
} }
LivenessAnalysis* computeLivenessInfo(CFG*) { LivenessAnalysis* computeLivenessInfo(CFG* cfg) {
return new LivenessAnalysis(); return new LivenessAnalysis(cfg);
} }
PhiAnalysis* computeRequiredPhis(const SourceInfo::ArgNames& args, CFG* cfg, LivenessAnalysis* liveness, PhiAnalysis* computeRequiredPhis(const SourceInfo::ArgNames& args, CFG* cfg, LivenessAnalysis* liveness,
......
...@@ -31,13 +31,31 @@ class ScopeInfo; ...@@ -31,13 +31,31 @@ class ScopeInfo;
class LivenessBBVisitor; class LivenessBBVisitor;
class LivenessAnalysis { class LivenessAnalysis {
public:
bool isLiveAtEnd(const std::string& name, CFGBlock* block);
private: private:
CFG* cfg;
friend class LivenessBBVisitor;
typedef std::unordered_map<CFGBlock*, std::unique_ptr<LivenessBBVisitor> > LivenessCacheMap; typedef std::unordered_map<CFGBlock*, std::unique_ptr<LivenessBBVisitor> > LivenessCacheMap;
LivenessCacheMap livenessCache; LivenessCacheMap liveness_cache;
std::unordered_map<int, std::unordered_map<CFGBlock*, bool> > result_cache;
// Map strings to unique indices. For a given CFG, the set of strings should be fairly small
// (a constant fraction max of the CFG itself), so just store all of them. The theory is that
// for any particular name, we will do many lookups on it in different hash tables, and by
// converting to a string only once, the extra hashtable lookup will be profitable since it
// can make all the rest faster (int hashes vs string hashes).
//
// Haven't validated this, though.
std::unordered_map<std::string, int> string_index_map;
int getStringIndex(const std::string& s);
public:
LivenessAnalysis(CFG* cfg);
bool isLiveAtEnd(const std::string& name, CFGBlock* block);
}; };
class DefinednessAnalysis { class DefinednessAnalysis {
public: public:
enum DefinitionLevel { enum DefinitionLevel {
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
#include "core/ast.h" #include "core/ast.h"
#include "core/options.h" #include "core/options.h"
#include "core/types.h" #include "core/types.h"
#include "runtime/objmodel.h"
#include "runtime/types.h" #include "runtime/types.h"
//#undef VERBOSITY //#undef VERBOSITY
...@@ -1266,8 +1267,7 @@ public: ...@@ -1266,8 +1267,7 @@ public:
virtual bool visit_return(AST_Return* node) { virtual bool visit_return(AST_Return* node) {
if (root_type != AST_TYPE::FunctionDef && root_type != AST_TYPE::Lambda) { if (root_type != AST_TYPE::FunctionDef && root_type != AST_TYPE::Lambda) {
fprintf(stderr, "SyntaxError: 'return' outside function\n"); raiseExcHelper(SyntaxError, "'return' outside function");
exit(1);
} }
AST_expr* value = remapExpr(node->value); AST_expr* value = remapExpr(node->value);
...@@ -1337,8 +1337,7 @@ public: ...@@ -1337,8 +1337,7 @@ public:
return true; return true;
if (loops.size() == 0) { if (loops.size() == 0) {
fprintf(stderr, "SyntaxError: 'break' outside loop\n"); raiseExcHelper(SyntaxError, "'break' outside loop");
exit(1);
} }
AST_Jump* j = makeJump(); AST_Jump* j = makeJump();
...@@ -1357,8 +1356,7 @@ public: ...@@ -1357,8 +1356,7 @@ public:
if (loops.size() == 0) { if (loops.size() == 0) {
// Note: error message is different than the 'break' case // Note: error message is different than the 'break' case
fprintf(stderr, "SyntaxError: 'continue' not properly in loop\n"); raiseExcHelper(SyntaxError, "'continue' not properly in loop");
exit(1);
} }
AST_Jump* j = makeJump(); AST_Jump* j = makeJump();
......
...@@ -165,6 +165,7 @@ int main(int argc, char** argv) { ...@@ -165,6 +165,7 @@ int main(int argc, char** argv) {
std::string msg = formatException(b); std::string msg = formatException(b);
printLastTraceback(); printLastTraceback();
fprintf(stderr, "%s\n", msg.c_str()); fprintf(stderr, "%s\n", msg.c_str());
exit(1); exit(1);
} }
} }
......
...@@ -382,7 +382,7 @@ BoxedModule* builtins_module; ...@@ -382,7 +382,7 @@ BoxedModule* builtins_module;
// TODO looks like CPython and pypy put this into an "exceptions" module: // TODO looks like CPython and pypy put this into an "exceptions" module:
BoxedClass* Exception, *AssertionError, *AttributeError, *GeneratorExit, *TypeError, *NameError, *KeyError, *IndexError, BoxedClass* Exception, *AssertionError, *AttributeError, *GeneratorExit, *TypeError, *NameError, *KeyError, *IndexError,
*IOError, *OSError, *ZeroDivisionError, *ValueError, *UnboundLocalError, *RuntimeError, *ImportError, *IOError, *OSError, *ZeroDivisionError, *ValueError, *UnboundLocalError, *RuntimeError, *ImportError,
*StopIteration, *Warning; *StopIteration, *Warning, *SyntaxError;
const ObjectFlavor exception_flavor(&boxGCHandler, NULL); const ObjectFlavor exception_flavor(&boxGCHandler, NULL);
Box* exceptionNew1(BoxedClass* cls) { Box* exceptionNew1(BoxedClass* cls) {
...@@ -472,6 +472,7 @@ void setupBuiltins() { ...@@ -472,6 +472,7 @@ void setupBuiltins() {
ImportError = makeBuiltinException(Exception, "ImportError"); ImportError = makeBuiltinException(Exception, "ImportError");
StopIteration = makeBuiltinException(Exception, "StopIteration"); StopIteration = makeBuiltinException(Exception, "StopIteration");
Warning = makeBuiltinException(Exception, "Warning"); Warning = makeBuiltinException(Exception, "Warning");
SyntaxError = makeBuiltinException(Exception, "SyntaxError");
/*ImportWarning =*/makeBuiltinException(Warning, "ImportWarning"); /*ImportWarning =*/makeBuiltinException(Warning, "ImportWarning");
/*PendingDeprecationWarning =*/makeBuiltinException(Warning, "PendingDeprecationWarning"); /*PendingDeprecationWarning =*/makeBuiltinException(Warning, "PendingDeprecationWarning");
/*DeprecationWarning =*/makeBuiltinException(Warning, "DeprecationWarning"); /*DeprecationWarning =*/makeBuiltinException(Warning, "DeprecationWarning");
......
...@@ -356,7 +356,7 @@ Box* exceptionNew2(BoxedClass* cls, Box* message); ...@@ -356,7 +356,7 @@ Box* exceptionNew2(BoxedClass* cls, Box* message);
extern BoxedClass* Exception, *AssertionError, *AttributeError, *TypeError, *NameError, *KeyError, *IndexError, extern BoxedClass* Exception, *AssertionError, *AttributeError, *TypeError, *NameError, *KeyError, *IndexError,
*IOError, *OSError, *ZeroDivisionError, *ValueError, *UnboundLocalError, *RuntimeError, *ImportError, *IOError, *OSError, *ZeroDivisionError, *ValueError, *UnboundLocalError, *RuntimeError, *ImportError,
*StopIteration, *GeneratorExit; *StopIteration, *GeneratorExit, *SyntaxError;
// cls should be obj->cls. // cls should be obj->cls.
// Added as parameter because it should typically be available // Added as parameter because it should typically be available
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment