Commit 81f00afe authored by Kevin Modzelewski's avatar Kevin Modzelewski

Fix analysis issue that virtualenv was running into

The issue was that the types analysis was osr-aware, where
we would only type-analyze the sections of the function
accessible from the osr entry point.  The phi and definedness
analyses were not osr-aware, so they would think that phis
in certain places where the type analysis knew that they
were undefined.

So this change makes definedness and phi analysis osr-aware,
where they only analyze the appropriate section of the function.
I think this means that we will do these analyses more, since we
have to rerun them for each entry point, so hopefully analysis time
doesn't increase too much.
parent a1100ede
...@@ -45,20 +45,23 @@ public: ...@@ -45,20 +45,23 @@ public:
}; };
template <typename T> template <typename T>
typename BBAnalyzer<T>::AllMap computeFixedPoint(CFG* cfg, const BBAnalyzer<T>& analyzer, bool reverse) { void computeFixedPoint(typename BBAnalyzer<T>::Map&& initial_map, CFGBlock* initial_block,
const BBAnalyzer<T>& analyzer, bool reverse, typename BBAnalyzer<T>::AllMap& starting_states,
typename BBAnalyzer<T>::AllMap& ending_states) {
assert(!reverse); assert(!reverse);
typedef typename BBAnalyzer<T>::Map Map; typedef typename BBAnalyzer<T>::Map Map;
typedef typename BBAnalyzer<T>::AllMap AllMap; typedef typename BBAnalyzer<T>::AllMap AllMap;
AllMap starting_states;
AllMap ending_states; assert(!starting_states.size());
assert(!ending_states.size());
llvm::SmallPtrSet<CFGBlock*, 32> in_queue; llvm::SmallPtrSet<CFGBlock*, 32> in_queue;
std::priority_queue<CFGBlock*, llvm::SmallVector<CFGBlock*, 32>, CFGBlockMinIndex> q; std::priority_queue<CFGBlock*, llvm::SmallVector<CFGBlock*, 32>, CFGBlockMinIndex> q;
starting_states.insert(make_pair(cfg->getStartingBlock(), Map())); starting_states.insert(make_pair(initial_block, std::move(initial_map)));
q.push(cfg->getStartingBlock()); q.push(initial_block);
in_queue.insert(cfg->getStartingBlock()); in_queue.insert(initial_block);
int num_evaluations = 0; int num_evaluations = 0;
while (!q.empty()) { while (!q.empty()) {
...@@ -124,12 +127,9 @@ typename BBAnalyzer<T>::AllMap computeFixedPoint(CFG* cfg, const BBAnalyzer<T>& ...@@ -124,12 +127,9 @@ typename BBAnalyzer<T>::AllMap computeFixedPoint(CFG* cfg, const BBAnalyzer<T>&
} }
if (VERBOSITY("analysis")) { if (VERBOSITY("analysis")) {
printf("%ld BBs, %d evaluations = %.1f evaluations/block\n", cfg->blocks.size(), num_evaluations, printf("%d BBs, %d evaluations = %.1f evaluations/block\n", starting_states.size(), num_evaluations,
1.0 * num_evaluations / cfg->blocks.size()); 1.0 * num_evaluations / starting_states.size());
} }
return ending_states;
} }
} }
......
...@@ -21,9 +21,11 @@ ...@@ -21,9 +21,11 @@
#include "llvm/ADT/SetVector.h" #include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallSet.h" #include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringSet.h"
#include "analysis/fpc.h" #include "analysis/fpc.h"
#include "analysis/scoping_analysis.h" #include "analysis/scoping_analysis.h"
#include "codegen/osrentry.h"
#include "core/ast.h" #include "core/ast.h"
#include "core/cfg.h" #include "core/cfg.h"
#include "core/common.h" #include "core/common.h"
...@@ -221,13 +223,10 @@ class DefinednessBBAnalyzer : public BBAnalyzer<DefinednessAnalysis::DefinitionL ...@@ -221,13 +223,10 @@ class DefinednessBBAnalyzer : public BBAnalyzer<DefinednessAnalysis::DefinitionL
private: private:
typedef DefinednessAnalysis::DefinitionLevel DefinitionLevel; typedef DefinednessAnalysis::DefinitionLevel DefinitionLevel;
CFG* cfg;
ScopeInfo* scope_info; ScopeInfo* scope_info;
const ParamNames& arg_names;
public: public:
DefinednessBBAnalyzer(CFG* cfg, ScopeInfo* scope_info, const ParamNames& arg_names) DefinednessBBAnalyzer(ScopeInfo* scope_info) : scope_info(scope_info) {}
: cfg(cfg), scope_info(scope_info), arg_names(arg_names) {}
virtual DefinitionLevel merge(DefinitionLevel from, DefinitionLevel into) const { virtual DefinitionLevel merge(DefinitionLevel from, DefinitionLevel into) const {
assert(from != DefinednessAnalysis::Undefined); assert(from != DefinednessAnalysis::Undefined);
...@@ -348,15 +347,6 @@ public: ...@@ -348,15 +347,6 @@ public:
void DefinednessBBAnalyzer::processBB(Map& starting, CFGBlock* block) const { void DefinednessBBAnalyzer::processBB(Map& starting, CFGBlock* block) const {
DefinednessVisitor visitor(starting); DefinednessVisitor visitor(starting);
if (block == cfg->getStartingBlock()) {
for (auto e : arg_names.args)
visitor._doSet(scope_info->internString(e));
if (arg_names.vararg.size())
visitor._doSet(scope_info->internString(arg_names.vararg));
if (arg_names.kwarg.size())
visitor._doSet(scope_info->internString(arg_names.kwarg));
}
for (int i = 0; i < block->body.size(); i++) { for (int i = 0; i < block->body.size(); i++) {
block->body[i]->accept(&visitor); block->body[i]->accept(&visitor);
} }
...@@ -369,20 +359,23 @@ void DefinednessBBAnalyzer::processBB(Map& starting, CFGBlock* block) const { ...@@ -369,20 +359,23 @@ void DefinednessBBAnalyzer::processBB(Map& starting, CFGBlock* block) const {
} }
} }
DefinednessAnalysis::DefinednessAnalysis(const ParamNames& arg_names, CFG* cfg, ScopeInfo* scope_info) void DefinednessAnalysis::run(llvm::DenseMap<InternedString, DefinednessAnalysis::DefinitionLevel>&& initial_map,
: scope_info(scope_info) { CFGBlock* initial_block, ScopeInfo* scope_info) {
Timer _t("DefinednessAnalysis()", 10); Timer _t("DefinednessAnalysis()", 10);
results = computeFixedPoint(cfg, DefinednessBBAnalyzer(cfg, scope_info, arg_names), false); // Don't run this twice:
assert(!defined_at_end.size());
for (const auto& p : results) { computeFixedPoint(std::move(initial_map), initial_block, DefinednessBBAnalyzer(scope_info), false,
RequiredSet& required = defined_at_end[p.first]; defined_at_beginning, defined_at_end);
for (const auto& p : defined_at_end) {
RequiredSet& required = defined_at_end_sets[p.first];
for (const auto& p2 : p.second) { for (const auto& p2 : p.second) {
ScopeInfo::VarScopeType vst = scope_info->getScopeTypeOfName(p2.first); ScopeInfo::VarScopeType vst = scope_info->getScopeTypeOfName(p2.first);
if (vst == ScopeInfo::VarScopeType::GLOBAL || vst == ScopeInfo::VarScopeType::NAME) if (vst == ScopeInfo::VarScopeType::GLOBAL || vst == ScopeInfo::VarScopeType::NAME)
continue; continue;
// printf("%d %s %d\n", p.first->idx, p2.first.c_str(), p2.second);
required.insert(p2.first); required.insert(p2.first);
} }
} }
...@@ -392,24 +385,45 @@ DefinednessAnalysis::DefinednessAnalysis(const ParamNames& arg_names, CFG* cfg, ...@@ -392,24 +385,45 @@ DefinednessAnalysis::DefinednessAnalysis(const ParamNames& arg_names, CFG* cfg,
} }
DefinednessAnalysis::DefinitionLevel DefinednessAnalysis::isDefinedAtEnd(InternedString name, CFGBlock* block) { DefinednessAnalysis::DefinitionLevel DefinednessAnalysis::isDefinedAtEnd(InternedString name, CFGBlock* block) {
auto& map = results[block]; assert(defined_at_end.count(block));
auto& map = defined_at_end[block];
if (map.count(name) == 0) if (map.count(name) == 0)
return Undefined; return Undefined;
return map[name]; return map[name];
} }
const DefinednessAnalysis::RequiredSet& DefinednessAnalysis::getDefinedNamesAtEnd(CFGBlock* block) { const DefinednessAnalysis::RequiredSet& DefinednessAnalysis::getDefinedNamesAtEnd(CFGBlock* block) {
return defined_at_end[block]; assert(defined_at_end_sets.count(block));
return defined_at_end_sets[block];
} }
PhiAnalysis::PhiAnalysis(const ParamNames& arg_names, CFG* cfg, LivenessAnalysis* liveness, ScopeInfo* scope_info) PhiAnalysis::PhiAnalysis(llvm::DenseMap<InternedString, DefinednessAnalysis::DefinitionLevel>&& initial_map,
: definedness(arg_names, cfg, scope_info), liveness(liveness) { CFGBlock* initial_block, bool initials_need_phis, LivenessAnalysis* liveness,
ScopeInfo* scope_info)
: definedness(), liveness(liveness) {
Timer _t("PhiAnalysis()", 10); Timer _t("PhiAnalysis()", 10);
for (CFGBlock* block : cfg->blocks) { // I think this should always be the case -- if we're going to generate phis for the initial block,
// then we should include the initial arguments as an extra entry point.
assert(initials_need_phis == (initial_block->predecessors.size() > 0));
definedness.run(std::move(initial_map), initial_block, scope_info);
for (const auto& p : definedness.defined_at_end) {
CFGBlock* block = p.first;
RequiredSet& required = required_phis[block]; RequiredSet& required = required_phis[block];
if (block->predecessors.size() > 1) {
int npred = 0;
for (CFGBlock* pred : block->predecessors) {
if (definedness.defined_at_end.count(pred))
npred++;
}
if (npred > 1 || (initials_need_phis && block == initial_block)) {
for (CFGBlock* pred : block->predecessors) { for (CFGBlock* pred : block->predecessors) {
if (!definedness.defined_at_end.count(pred))
continue;
const RequiredSet& defined = definedness.getDefinedNamesAtEnd(pred); const RequiredSet& defined = definedness.getDefinedNamesAtEnd(pred);
for (const auto& s : defined) { for (const auto& s : defined) {
if (required.count(s) == 0 && liveness->isLiveAtEnd(s, pred)) { if (required.count(s) == 0 && liveness->isLiveAtEnd(s, pred)) {
...@@ -430,15 +444,18 @@ const PhiAnalysis::RequiredSet& PhiAnalysis::getAllRequiredAfter(CFGBlock* block ...@@ -430,15 +444,18 @@ const PhiAnalysis::RequiredSet& PhiAnalysis::getAllRequiredAfter(CFGBlock* block
static RequiredSet empty; static RequiredSet empty;
if (block->successors.size() == 0) if (block->successors.size() == 0)
return empty; return empty;
assert(required_phis.count(block->successors[0]));
return required_phis[block->successors[0]]; return required_phis[block->successors[0]];
} }
const PhiAnalysis::RequiredSet& PhiAnalysis::getAllRequiredFor(CFGBlock* block) { const PhiAnalysis::RequiredSet& PhiAnalysis::getAllRequiredFor(CFGBlock* block) {
assert(required_phis.count(block));
return required_phis[block]; return required_phis[block];
} }
bool PhiAnalysis::isRequired(InternedString name, CFGBlock* block) { bool PhiAnalysis::isRequired(InternedString name, CFGBlock* block) {
assert(!startswith(name.str(), "!")); assert(!startswith(name.str(), "!"));
assert(required_phis.count(block));
return required_phis[block].count(name) != 0; return required_phis[block].count(name) != 0;
} }
...@@ -456,21 +473,18 @@ bool PhiAnalysis::isRequiredAfter(InternedString name, CFGBlock* block) { ...@@ -456,21 +473,18 @@ bool PhiAnalysis::isRequiredAfter(InternedString name, CFGBlock* block) {
bool PhiAnalysis::isPotentiallyUndefinedAfter(InternedString name, CFGBlock* block) { bool PhiAnalysis::isPotentiallyUndefinedAfter(InternedString name, CFGBlock* block) {
assert(!startswith(name.str(), "!")); assert(!startswith(name.str(), "!"));
if (block->successors.size() != 1) for (auto b : block->successors) {
return false; if (isPotentiallyUndefinedAt(name, b))
return true;
return isPotentiallyUndefinedAt(name, block->successors[0]); }
return false;
} }
bool PhiAnalysis::isPotentiallyUndefinedAt(InternedString name, CFGBlock* block) { bool PhiAnalysis::isPotentiallyUndefinedAt(InternedString name, CFGBlock* block) {
assert(!startswith(name.str(), "!")); assert(!startswith(name.str(), "!"));
for (CFGBlock* pred : block->predecessors) { assert(definedness.defined_at_beginning.count(block));
DefinednessAnalysis::DefinitionLevel dlevel = definedness.isDefinedAtEnd(name, pred); return definedness.defined_at_beginning[block][name] != DefinednessAnalysis::Defined;
if (dlevel != DefinednessAnalysis::Defined)
return true;
}
return false;
} }
LivenessAnalysis* computeLivenessInfo(CFG* cfg) { LivenessAnalysis* computeLivenessInfo(CFG* cfg) {
...@@ -478,6 +492,38 @@ LivenessAnalysis* computeLivenessInfo(CFG* cfg) { ...@@ -478,6 +492,38 @@ LivenessAnalysis* computeLivenessInfo(CFG* cfg) {
} }
PhiAnalysis* computeRequiredPhis(const ParamNames& args, CFG* cfg, LivenessAnalysis* liveness, ScopeInfo* scope_info) { PhiAnalysis* computeRequiredPhis(const ParamNames& args, CFG* cfg, LivenessAnalysis* liveness, ScopeInfo* scope_info) {
return new PhiAnalysis(args, cfg, liveness, scope_info); llvm::DenseMap<InternedString, DefinednessAnalysis::DefinitionLevel> initial_map;
for (auto e : args.args)
initial_map[scope_info->internString(e)] = DefinednessAnalysis::Defined;
if (args.vararg.size())
initial_map[scope_info->internString(args.vararg)] = DefinednessAnalysis::Defined;
if (args.kwarg.size())
initial_map[scope_info->internString(args.kwarg)] = DefinednessAnalysis::Defined;
return new PhiAnalysis(std::move(initial_map), cfg->getStartingBlock(), false, liveness, scope_info);
}
PhiAnalysis* computeRequiredPhis(const OSREntryDescriptor* entry_descriptor, LivenessAnalysis* liveness,
ScopeInfo* scope_info) {
llvm::DenseMap<InternedString, DefinednessAnalysis::DefinitionLevel> initial_map;
llvm::StringSet<> potentially_undefined;
for (const auto& p : entry_descriptor->args) {
if (!startswith(p.first.str(), "!is_defined_"))
continue;
potentially_undefined.insert(p.first.str().substr(12));
}
for (const auto& p : entry_descriptor->args) {
if (p.first.str()[0] == '!')
continue;
if (potentially_undefined.count(p.first.str()))
initial_map[p.first] = DefinednessAnalysis::PotentiallyDefined;
else
initial_map[p.first] = DefinednessAnalysis::Defined;
}
return new PhiAnalysis(std::move(initial_map), entry_descriptor->backedge->target, true, liveness, scope_info);
} }
} }
...@@ -52,6 +52,8 @@ public: ...@@ -52,6 +52,8 @@ public:
bool isLiveAtEnd(InternedString name, CFGBlock* block); bool isLiveAtEnd(InternedString name, CFGBlock* block);
}; };
class PhiAnalysis;
class DefinednessAnalysis { class DefinednessAnalysis {
public: public:
enum DefinitionLevel { enum DefinitionLevel {
...@@ -62,16 +64,21 @@ public: ...@@ -62,16 +64,21 @@ public:
typedef llvm::DenseSet<InternedString> RequiredSet; typedef llvm::DenseSet<InternedString> RequiredSet;
private: private:
llvm::DenseMap<CFGBlock*, llvm::DenseMap<InternedString, DefinitionLevel>> results; llvm::DenseMap<CFGBlock*, llvm::DenseMap<InternedString, DefinitionLevel>> defined_at_beginning, defined_at_end;
llvm::DenseMap<CFGBlock*, RequiredSet> defined_at_end; llvm::DenseMap<CFGBlock*, RequiredSet> defined_at_end_sets;
ScopeInfo* scope_info;
public: public:
DefinednessAnalysis(const ParamNames& param_names, CFG* cfg, ScopeInfo* scope_info); DefinednessAnalysis() {}
void run(llvm::DenseMap<InternedString, DefinitionLevel>&& initial_map, CFGBlock* initial_block,
ScopeInfo* scope_info);
DefinitionLevel isDefinedAtEnd(InternedString name, CFGBlock* block); DefinitionLevel isDefinedAtEnd(InternedString name, CFGBlock* block);
const RequiredSet& getDefinedNamesAtEnd(CFGBlock* block); const RequiredSet& getDefinedNamesAtEnd(CFGBlock* block);
friend class PhiAnalysis;
}; };
class PhiAnalysis { class PhiAnalysis {
public: public:
typedef llvm::DenseSet<InternedString> RequiredSet; typedef llvm::DenseSet<InternedString> RequiredSet;
...@@ -83,18 +90,24 @@ private: ...@@ -83,18 +90,24 @@ private:
llvm::DenseMap<CFGBlock*, RequiredSet> required_phis; llvm::DenseMap<CFGBlock*, RequiredSet> required_phis;
public: public:
PhiAnalysis(const ParamNames&, CFG* cfg, LivenessAnalysis* liveness, ScopeInfo* scope_info); // Initials_need_phis specifies that initial_map should count as an additional entry point
// that may require phis.
PhiAnalysis(llvm::DenseMap<InternedString, DefinednessAnalysis::DefinitionLevel>&& initial_map,
CFGBlock* initial_block, bool initials_need_phis, LivenessAnalysis* liveness, ScopeInfo* scope_info);
bool isRequired(InternedString name, CFGBlock* block); bool isRequired(InternedString name, CFGBlock* block);
bool isRequiredAfter(InternedString name, CFGBlock* block); bool isRequiredAfter(InternedString name, CFGBlock* block);
const RequiredSet& getAllRequiredAfter(CFGBlock* block); const RequiredSet& getAllRequiredAfter(CFGBlock* block);
const RequiredSet& getAllRequiredFor(CFGBlock* block); const RequiredSet& getAllRequiredFor(CFGBlock* block);
// If "name" may be undefined at the beginning of any immediate successor block of "block":
bool isPotentiallyUndefinedAfter(InternedString name, CFGBlock* block); bool isPotentiallyUndefinedAfter(InternedString name, CFGBlock* block);
// If "name" may be undefined at the beginning of "block"
bool isPotentiallyUndefinedAt(InternedString name, CFGBlock* block); bool isPotentiallyUndefinedAt(InternedString name, CFGBlock* block);
}; };
LivenessAnalysis* computeLivenessInfo(CFG*); LivenessAnalysis* computeLivenessInfo(CFG*);
PhiAnalysis* computeRequiredPhis(const ParamNames&, CFG*, LivenessAnalysis*, ScopeInfo* scope_Info); PhiAnalysis* computeRequiredPhis(const ParamNames&, CFG*, LivenessAnalysis*, ScopeInfo* scope_info);
PhiAnalysis* computeRequiredPhis(const OSREntryDescriptor*, LivenessAnalysis*, ScopeInfo* scope_info);
} }
#endif #endif
...@@ -721,7 +721,7 @@ public: ...@@ -721,7 +721,7 @@ public:
return changed; return changed;
} }
static PropagatingTypeAnalysis* doAnalysis(CFG* cfg, SpeculationLevel speculation, ScopeInfo* scope_info, static PropagatingTypeAnalysis* doAnalysis(SpeculationLevel speculation, ScopeInfo* scope_info,
TypeMap&& initial_types, CFGBlock* initial_block) { TypeMap&& initial_types, CFGBlock* initial_block) {
Timer _t("PropagatingTypeAnalysis::doAnalysis()"); Timer _t("PropagatingTypeAnalysis::doAnalysis()");
...@@ -785,15 +785,16 @@ public: ...@@ -785,15 +785,16 @@ public:
} }
if (VERBOSITY("types")) { if (VERBOSITY("types")) {
printf("Type analysis: %ld BBs, %d evaluations = %.1f evaluations/block\n", cfg->blocks.size(), printf("Type analysis: %d BBs, %d evaluations = %.1f evaluations/block\n", starting_types.size(),
num_evaluations, 1.0 * num_evaluations / cfg->blocks.size()); num_evaluations, 1.0 * num_evaluations / starting_types.size());
} }
if (VERBOSITY("types") >= 3) { if (VERBOSITY("types") >= 3) {
for (CFGBlock* b : cfg->blocks) { for (const auto& p : starting_types) {
auto b = p.first;
printf("Types at beginning of block %d:\n", b->idx); printf("Types at beginning of block %d:\n", b->idx);
TypeMap& starting = starting_types[b]; const TypeMap& starting = p.second;
for (const auto& p : starting) { for (const auto& p : starting) {
ASSERT(p.second, "%s", p.first.c_str()); ASSERT(p.second, "%s", p.first.c_str());
printf("%s: %s\n", p.first.c_str(), p.second->debugName().c_str()); printf("%s: %s\n", p.first.c_str(), p.second->debugName().c_str());
...@@ -836,17 +837,17 @@ TypeAnalysis* doTypeAnalysis(CFG* cfg, const ParamNames& arg_names, const std::v ...@@ -836,17 +837,17 @@ TypeAnalysis* doTypeAnalysis(CFG* cfg, const ParamNames& arg_names, const std::v
assert(i == arg_types.size()); assert(i == arg_types.size());
return PropagatingTypeAnalysis::doAnalysis(cfg, speculation, scope_info, std::move(initial_types), return PropagatingTypeAnalysis::doAnalysis(speculation, scope_info, std::move(initial_types),
cfg->getStartingBlock()); cfg->getStartingBlock());
} }
TypeAnalysis* doTypeAnalysis(CFG* cfg, const OSREntryDescriptor* entry_descriptor, EffortLevel effort, TypeAnalysis* doTypeAnalysis(const OSREntryDescriptor* entry_descriptor, EffortLevel effort,
TypeAnalysis::SpeculationLevel speculation, ScopeInfo* scope_info) { TypeAnalysis::SpeculationLevel speculation, ScopeInfo* scope_info) {
// if (effort == EffortLevel::INTERPRETED) { // if (effort == EffortLevel::INTERPRETED) {
// return new NullTypeAnalysis(); // return new NullTypeAnalysis();
//} //}
TypeMap initial_types(entry_descriptor->args.begin(), entry_descriptor->args.end()); TypeMap initial_types(entry_descriptor->args.begin(), entry_descriptor->args.end());
return PropagatingTypeAnalysis::doAnalysis(cfg, speculation, scope_info, std::move(initial_types), return PropagatingTypeAnalysis::doAnalysis(speculation, scope_info, std::move(initial_types),
entry_descriptor->backedge->target); entry_descriptor->backedge->target);
} }
} }
...@@ -46,7 +46,7 @@ public: ...@@ -46,7 +46,7 @@ public:
TypeAnalysis* doTypeAnalysis(CFG* cfg, const ParamNames& param_names, TypeAnalysis* doTypeAnalysis(CFG* cfg, const ParamNames& param_names,
const std::vector<ConcreteCompilerType*>& arg_types, EffortLevel effort, const std::vector<ConcreteCompilerType*>& arg_types, EffortLevel effort,
TypeAnalysis::SpeculationLevel speculation, ScopeInfo* scope_info); TypeAnalysis::SpeculationLevel speculation, ScopeInfo* scope_info);
TypeAnalysis* doTypeAnalysis(CFG* cfg, const OSREntryDescriptor* entry_descriptor, EffortLevel effort, TypeAnalysis* doTypeAnalysis(const OSREntryDescriptor* entry_descriptor, EffortLevel effort,
TypeAnalysis::SpeculationLevel speculation, ScopeInfo* scope_info); TypeAnalysis::SpeculationLevel speculation, ScopeInfo* scope_info);
} }
......
...@@ -132,6 +132,7 @@ private: ...@@ -132,6 +132,7 @@ private:
CompiledFunction* compiled_func; CompiledFunction* compiled_func;
SourceInfo* source_info; SourceInfo* source_info;
ScopeInfo* scope_info; ScopeInfo* scope_info;
PhiAnalysis* phis;
SymMap sym_table; SymMap sym_table;
CFGBlock* next_block, *current_block; CFGBlock* next_block, *current_block;
...@@ -223,9 +224,9 @@ void ASTInterpreter::gcVisit(GCVisitor* visitor) { ...@@ -223,9 +224,9 @@ void ASTInterpreter::gcVisit(GCVisitor* visitor) {
} }
ASTInterpreter::ASTInterpreter(CompiledFunction* compiled_function) ASTInterpreter::ASTInterpreter(CompiledFunction* compiled_function)
: compiled_func(compiled_function), source_info(compiled_function->clfunc->source), scope_info(0), current_block(0), : compiled_func(compiled_function), source_info(compiled_function->clfunc->source), scope_info(0), phis(NULL),
current_inst(0), last_exception(NULL, NULL, NULL), passed_closure(0), created_closure(0), generator(0), current_block(0), current_inst(0), last_exception(NULL, NULL, NULL), passed_closure(0), created_closure(0),
edgecount(0), frame_info(ExcInfo(NULL, NULL, NULL)) { generator(0), edgecount(0), frame_info(ExcInfo(NULL, NULL, NULL)) {
CLFunction* f = compiled_function->clfunc; CLFunction* f = compiled_function->clfunc;
if (!source_info->cfg) if (!source_info->cfg)
...@@ -324,15 +325,19 @@ void ASTInterpreter::eraseDeadSymbols() { ...@@ -324,15 +325,19 @@ void ASTInterpreter::eraseDeadSymbols() {
if (source_info->liveness == NULL) if (source_info->liveness == NULL)
source_info->liveness = computeLivenessInfo(source_info->cfg); source_info->liveness = computeLivenessInfo(source_info->cfg);
if (source_info->phis == NULL) if (this->phis == NULL) {
source_info->phis = computeRequiredPhis(compiled_func->clfunc->param_names, source_info->cfg, PhiAnalysis*& phis = source_info->phis[/* entry_descriptor = */ NULL];
source_info->liveness, scope_info); if (!phis)
phis = computeRequiredPhis(compiled_func->clfunc->param_names, source_info->cfg, source_info->liveness,
scope_info);
this->phis = phis;
}
std::vector<InternedString> dead_symbols; std::vector<InternedString> dead_symbols;
for (auto& it : sym_table) { for (auto& it : sym_table) {
if (!source_info->liveness->isLiveAtEnd(it.first, current_block)) { if (!source_info->liveness->isLiveAtEnd(it.first, current_block)) {
dead_symbols.push_back(it.first); dead_symbols.push_back(it.first);
} else if (source_info->phis->isRequiredAfter(it.first, current_block)) { } else if (phis->isRequiredAfter(it.first, current_block)) {
assert(scope_info->getScopeTypeOfName(it.first) != ScopeInfo::VarScopeType::GLOBAL); assert(scope_info->getScopeTypeOfName(it.first) != ScopeInfo::VarScopeType::GLOBAL);
} else { } else {
} }
...@@ -472,10 +477,9 @@ Value ASTInterpreter::visit_jump(AST_Jump* node) { ...@@ -472,10 +477,9 @@ Value ASTInterpreter::visit_jump(AST_Jump* node) {
std::map<InternedString, Box*> sorted_symbol_table; std::map<InternedString, Box*> sorted_symbol_table;
auto phis = compiled_func->clfunc->source->phis;
for (auto& name : phis->definedness.getDefinedNamesAtEnd(current_block)) { for (auto& name : phis->definedness.getDefinedNamesAtEnd(current_block)) {
auto it = sym_table.find(name); auto it = sym_table.find(name);
if (!compiled_func->clfunc->source->liveness->isLiveAtEnd(name, current_block)) if (!source_info->liveness->isLiveAtEnd(name, current_block))
continue; continue;
if (phis->isPotentiallyUndefinedAfter(name, current_block)) { if (phis->isPotentiallyUndefinedAfter(name, current_block)) {
......
...@@ -35,7 +35,7 @@ namespace pyston { ...@@ -35,7 +35,7 @@ namespace pyston {
DS_DEFINE_RWLOCK(codegen_rwlock); DS_DEFINE_RWLOCK(codegen_rwlock);
SourceInfo::SourceInfo(BoxedModule* m, ScopingAnalysis* scoping, AST* ast, const std::vector<AST_stmt*>& body) SourceInfo::SourceInfo(BoxedModule* m, ScopingAnalysis* scoping, AST* ast, const std::vector<AST_stmt*>& body)
: parent_module(m), scoping(scoping), ast(ast), cfg(NULL), liveness(NULL), phis(NULL), body(body) { : parent_module(m), scoping(scoping), ast(ast), cfg(NULL), liveness(NULL), body(body) {
switch (ast->type) { switch (ast->type) {
case AST_TYPE::ClassDef: case AST_TYPE::ClassDef:
case AST_TYPE::Lambda: case AST_TYPE::Lambda:
......
...@@ -342,6 +342,8 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -342,6 +342,8 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
CompiledFunction* cf = irstate->getCurFunction(); CompiledFunction* cf = irstate->getCurFunction();
ConcreteCompilerType* rtn_type = irstate->getReturnType(); ConcreteCompilerType* rtn_type = irstate->getReturnType();
// llvm::MDNode* func_info = irstate->getFuncDbgInfo(); // llvm::MDNode* func_info = irstate->getFuncDbgInfo();
PhiAnalysis* phi_analysis = source->phis[entry_descriptor];
assert(phi_analysis);
if (entry_descriptor != NULL) if (entry_descriptor != NULL)
assert(blocks.count(source->cfg->getStartingBlock()) == 0); assert(blocks.count(source->cfg->getStartingBlock()) == 0);
...@@ -508,18 +510,11 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -508,18 +510,11 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
CFGBlock* block = traversal_order[_i].first; CFGBlock* block = traversal_order[_i].first;
CFGBlock* pred = traversal_order[_i].second; CFGBlock* pred = traversal_order[_i].second;
if (VERBOSITY("irgen") >= 3) if (!blocks.count(block))
printf("processing block %d\n", block->idx);
if (!blocks.count(block)) {
if (VERBOSITY("irgen") >= 3)
printf("Skipping this block\n");
// created_phis[block] = NULL;
// ending_symbol_tables[block] = NULL;
// phi_ending_symbol_tables[block] = NULL;
// llvm_exit_blocks[block] = NULL;
continue; continue;
}
if (VERBOSITY("irgen") >= 2)
printf("processing block %d\n", block->idx);
std::unique_ptr<IRGenerator> generator(createIRGenerator(irstate, llvm_entry_blocks, block, types)); std::unique_ptr<IRGenerator> generator(createIRGenerator(irstate, llvm_entry_blocks, block, types));
llvm::BasicBlock* entry_block_end = llvm_entry_blocks[block]; llvm::BasicBlock* entry_block_end = llvm_entry_blocks[block];
...@@ -634,9 +629,9 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -634,9 +629,9 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
std::set<InternedString> names; std::set<InternedString> names;
for (const auto& s : source->phis->getAllRequiredFor(block)) { for (const auto& s : phi_analysis->getAllRequiredFor(block)) {
names.insert(s); names.insert(s);
if (source->phis->isPotentiallyUndefinedAfter(s, block->predecessors[0])) { if (phi_analysis->isPotentiallyUndefinedAfter(s, block->predecessors[0])) {
names.insert(getIsDefinedName(s, source->getInternedStrings())); names.insert(getIsDefinedName(s, source->getInternedStrings()));
} }
} }
...@@ -807,7 +802,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -807,7 +802,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
// printf("(%d %ld) -> (%d %ld)\n", bpred->idx, phi_ending_symbol_tables[bpred]->size(), b->idx, // printf("(%d %ld) -> (%d %ld)\n", bpred->idx, phi_ending_symbol_tables[bpred]->size(), b->idx,
// phis->size()); // phis->size());
assert(sameKeyset(phi_ending_symbol_tables[bpred], phis)); ASSERT(sameKeyset(phi_ending_symbol_tables[bpred], phis), "%d->%d", bpred->idx, b->idx);
assert(phi_ending_symbol_tables[bpred]->size() == phis->size()); assert(phi_ending_symbol_tables[bpred]->size() == phis->size());
} }
...@@ -1042,7 +1037,7 @@ CompiledFunction* doCompile(SourceInfo* source, ParamNames* param_names, const O ...@@ -1042,7 +1037,7 @@ CompiledFunction* doCompile(SourceInfo* source, ParamNames* param_names, const O
speculation_level = TypeAnalysis::SOME; speculation_level = TypeAnalysis::SOME;
TypeAnalysis* types; TypeAnalysis* types;
if (entry_descriptor) if (entry_descriptor)
types = doTypeAnalysis(source->cfg, entry_descriptor, effort, speculation_level, source->getScopeInfo()); types = doTypeAnalysis(entry_descriptor, effort, speculation_level, source->getScopeInfo());
else else
types = doTypeAnalysis(source->cfg, *param_names, spec->arg_types, effort, speculation_level, types = doTypeAnalysis(source->cfg, *param_names, spec->arg_types, effort, speculation_level,
source->getScopeInfo()); source->getScopeInfo());
...@@ -1060,7 +1055,7 @@ CompiledFunction* doCompile(SourceInfo* source, ParamNames* param_names, const O ...@@ -1060,7 +1055,7 @@ CompiledFunction* doCompile(SourceInfo* source, ParamNames* param_names, const O
computeBlockSetClosure(blocks); computeBlockSetClosure(blocks);
} }
IRGenState irstate(cf, source, param_names, getGCBuilder(), dbg_funcinfo); IRGenState irstate(cf, source, source->phis[entry_descriptor], param_names, getGCBuilder(), dbg_funcinfo);
emitBBs(&irstate, types, entry_descriptor, blocks); emitBBs(&irstate, types, entry_descriptor, blocks);
......
...@@ -232,8 +232,13 @@ CompiledFunction* compileFunction(CLFunction* f, FunctionSpecialization* spec, E ...@@ -232,8 +232,13 @@ CompiledFunction* compileFunction(CLFunction* f, FunctionSpecialization* spec, E
if (source->liveness == NULL) if (source->liveness == NULL)
source->liveness = computeLivenessInfo(source->cfg); source->liveness = computeLivenessInfo(source->cfg);
if (source->phis == NULL) PhiAnalysis*& phis = source->phis[entry_descriptor];
source->phis = computeRequiredPhis(f->param_names, source->cfg, source->liveness, source->getScopeInfo()); if (!phis) {
if (entry_descriptor)
phis = computeRequiredPhis(entry_descriptor, source->liveness, source->getScopeInfo());
else
phis = computeRequiredPhis(f->param_names, source->cfg, source->liveness, source->getScopeInfo());
}
} }
......
...@@ -2278,7 +2278,7 @@ private: ...@@ -2278,7 +2278,7 @@ private:
p.second->decvref(emitter); p.second->decvref(emitter);
symbol_table.erase(getIsDefinedName(p.first)); symbol_table.erase(getIsDefinedName(p.first));
symbol_table.erase(p.first); symbol_table.erase(p.first);
} else if (source->phis->isRequiredAfter(p.first, myblock)) { } else if (irstate->getPhis()->isRequiredAfter(p.first, myblock)) {
assert(scope_info->getScopeTypeOfName(p.first) != ScopeInfo::VarScopeType::GLOBAL); assert(scope_info->getScopeTypeOfName(p.first) != ScopeInfo::VarScopeType::GLOBAL);
ConcreteCompilerType* phi_type = types->getTypeAtBlockEnd(p.first, myblock); ConcreteCompilerType* phi_type = types->getTypeAtBlockEnd(p.first, myblock);
// printf("Converting %s from %s to %s\n", p.first.c_str(), // printf("Converting %s from %s to %s\n", p.first.c_str(),
...@@ -2302,9 +2302,10 @@ private: ...@@ -2302,9 +2302,10 @@ private:
} }
} }
const PhiAnalysis::RequiredSet& all_phis = source->phis->getAllRequiredAfter(myblock); const PhiAnalysis::RequiredSet& all_phis = irstate->getPhis()->getAllRequiredAfter(myblock);
for (PhiAnalysis::RequiredSet::const_iterator it = all_phis.begin(), end = all_phis.end(); it != end; ++it) { for (PhiAnalysis::RequiredSet::const_iterator it = all_phis.begin(), end = all_phis.end(); it != end; ++it) {
// printf("phi will be required for %s\n", it->c_str()); if (VERBOSITY() >= 3)
printf("phi will be required for %s\n", it->c_str());
assert(scope_info->getScopeTypeOfName(*it) != ScopeInfo::VarScopeType::GLOBAL); assert(scope_info->getScopeTypeOfName(*it) != ScopeInfo::VarScopeType::GLOBAL);
CompilerVariable*& cur = symbol_table[*it]; CompilerVariable*& cur = symbol_table[*it];
...@@ -2316,7 +2317,7 @@ private: ...@@ -2316,7 +2317,7 @@ private:
ConcreteCompilerVariable* is_defined ConcreteCompilerVariable* is_defined
= static_cast<ConcreteCompilerVariable*>(_popFake(defined_name, true)); = static_cast<ConcreteCompilerVariable*>(_popFake(defined_name, true));
if (source->phis->isPotentiallyUndefinedAfter(*it, myblock)) { if (irstate->getPhis()->isPotentiallyUndefinedAfter(*it, myblock)) {
// printf("is potentially undefined later, so marking it defined\n"); // printf("is potentially undefined later, so marking it defined\n");
if (is_defined) { if (is_defined) {
_setFake(defined_name, is_defined); _setFake(defined_name, is_defined);
...@@ -2415,7 +2416,7 @@ public: ...@@ -2415,7 +2416,7 @@ public:
// We have one successor, but they have more than one predecessor. // We have one successor, but they have more than one predecessor.
// We're going to sort out which symbols need to go in phi_st and which belong inst. // We're going to sort out which symbols need to go in phi_st and which belong inst.
for (SymbolTable::iterator it = st->begin(); it != st->end();) { for (SymbolTable::iterator it = st->begin(); it != st->end();) {
if (allowableFakeEndingSymbol(it->first) || source->phis->isRequiredAfter(it->first, myblock)) { if (allowableFakeEndingSymbol(it->first) || irstate->getPhis()->isRequiredAfter(it->first, myblock)) {
ASSERT(it->second->isGrabbed(), "%s", it->first.c_str()); ASSERT(it->second->isGrabbed(), "%s", it->first.c_str());
assert(it->second->getVrefs() == 1); assert(it->second->getVrefs() == 1);
// this conversion should have already happened... should refactor this. // this conversion should have already happened... should refactor this.
......
...@@ -56,6 +56,7 @@ class IRGenState { ...@@ -56,6 +56,7 @@ class IRGenState {
private: private:
CompiledFunction* cf; CompiledFunction* cf;
SourceInfo* source_info; SourceInfo* source_info;
PhiAnalysis* phis;
ParamNames* param_names; ParamNames* param_names;
GCBuilder* gc; GCBuilder* gc;
llvm::MDNode* func_dbg_info; llvm::MDNode* func_dbg_info;
...@@ -68,9 +69,9 @@ private: ...@@ -68,9 +69,9 @@ private:
public: public:
IRGenState(CompiledFunction* cf, SourceInfo* source_info, ParamNames* param_names, GCBuilder* gc, IRGenState(CompiledFunction* cf, SourceInfo* source_info, PhiAnalysis* phis, ParamNames* param_names, GCBuilder* gc,
llvm::MDNode* func_dbg_info) llvm::MDNode* func_dbg_info)
: cf(cf), source_info(source_info), param_names(param_names), gc(gc), func_dbg_info(func_dbg_info), : cf(cf), source_info(source_info), phis(phis), param_names(param_names), gc(gc), func_dbg_info(func_dbg_info),
scratch_space(NULL), frame_info(NULL), frame_info_arg(NULL), scratch_size(0) { scratch_space(NULL), frame_info(NULL), frame_info_arg(NULL), scratch_size(0) {
assert(cf->func); assert(cf->func);
assert(!cf->clfunc); // in this case don't need to pass in sourceinfo assert(!cf->clfunc); // in this case don't need to pass in sourceinfo
...@@ -92,6 +93,8 @@ public: ...@@ -92,6 +93,8 @@ public:
SourceInfo* getSourceInfo() { return source_info; } SourceInfo* getSourceInfo() { return source_info; }
PhiAnalysis* getPhis() { return phis; }
ScopeInfo* getScopeInfo(); ScopeInfo* getScopeInfo();
ScopeInfo* getScopeInfoForNode(AST* node); ScopeInfo* getScopeInfoForNode(AST* node);
......
...@@ -244,7 +244,7 @@ public: ...@@ -244,7 +244,7 @@ public:
AST* ast; AST* ast;
CFG* cfg; CFG* cfg;
LivenessAnalysis* liveness; LivenessAnalysis* liveness;
PhiAnalysis* phis; std::unordered_map<const OSREntryDescriptor*, PhiAnalysis*> phis;
bool is_generator; bool is_generator;
InternedStringPool& getInternedStrings(); InternedStringPool& getInternedStrings();
......
# fail-if: '-O' not in EXTRA_JIT_ARGS
# - wip
# Regression test: make sure we can handle variables that are only defined # Regression test: make sure we can handle variables that are only defined
# on excluded parts of an osr compilation # on excluded parts of an osr compilation
def f(): def f():
......
...@@ -16,3 +16,4 @@ endmacro() ...@@ -16,3 +16,4 @@ endmacro()
add_unittest(gc) add_unittest(gc)
add_unittest(analysis) add_unittest(analysis)
add_custom_command(TARGET analysis_unittest POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/test/unittests/analysis_listcomp.py ${CMAKE_BINARY_DIR}/test/unittests/analysis_listcomp.py) add_custom_command(TARGET analysis_unittest POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/test/unittests/analysis_listcomp.py ${CMAKE_BINARY_DIR}/test/unittests/analysis_listcomp.py)
add_custom_command(TARGET analysis_unittest POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/test/unittests/analysis_osr.py ${CMAKE_BINARY_DIR}/test/unittests/analysis_osr.py)
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#include "analysis/function_analysis.h" #include "analysis/function_analysis.h"
#include "analysis/scoping_analysis.h" #include "analysis/scoping_analysis.h"
#include "codegen/osrentry.h"
#include "codegen/parser.h" #include "codegen/parser.h"
#include "core/ast.h" #include "core/ast.h"
#include "core/cfg.h" #include "core/cfg.h"
...@@ -16,7 +17,7 @@ using namespace pyston; ...@@ -16,7 +17,7 @@ using namespace pyston;
class AnalysisTest : public ::testing::Test { class AnalysisTest : public ::testing::Test {
protected: protected:
virtual void SetUp() { static void SetUpTestCase() {
initCodegen(); initCodegen();
} }
}; };
...@@ -35,7 +36,7 @@ TEST_F(AnalysisTest, augassign) { ...@@ -35,7 +36,7 @@ TEST_F(AnalysisTest, augassign) {
ASSERT_FALSE(scope_info->getScopeTypeOfName(module->interned_strings->get("a")) == ScopeInfo::VarScopeType::GLOBAL); ASSERT_FALSE(scope_info->getScopeTypeOfName(module->interned_strings->get("a")) == ScopeInfo::VarScopeType::GLOBAL);
ASSERT_FALSE(scope_info->getScopeTypeOfName(module->interned_strings->get("b")) == ScopeInfo::VarScopeType::GLOBAL); ASSERT_FALSE(scope_info->getScopeTypeOfName(module->interned_strings->get("b")) == ScopeInfo::VarScopeType::GLOBAL);
SourceInfo* si = new SourceInfo(createModule("__main__", fn), scoping, func, func->body); SourceInfo* si = new SourceInfo(createModule("augassign", fn), scoping, func, func->body);
CFG* cfg = computeCFG(si, func->body); CFG* cfg = computeCFG(si, func->body);
LivenessAnalysis* liveness = computeLivenessInfo(cfg); LivenessAnalysis* liveness = computeLivenessInfo(cfg);
...@@ -51,3 +52,83 @@ TEST_F(AnalysisTest, augassign) { ...@@ -51,3 +52,83 @@ TEST_F(AnalysisTest, augassign) {
PhiAnalysis* phis = computeRequiredPhis(ParamNames(func), cfg, liveness, scope_info); PhiAnalysis* phis = computeRequiredPhis(ParamNames(func), cfg, liveness, scope_info);
} }
void doOsrTest(bool is_osr, bool i_maybe_undefined) {
const std::string fn("test/unittests/analysis_osr.py");
AST_Module* module = caching_parse_file(fn.c_str());
assert(module);
ScopingAnalysis *scoping = new ScopingAnalysis(module);
assert(module->body[0]->type == AST_TYPE::FunctionDef);
AST_FunctionDef* func = static_cast<AST_FunctionDef*>(module->body[0]);
ScopeInfo* scope_info = scoping->getScopeInfoForNode(func);
SourceInfo* si = new SourceInfo(createModule("osr" + std::to_string((is_osr << 1) + i_maybe_undefined), fn),
scoping, func, func->body);
CFG* cfg = computeCFG(si, func->body);
LivenessAnalysis* liveness = computeLivenessInfo(cfg);
// cfg->print();
InternedString i_str = module->interned_strings->get("i");
InternedString idi_str = module->interned_strings->get("!is_defined_i");
InternedString iter_str = module->interned_strings->get("#iter_3");
CFGBlock* loop_backedge = cfg->blocks[5];
ASSERT_EQ(6, loop_backedge->idx);
ASSERT_EQ(1, loop_backedge->body.size());
ASSERT_EQ(AST_TYPE::Jump, loop_backedge->body[0]->type);
AST_Jump* backedge = ast_cast<AST_Jump>(loop_backedge->body[0]);
ASSERT_LE(backedge->target->idx, loop_backedge->idx);
PhiAnalysis* phis;
if (is_osr) {
OSREntryDescriptor* entry_descriptor = OSREntryDescriptor::create(NULL, backedge);
entry_descriptor->args[i_str] = NULL;
if (i_maybe_undefined)
entry_descriptor->args[idi_str] = NULL;
entry_descriptor->args[iter_str] = NULL;
phis = computeRequiredPhis(entry_descriptor, liveness, scope_info);
} else {
phis = computeRequiredPhis(ParamNames(func), cfg, liveness, scope_info);
}
// First, verify that we require phi nodes for the block we enter into.
// This is somewhat tricky since the osr entry represents an extra entry
// into the BB which the analysis might not otherwise track.
auto required_phis = phis->getAllRequiredFor(backedge->target);
EXPECT_EQ(1, required_phis.count(i_str));
EXPECT_EQ(0, required_phis.count(idi_str));
EXPECT_EQ(1, required_phis.count(iter_str));
EXPECT_EQ(2, required_phis.size());
EXPECT_EQ(!is_osr || i_maybe_undefined, phis->isPotentiallyUndefinedAt(i_str, backedge->target));
EXPECT_FALSE(phis->isPotentiallyUndefinedAt(iter_str, backedge->target));
EXPECT_EQ(!is_osr || i_maybe_undefined, phis->isPotentiallyUndefinedAfter(i_str, loop_backedge));
EXPECT_FALSE(phis->isPotentiallyUndefinedAfter(iter_str, loop_backedge));
// Now, let's verify that we don't need a phi after the loop
CFGBlock* if_join = cfg->blocks[7];
ASSERT_EQ(8, if_join->idx);
ASSERT_EQ(2, if_join->predecessors.size());
if (is_osr)
EXPECT_EQ(0, phis->getAllRequiredFor(if_join).size());
else
EXPECT_EQ(1, phis->getAllRequiredFor(if_join).size());
}
TEST_F(AnalysisTest, osr_initial) {
doOsrTest(false, false);
}
TEST_F(AnalysisTest, osr1) {
doOsrTest(true, false);
}
TEST_F(AnalysisTest, osr2) {
doOsrTest(true, true);
}
def f():
if True:
for i in xrange(20000):
pass
else:
a = 1
f()
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