Commit 7fb7aaa5 authored by Michael Arntzenius's avatar Michael Arntzenius

with statements now handle exceptions properly

parent be19d931
...@@ -738,6 +738,8 @@ CompilerVariable* makeFunction(IREmitter& emitter, CLFunction* f, CompilerVariab ...@@ -738,6 +738,8 @@ CompilerVariable* makeFunction(IREmitter& emitter, CLFunction* f, CompilerVariab
llvm::Value* isGenerator_v = llvm::ConstantInt::get(g.i1, isGenerator, false); llvm::Value* isGenerator_v = llvm::ConstantInt::get(g.i1, isGenerator, false);
// We know this function call can't throw, so it's safe to use emitter.getBuilder()->CreateCall() rather than
// emitter.createCall().
llvm::Value* boxed = emitter.getBuilder()->CreateCall( llvm::Value* boxed = emitter.getBuilder()->CreateCall(
g.funcs.boxCLFunction, g.funcs.boxCLFunction,
std::vector<llvm::Value*>{ embedConstantPtr(f, g.llvm_clfunction_type_ptr), closure_v, isGenerator_v, scratch, std::vector<llvm::Value*>{ embedConstantPtr(f, g.llvm_clfunction_type_ptr), closure_v, isGenerator_v, scratch,
......
...@@ -286,7 +286,7 @@ private: ...@@ -286,7 +286,7 @@ private:
protected: protected:
void drop(IREmitter& emitter) override { type->drop(emitter, this); } void drop(IREmitter& emitter) override { type->drop(emitter, this); }
void grab(IREmitter& emmitter) override { type->grab(emmitter, this); } void grab(IREmitter& emitter) override { type->grab(emitter, this); }
public: public:
ValuedCompilerVariable(T* type, V value, bool grabbed) : CompilerVariable(grabbed), type(type), value(value) { ValuedCompilerVariable(T* type, V value, bool grabbed) : CompilerVariable(grabbed), type(type), value(value) {
......
...@@ -358,8 +358,8 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -358,8 +358,8 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
llvm_entry_blocks[block] = llvm::BasicBlock::Create(g.context, buf, irstate->getLLVMFunction()); llvm_entry_blocks[block] = llvm::BasicBlock::Create(g.context, buf, irstate->getLLVMFunction());
} }
llvm::BasicBlock* osr_entry_block // the function entry block, where we add the type guards [no guards anymore]
= NULL; // the function entry block, where we add the type guards [no guards anymore] llvm::BasicBlock* osr_entry_block = NULL;
llvm::BasicBlock* osr_unbox_block_end = NULL; // the block after type guards where we up/down-convert things llvm::BasicBlock* osr_unbox_block_end = NULL; // the block after type guards where we up/down-convert things
ConcreteSymbolTable* osr_syms = NULL; // syms after conversion ConcreteSymbolTable* osr_syms = NULL; // syms after conversion
if (entry_descriptor != NULL) { if (entry_descriptor != NULL) {
...@@ -516,6 +516,8 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -516,6 +516,8 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
created_phis[block] = phis; created_phis[block] = phis;
// Set initial symbol table: // Set initial symbol table:
// If we're in the starting block, no phis or symbol table changes for us.
// Generate function entry code instead.
if (block == source->cfg->getStartingBlock()) { if (block == source->cfg->getStartingBlock()) {
assert(entry_descriptor == NULL); assert(entry_descriptor == NULL);
...@@ -594,7 +596,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -594,7 +596,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
ConcreteCompilerType* analyzed_type = getTypeAtBlockStart(types, p.first, block); ConcreteCompilerType* analyzed_type = getTypeAtBlockStart(types, p.first, block);
// printf("For %s, given %s, analyzed for %s\n", p.first.c_str(), p.second->debugName().c_str(), // printf("For %s, given %s, analyzed for %s\n", p.first.c_str(), p.second->debugName().c_str(),
// analyzed_type->debugName().c_str()); // analyzed_type->debugName().c_str());
llvm::PHINode* phi = emitter->getBuilder()->CreatePHI(analyzed_type->llvmType(), llvm::PHINode* phi = emitter->getBuilder()->CreatePHI(analyzed_type->llvmType(),
block->predecessors.size() + 1, p.first.str()); block->predecessors.size() + 1, p.first.str());
...@@ -650,30 +652,92 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -650,30 +652,92 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
ASSERT(phi_ending_symbol_tables[pred]->size() == 0, "%d %d", block->idx, pred->idx); ASSERT(phi_ending_symbol_tables[pred]->size() == 0, "%d %d", block->idx, pred->idx);
assert(ending_symbol_tables.count(pred)); assert(ending_symbol_tables.count(pred));
// Filter out any names set by an invoke statement at the end // Filter out any names set by an invoke statement at the end of the previous block, if we're in the
// of the previous block, if we're in the unwind path. // unwind path. This definitely doesn't seem like the most elegant way to do this, but the rest of the
// This definitely doesn't seem like the most elegant way to do this, // analysis frameworks can't (yet) support the idea of a block flowing differently to its different
// but the rest of the analysis frameworks can't (yet) support the idea of // successors.
// a block flowing differently to its different predecessors. //
// AST statements which can set a name:
// - Assign
// - ClassDef
// - FunctionDef
// - Import, ImportFrom: these get translated into Assigns by our CFG pass.
//
// We only need to do this in the case that we have exactly one predecessor, because:
// - a block ending in an invoke will have multiple successors
// - critical edges (block with multiple successors -> block with multiple predecessors)
// are disallowed
// FIXME: this doesn't work if we're over-writing a local name that's already been defined.
// see with_class_redefine.py
//
// There isn't any great solution to this except to fundamentally change the way we handle this problem.
//
// One solution:
// - make "create-class" & "create-function" expressions, and translate classdefs into:
//
// Assign(temporary, CreateClass(....))
// Assign(classname, temporary)
//
// Then the Assign(temporary, CreateClass(...)) will go in an invoke, which is fine because we never
// reuse temporary names.
// FIXME: Unfortunately, doing this will create heisenbugs unless the name we're removing is a
// temporary.
//
// The reason being, our definedness analysis passes don't know *anything* about how invoke statements
// don't actually define anything on the exception pass. Which means that their information and our
// symbol tables will disagree. This manifests in various ways, such as `sameKeyset()` asserts tripping.
// The only good solutions to this are:
//
// 1. making our analysis passes understand invokes
// 2. making assignments inside invokes always be to temporary variables
// (and never referring to those temporary variables on the exception path)
//
// TODO(rntz): implement #2. This would also solve the above FIXME in this file.
auto pred = block->predecessors[0]; auto pred = block->predecessors[0];
auto last_inst = pred->body.back(); auto last_inst = pred->body.back();
SymbolTable* sym_table = ending_symbol_tables[pred]; SymbolTable* sym_table = ending_symbol_tables[pred];
bool created_new_sym_table = false; bool created_new_sym_table = false;
if (last_inst->type == AST_TYPE::Invoke) { if (last_inst->type == AST_TYPE::Invoke && ast_cast<AST_Invoke>(last_inst)->exc_dest == block) {
auto invoke = ast_cast<AST_Invoke>(last_inst); AST_stmt* stmt = ast_cast<AST_Invoke>(last_inst)->stmt;
if (invoke->exc_dest == block && invoke->stmt->type == AST_TYPE::Assign) { bool remove_name = false;
auto asgn = ast_cast<AST_Assign>(invoke->stmt); InternedString name;
if (stmt->type == AST_TYPE::Assign) {
auto asgn = ast_cast<AST_Assign>(stmt);
assert(asgn->targets.size() == 1); assert(asgn->targets.size() == 1);
if (asgn->targets[0]->type == AST_TYPE::Name) { if (asgn->targets[0]->type == AST_TYPE::Name) {
auto name = ast_cast<AST_Name>(asgn->targets[0]); name = ast_cast<AST_Name>(asgn->targets[0])->id;
remove_name = true;
// TODO: inneficient // You might think I need to check whether `name' is being assigned globally or locally,
sym_table = new SymbolTable(*sym_table); // since a global assign doesn't affect the symbol table. However, the CFG pass only
ASSERT(sym_table->count(name->id), "%d %s\n", block->idx, name->id.c_str()); // generates invoke-assigns to temporary variables. Just to be sure, we assert:
sym_table->erase(name->id); assert(!source->getScopeInfo()->refersToGlobal(name));
created_new_sym_table = true;
} }
} else if (stmt->type == AST_TYPE::ClassDef) {
// However, here we *do* have to check for global scope. :(
name = ast_cast<AST_ClassDef>(stmt)->name;
remove_name = !source->getScopeInfo()->refersToGlobal(name);
} else if (stmt->type == AST_TYPE::FunctionDef) {
// and here, as well.
name = ast_cast<AST_FunctionDef>(stmt)->name;
remove_name = !source->getScopeInfo()->refersToGlobal(name);
}
// The CFG pass translates away these statements, so we should never encounter them. If we did, we
// would need to remove a name here.
assert(stmt->type != AST_TYPE::Import);
assert(stmt->type != AST_TYPE::ImportFrom);
if (remove_name) {
// TODO: inefficient
sym_table = new SymbolTable(*sym_table);
// FIXME: is getting tripped sometimes by classdef
ASSERT(sym_table->count(name), "%d %s\n", block->idx, name.c_str());
sym_table->erase(name);
created_new_sym_table = true;
} }
} }
...@@ -689,21 +753,25 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -689,21 +753,25 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
// Start off with the non-phi ones: // Start off with the non-phi ones:
generator->copySymbolsFrom(ending_symbol_tables[pred]); generator->copySymbolsFrom(ending_symbol_tables[pred]);
// NB. This is where most `typical' phi nodes get added.
// And go through and add phi nodes: // And go through and add phi nodes:
ConcreteSymbolTable* pred_st = phi_ending_symbol_tables[pred]; ConcreteSymbolTable* pred_st = phi_ending_symbol_tables[pred];
for (ConcreteSymbolTable::iterator it = pred_st->begin(); it != pred_st->end(); ++it) { for (auto it = pred_st->begin(); it != pred_st->end(); ++it) {
// printf("adding phi for %s\n", it->first.c_str()); InternedString name = it->first;
llvm::PHINode* phi = emitter->getBuilder()->CreatePHI(it->second->getType()->llvmType(), ConcreteCompilerVariable* cv = it->second; // incoming CCV from predecessor block
block->predecessors.size(), it->first.str()); // printf("block %d: adding phi for %s from pred %d\n", block->idx, name.c_str(), pred->idx);
llvm::PHINode* phi = emitter->getBuilder()->CreatePHI(cv->getType()->llvmType(),
block->predecessors.size(), name.str());
// emitter->getBuilder()->CreateCall(g.funcs.dump, phi); // emitter->getBuilder()->CreateCall(g.funcs.dump, phi);
ConcreteCompilerVariable* var = new ConcreteCompilerVariable(it->second->getType(), phi, true); ConcreteCompilerVariable* var = new ConcreteCompilerVariable(cv->getType(), phi, true);
generator->giveLocalSymbol(it->first, var); generator->giveLocalSymbol(name, var);
(*phis)[it->first] = std::make_pair(it->second->getType(), phi); (*phis)[it->first] = std::make_pair(it->second->getType(), phi);
} }
} }
} }
// Generate loop safepoints on backedges.
for (CFGBlock* predecessor : block->predecessors) { for (CFGBlock* predecessor : block->predecessors) {
if (predecessor->idx > block->idx) { if (predecessor->idx > block->idx) {
// Loop safepoint: // Loop safepoint:
...@@ -713,6 +781,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -713,6 +781,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
} }
} }
// Generate the IR for the block.
generator->run(block); generator->run(block);
const IRGenerator::EndingState& ending_st = generator->getEndingSymbolTable(); const IRGenerator::EndingState& ending_st = generator->getEndingSymbolTable();
...@@ -725,7 +794,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -725,7 +794,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
} }
//// ////
// Phi generation. // Phi population.
// We don't know the exact ssa values to back-propagate to the phi nodes until we've generated // We don't know the exact ssa values to back-propagate to the phi nodes until we've generated
// the relevant IR, so after we have done all of it, go back through and populate the phi nodes. // the relevant IR, so after we have done all of it, go back through and populate the phi nodes.
// Also, do some checking to make sure that the phi analysis stuff worked out, and that all blocks // Also, do some checking to make sure that the phi analysis stuff worked out, and that all blocks
...@@ -737,19 +806,23 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -737,19 +806,23 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
bool this_is_osr_entry = (entry_descriptor && b == entry_descriptor->backedge->target); bool this_is_osr_entry = (entry_descriptor && b == entry_descriptor->backedge->target);
#ifndef NDEBUG
// Check to see that all blocks agree on what symbols + types they should be propagating for phis.
for (int j = 0; j < b->predecessors.size(); j++) { for (int j = 0; j < b->predecessors.size(); j++) {
CFGBlock* b2 = b->predecessors[j]; CFGBlock* bpred = b->predecessors[j];
if (blocks.count(b2) == 0) if (blocks.count(bpred) == 0)
continue; continue;
// printf("(%d %ld) -> (%d %ld)\n", b2->idx, phi_ending_symbol_tables[b2]->size(), b->idx, phis->size()); // printf("(%d %ld) -> (%d %ld)\n", bpred->idx, phi_ending_symbol_tables[bpred]->size(), b->idx,
compareKeyset(phi_ending_symbol_tables[b2], phis); // phis->size());
assert(phi_ending_symbol_tables[b2]->size() == phis->size()); assert(sameKeyset(phi_ending_symbol_tables[bpred], phis));
assert(phi_ending_symbol_tables[bpred]->size() == phis->size());
} }
if (this_is_osr_entry) { if (this_is_osr_entry) {
compareKeyset(osr_syms, phis); assert(sameKeyset(osr_syms, phis));
} }
#endif // end checking phi agreement.
// Can't always add the phi incoming value right away, since we may have to create more // Can't always add the phi incoming value right away, since we may have to create more
// basic blocks as part of type coercion. // basic blocks as part of type coercion.
...@@ -757,21 +830,22 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -757,21 +830,22 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
// which we won't read until after all new BBs have been added. // which we won't read until after all new BBs have been added.
std::vector<std::tuple<llvm::PHINode*, llvm::Value*, llvm::BasicBlock*&>> phi_args; std::vector<std::tuple<llvm::PHINode*, llvm::Value*, llvm::BasicBlock*&>> phi_args;
for (PHITable::iterator it = phis->begin(); it != phis->end(); ++it) { for (auto it = phis->begin(); it != phis->end(); ++it) {
llvm::PHINode* llvm_phi = it->second.second; llvm::PHINode* llvm_phi = it->second.second;
for (int j = 0; j < b->predecessors.size(); j++) { for (int j = 0; j < b->predecessors.size(); j++) {
CFGBlock* b2 = b->predecessors[j]; CFGBlock* bpred = b->predecessors[j];
if (blocks.count(b2) == 0) if (blocks.count(bpred) == 0)
continue; continue;
ConcreteCompilerVariable* v = (*phi_ending_symbol_tables[b2])[it->first]; ConcreteCompilerVariable* v = (*phi_ending_symbol_tables[bpred])[it->first];
assert(v); assert(v);
assert(v->isGrabbed()); assert(v->isGrabbed());
// Make sure they all prepared for the same type: // Make sure they all prepared for the same type:
ASSERT(it->second.first == v->getType(), "%d %d: %s %s %s", b->idx, b2->idx, it->first.c_str(), ASSERT(it->second.first == v->getType(), "%d %d: %s %s %s", b->idx, bpred->idx, it->first.c_str(),
it->second.first->debugName().c_str(), v->getType()->debugName().c_str()); it->second.first->debugName().c_str(), v->getType()->debugName().c_str());
llvm::Value* val = v->getValue();
llvm_phi->addIncoming(v->getValue(), llvm_exit_blocks[b->predecessors[j]]); llvm_phi->addIncoming(v->getValue(), llvm_exit_blocks[b->predecessors[j]]);
} }
...@@ -783,14 +857,13 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -783,14 +857,13 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc
ASSERT(it->second.first == v->getType(), ""); ASSERT(it->second.first == v->getType(), "");
llvm_phi->addIncoming(v->getValue(), osr_unbox_block_end); llvm_phi->addIncoming(v->getValue(), osr_unbox_block_end);
} }
InternedString is_defined_name = getIsDefinedName(it->first, source->getInternedStrings());
} }
for (auto t : phi_args) { for (auto t : phi_args) {
std::get<0>(t)->addIncoming(std::get<1>(t), std::get<2>(t)); std::get<0>(t)->addIncoming(std::get<1>(t), std::get<2>(t));
} }
} }
// deallocate/dereference memory
for (CFGBlock* b : source->cfg->blocks) { for (CFGBlock* b : source->cfg->blocks) {
if (ending_symbol_tables[b] == NULL) if (ending_symbol_tables[b] == NULL)
continue; continue;
......
...@@ -291,6 +291,7 @@ private: ...@@ -291,6 +291,7 @@ private:
llvm::BasicBlock* curblock; llvm::BasicBlock* curblock;
IREmitterImpl emitter; IREmitterImpl emitter;
// symbol_table tracks which (non-global) python variables are bound to which CompilerVariables
SymbolTable symbol_table; SymbolTable symbol_table;
std::unordered_map<CFGBlock*, llvm::BasicBlock*>& entry_blocks; std::unordered_map<CFGBlock*, llvm::BasicBlock*>& entry_blocks;
CFGBlock* myblock; CFGBlock* myblock;
...@@ -883,6 +884,7 @@ private: ...@@ -883,6 +884,7 @@ private:
return closure->getattr(emitter, getEmptyOpInfo(unw_info), &node->id.str(), false); return closure->getattr(emitter, getEmptyOpInfo(unw_info), &node->id.str(), false);
} else { } else {
// vst is one of {FAST, CLOSURE, NAME}
if (symbol_table.find(node->id) == symbol_table.end()) { if (symbol_table.find(node->id) == symbol_table.end()) {
// classdefs have different scoping rules than functions: // classdefs have different scoping rules than functions:
if (vst == ScopeInfo::VarScopeType::NAME) { if (vst == ScopeInfo::VarScopeType::NAME) {
...@@ -1225,6 +1227,12 @@ private: ...@@ -1225,6 +1227,12 @@ private:
cur = val; cur = val;
} }
// whether a Python variable FOO might be undefined or not is determined by whether the corresponding is_defined_FOO
// variable is present in our symbol table. If it is, then it *might* be undefined. If it isn't, then it either is
// definitely defined, or definitely isn't.
//
// to check whether a variable is in our symbol table, call _getFake with allow_missing = true and check whether the
// result is NULL.
CompilerVariable* _getFake(InternedString name, bool allow_missing = false) { CompilerVariable* _getFake(InternedString name, bool allow_missing = false) {
assert(name.str()[0] == '!'); assert(name.str()[0] == '!');
auto it = symbol_table.find(name); auto it = symbol_table.find(name);
...@@ -1243,6 +1251,7 @@ private: ...@@ -1243,6 +1251,7 @@ private:
return rtn; return rtn;
} }
// only updates symbol_table if we're *not* setting a global
void _doSet(InternedString name, CompilerVariable* val, UnwindInfo unw_info) { void _doSet(InternedString name, CompilerVariable* val, UnwindInfo unw_info) {
assert(name.str() != "None"); assert(name.str() != "None");
...@@ -1702,7 +1711,7 @@ private: ...@@ -1702,7 +1711,7 @@ private:
// that case asking it to convert to itself ends up just being an incvref // that case asking it to convert to itself ends up just being an incvref
// and doesn't end up emitting an incref+decref pair. // and doesn't end up emitting an incref+decref pair.
// This could also be handled by casting from the CompilerVariable to // This could also be handled by casting from the CompilerVariable to
// ConcreteCOmpilerVariable, but this way feels a little more robust to me. // ConcreteCompilerVariable, but this way feels a little more robust to me.
ConcreteCompilerType* opt_rtn_type = irstate->getReturnType(); ConcreteCompilerType* opt_rtn_type = irstate->getReturnType();
if (irstate->getReturnType()->llvmType() == val->getConcreteType()->llvmType()) if (irstate->getReturnType()->llvmType() == val->getConcreteType()->llvmType())
opt_rtn_type = val->getConcreteType(); opt_rtn_type = val->getConcreteType();
...@@ -2060,9 +2069,9 @@ private: ...@@ -2060,9 +2069,9 @@ private:
SourceInfo* source = irstate->getSourceInfo(); SourceInfo* source = irstate->getSourceInfo();
ScopeInfo* scope_info = irstate->getScopeInfo(); ScopeInfo* scope_info = irstate->getScopeInfo();
// Additional names to remove; remove them after iteration is done to new mess up the iterators // Additional names to remove; remove them after iteration is done to not mess up the iterators
std::vector<InternedString> also_remove; std::vector<InternedString> also_remove;
for (SymbolTable::iterator it = symbol_table.begin(); it != symbol_table.end();) { for (auto it = symbol_table.begin(); it != symbol_table.end();) {
if (allowableFakeEndingSymbol(it->first)) { if (allowableFakeEndingSymbol(it->first)) {
++it; ++it;
continue; continue;
...@@ -2073,7 +2082,7 @@ private: ...@@ -2073,7 +2082,7 @@ private:
if (!source->liveness->isLiveAtEnd(it->first, myblock)) { if (!source->liveness->isLiveAtEnd(it->first, myblock)) {
// printf("%s dead at end of %d; grabbed = %d, %d vrefs\n", it->first.c_str(), myblock->idx, // printf("%s dead at end of %d; grabbed = %d, %d vrefs\n", it->first.c_str(), myblock->idx,
// it->second->isGrabbed(), it->second->getVrefs()); // it->second->isGrabbed(), it->second->getVrefs());
also_remove.push_back(getIsDefinedName(it->first)); also_remove.push_back(getIsDefinedName(it->first));
it->second->decvref(emitter); it->second->decvref(emitter);
...@@ -2208,6 +2217,8 @@ public: ...@@ -2208,6 +2217,8 @@ public:
return EndingState(st, phi_st, curblock); return EndingState(st, phi_st, curblock);
} }
// 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.
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) || source->phis->isRequiredAfter(it->first, myblock)) {
ASSERT(it->second->isGrabbed(), "%s", it->first.c_str()); ASSERT(it->second->isGrabbed(), "%s", it->first.c_str());
...@@ -2344,12 +2355,24 @@ public: ...@@ -2344,12 +2355,24 @@ public:
} }
void run(const CFGBlock* block) override { void run(const CFGBlock* block) override {
if (VERBOSITY("irgenerator") >= 1) { // print starting symbol table
printf(" %d init:", block->idx);
for (auto it = symbol_table.begin(); it != symbol_table.end(); ++it)
printf(" %s", it->first.c_str());
printf("\n");
}
for (int i = 0; i < block->body.size(); i++) { for (int i = 0; i < block->body.size(); i++) {
if (state == DEAD) if (state == DEAD)
break; break;
assert(state != FINISHED); assert(state != FINISHED);
doStmt(block->body[i], UnwindInfo(block->body[i], NULL)); doStmt(block->body[i], UnwindInfo(block->body[i], NULL));
} }
if (VERBOSITY("irgenerator") >= 1) { // print ending symbol table
printf(" %d fini:", block->idx);
for (auto it = symbol_table.begin(); it != symbol_table.end(); ++it)
printf(" %s", it->first.c_str());
printf("\n");
}
} }
void doSafePoint() override { emitter.getBuilder()->CreateCall(g.funcs.allowGLReadPreemption); } void doSafePoint() override { emitter.getBuilder()->CreateCall(g.funcs.allowGLReadPreemption); }
......
...@@ -95,10 +95,14 @@ public: ...@@ -95,10 +95,14 @@ public:
ParamNames* getParamNames() { return param_names; } ParamNames* getParamNames() { return param_names; }
}; };
// turns CFGBlocks into LLVM IR
class IRGenerator { class IRGenerator {
private: private:
public: public:
struct EndingState { struct EndingState {
// symbol_table records which Python variables are bound to what CompilerVariables at the end of this block.
// phi_symbol_table records the ones that will need to be `phi'd.
// both only record non-globals.
SymbolTable* symbol_table; SymbolTable* symbol_table;
ConcreteSymbolTable* phi_symbol_table; ConcreteSymbolTable* phi_symbol_table;
llvm::BasicBlock* ending_block; llvm::BasicBlock* ending_block;
...@@ -113,7 +117,7 @@ public: ...@@ -113,7 +117,7 @@ public:
virtual void giveLocalSymbol(InternedString name, CompilerVariable* var) = 0; virtual void giveLocalSymbol(InternedString name, CompilerVariable* var) = 0;
virtual void copySymbolsFrom(SymbolTable* st) = 0; virtual void copySymbolsFrom(SymbolTable* st) = 0;
virtual void run(const CFGBlock* block) = 0; virtual void run(const CFGBlock* block) = 0; // primary entry point
virtual EndingState getEndingSymbolTable() = 0; virtual EndingState getEndingSymbolTable() = 0;
virtual void doSafePoint() = 0; virtual void doSafePoint() = 0;
virtual void addFrameStackmapArgs(PatchpointInfo* pp, AST_stmt* current_stmt, virtual void addFrameStackmapArgs(PatchpointInfo* pp, AST_stmt* current_stmt,
......
...@@ -118,7 +118,7 @@ enum AST_TYPE { ...@@ -118,7 +118,7 @@ enum AST_TYPE {
DictComp = 15, DictComp = 15,
Set = 43, Set = 43,
Ellipsis = 87, Ellipsis = 87,
Expression = 88, Expression = 88, // like Module, but used for eval.
// Pseudo-nodes that are specific to this compiler: // Pseudo-nodes that are specific to this compiler:
Branch = 200, Branch = 200,
...@@ -1007,14 +1007,14 @@ class AST_LangPrimitive : public AST_expr { ...@@ -1007,14 +1007,14 @@ class AST_LangPrimitive : public AST_expr {
public: public:
enum Opcodes { enum Opcodes {
ISINSTANCE, ISINSTANCE,
LANDINGPAD, LANDINGPAD, // grabs the info about the last raised exception
LOCALS, LOCALS,
GET_ITER, GET_ITER,
IMPORT_FROM, IMPORT_FROM,
IMPORT_NAME, IMPORT_NAME,
IMPORT_STAR, IMPORT_STAR,
NONE, NONE,
NONZERO, NONZERO, // determines whether something is "true" for purposes of `if'
SET_EXC_INFO, SET_EXC_INFO,
UNCACHE_EXC_INFO, UNCACHE_EXC_INFO,
} opcode; } opcode;
......
...@@ -36,7 +36,8 @@ void CFGBlock::connectTo(CFGBlock* successor, bool allow_backedge) { ...@@ -36,7 +36,8 @@ void CFGBlock::connectTo(CFGBlock* successor, bool allow_backedge) {
if (!allow_backedge) { if (!allow_backedge) {
assert(this->idx >= 0); assert(this->idx >= 0);
ASSERT(successor->idx == -1 || successor->idx > this->idx, "edge from %d to %d", this->idx, successor->idx); ASSERT(successor->idx == -1 || successor->idx > this->idx, "edge from %d (%s) to %d (%s)", this->idx,
this->info, successor->idx, successor->info);
} }
// assert(successors.count(successor) == 0); // assert(successors.count(successor) == 0);
// assert(successor->predecessors.count(this) == 0); // assert(successor->predecessors.count(this) == 0);
...@@ -53,16 +54,123 @@ void CFGBlock::unconnectFrom(CFGBlock* successor) { ...@@ -53,16 +54,123 @@ void CFGBlock::unconnectFrom(CFGBlock* successor) {
successor->predecessors.end()); successor->predecessors.end());
} }
void CFGBlock::print() {
printf("Block %d", idx);
if (info)
printf(" '%s'", info);
printf("; Predecessors:");
for (int j = 0; j < predecessors.size(); j++) {
printf(" %d", predecessors[j]->idx);
}
printf(" Successors:");
for (int j = 0; j < successors.size(); j++) {
printf(" %d", successors[j]->idx);
}
printf("\n");
PrintVisitor pv(4);
for (int j = 0; j < body.size(); j++) {
printf(" ");
body[j]->accept(&pv);
printf("\n");
}
}
static const std::string RETURN_NAME("#rtnval"); static const std::string RETURN_NAME("#rtnval");
// The various reasons why a `finally' block (or similar, eg. a `with' exit block) might get entered.
// this has to go outside CFGVisitor b/c why_values can't go inside it.
enum Why : int8_t {
FALLTHROUGH, // i.e. normal control flow
CONTINUE,
BREAK,
RETURN,
EXCEPTION,
};
static const Why why_values[] = { FALLTHROUGH, CONTINUE, BREAK, RETURN, EXCEPTION };
class CFGVisitor : public ASTVisitor { class CFGVisitor : public ASTVisitor {
// ---------- Types ----------
private:
/* Explanation of ContInfo and ExcBlockInfo:
*
* While generating the CFG, we need to know what to do if we:
* 1. hit a `continue'
* 2. hit a `break'
* 3. hit a `return'
* 4. raise an exception
*
* We call these "continuations", because they're what we "continue on to" after these conditions occur.
*
* Various control flow constructs affect each of these:
* - `for' and `while' affect (1-2).
* - `try/except' affects (4).
* - `try/finally' and `with' affect all four.
*
* Each of these take effect only within some chunk of code. So, notionally, we keep a stack for each of (1-4) whose
* _top_ value says what to do if that condition occurs. The top of the continue-stack points to the block to jump
* to if we hit a `continue', etc.
*
* For example, when we enter a loop, we push a pointer to the head of the loop onto the continue-stack, and a
* pointer to the code after the loop onto the break-stack. When we visit a `break' in the loop body, we emit a jump
* to the top of the break-stack, which is the end of the loop. After we finish visiting the loop body, we pop the
* break- & continue-stacks, restoring our old state (maybe we were inside another loop, for example).
*
* It's more complicated in practice, because:
*
* 1. When we jump to a `finally' block, we must tell it *why* we jumped to it. After the `finally' block finishes,
* it uses this info to resume what we were doing before we entered it (returning, raising an exception, etc).
*
* 2. When we jump to a `except' block, we must record three pieces of information about the exception (its type,
* value, and traceback).
*
* So instead of four stacks of block pointers, instead we have two stacks:
* - `continuations', a stack of ContInfos, for `continue', `break', and `return'
* - `exc_handlers', a stack of ExcBlockInfos, for exceptions
*
* Read the comments in ContInfo & ExcBlockInfo for more information.
*/
struct ContInfo {
// where to jump to if a continue, break, or return happens respectively
CFGBlock* continue_dest, *break_dest, *return_dest;
// true if this continuation needs to know the reason why we entered it. `finally' blocks use this info to
// determine how to resume execution after they finish.
bool say_why;
// bit-vector tracking all reasons Why we ever might enter this continuation. is only updated/used if `say_why'
// is true. when we emit a jump to this continuation for reason w, we set the bit (did_why & (1 << w)). this is
// used when emitting `finally' blocks to determine which continuation-cases to emit.
int did_why;
// name of the variable to store the reason Why we jumped in.
InternedString why_name;
ContInfo(CFGBlock* continue_dest, CFGBlock* break_dest, CFGBlock* return_dest, bool say_why,
InternedString why_name)
: continue_dest(continue_dest), break_dest(break_dest), return_dest(return_dest), say_why(say_why),
did_why(0), why_name(why_name) {}
};
struct ExcBlockInfo {
// where to jump in case of an exception
CFGBlock* exc_dest;
// variable names to store the exception (type, value, traceback) in
InternedString exc_type_name, exc_value_name, exc_traceback_name;
};
// ---------- Member fields ----------
private: private:
SourceInfo* source; SourceInfo* source;
// `root_type' is the type of the root of the AST tree that we are turning
// into a CFG. Used when we find a "return" to check that we're inside a
// function (otherwise we SyntaxError).
AST_TYPE::AST_TYPE root_type; AST_TYPE::AST_TYPE root_type;
FutureFlags future_flags; FutureFlags future_flags;
CFG* cfg; CFG* cfg;
CFGBlock* curblock; CFGBlock* curblock;
ScopingAnalysis* scoping_analysis; ScopingAnalysis* scoping_analysis;
std::vector<ContInfo> continuations;
std::vector<ExcBlockInfo> exc_handlers;
public: public:
CFGVisitor(SourceInfo* source, AST_TYPE::AST_TYPE root_type, FutureFlags future_flags, CFGVisitor(SourceInfo* source, AST_TYPE::AST_TYPE root_type, FutureFlags future_flags,
...@@ -74,32 +182,12 @@ public: ...@@ -74,32 +182,12 @@ public:
} }
~CFGVisitor() { ~CFGVisitor() {
assert(regions.size() == 0); assert(continuations.size() == 0);
assert(exc_handlers.size() == 0); assert(exc_handlers.size() == 0);
} }
// ---------- private methods ----------
private: private:
enum Why : int8_t {
FALLTHROUGH,
CONTINUE,
BREAK,
RETURN,
EXCEPTION,
};
// My first thought is to call this BlockInfo, but this is separate from the idea of cfg blocks.
struct RegionInfo {
CFGBlock* continue_dest, *break_dest, *return_dest;
bool say_why;
int did_why;
InternedString why_name;
RegionInfo(CFGBlock* continue_dest, CFGBlock* break_dest, CFGBlock* return_dest, bool say_why,
InternedString why_name)
: continue_dest(continue_dest), break_dest(break_dest), return_dest(return_dest), say_why(say_why),
did_why(0), why_name(why_name) {}
};
template <typename T> InternedString internString(T&& s) { template <typename T> InternedString internString(T&& s) {
return source->getInternedStrings().get(std::forward<T>(s)); return source->getInternedStrings().get(std::forward<T>(s));
} }
...@@ -109,48 +197,32 @@ private: ...@@ -109,48 +197,32 @@ private:
return name; return name;
} }
std::vector<RegionInfo> regions; AST_Name* makeLoad(InternedString id, AST* node) { return makeName(id, AST_TYPE::Load, node->lineno); }
struct ExcBlockInfo { void pushLoopContinuation(CFGBlock* continue_dest, CFGBlock* break_dest) {
CFGBlock* exc_dest;
InternedString exc_type_name, exc_value_name, exc_traceback_name;
};
std::vector<ExcBlockInfo> exc_handlers;
void pushLoopRegion(CFGBlock* continue_dest, CFGBlock* break_dest) {
assert(continue_dest assert(continue_dest
!= break_dest); // I guess this doesn't have to be true, but validates passing say_why=false != break_dest); // I guess this doesn't have to be true, but validates passing say_why=false
regions.emplace_back(continue_dest, break_dest, nullptr, false, internString("")); continuations.emplace_back(continue_dest, break_dest, nullptr, false, internString(""));
} }
void pushFinallyRegion(CFGBlock* finally_block, InternedString why_name) { void pushFinallyContinuation(CFGBlock* finally_block, InternedString why_name) {
regions.emplace_back(finally_block, finally_block, finally_block, true, why_name); continuations.emplace_back(finally_block, finally_block, finally_block, true, why_name);
} }
void popRegion() { regions.pop_back(); } void popContinuation() { continuations.pop_back(); }
// XXX get rid of this
void pushReturnRegion(CFGBlock* return_dest) {
regions.emplace_back(nullptr, nullptr, return_dest, false, internString(""));
}
void doReturn(AST_expr* value) { void doReturn(AST_expr* value) {
assert(value); assert(value);
for (auto& region : llvm::make_range(regions.rbegin(), regions.rend())) { for (auto& cont : llvm::make_range(continuations.rbegin(), continuations.rend())) {
if (region.return_dest) { if (cont.return_dest) {
if (region.say_why) { if (cont.say_why) {
pushAssign(region.why_name, makeNum(Why::RETURN)); pushAssign(cont.why_name, makeNum(Why::RETURN));
region.did_why |= (1 << Why::RETURN); cont.did_why |= (1 << Why::RETURN);
} }
pushAssign(internString(RETURN_NAME), value); pushAssign(internString(RETURN_NAME), value);
pushJump(cont.return_dest);
AST_Jump* j = makeJump();
j->target = region.return_dest;
curblock->connectTo(region.return_dest);
push_back(j);
curblock = NULL;
return; return;
} }
} }
...@@ -164,18 +236,14 @@ private: ...@@ -164,18 +236,14 @@ private:
} }
void doContinue() { void doContinue() {
for (auto& region : llvm::make_range(regions.rbegin(), regions.rend())) { for (auto& cont : llvm::make_range(continuations.rbegin(), continuations.rend())) {
if (region.continue_dest) { if (cont.continue_dest) {
if (region.say_why) { if (cont.say_why) {
pushAssign(region.why_name, makeNum(Why::CONTINUE)); pushAssign(cont.why_name, makeNum(Why::CONTINUE));
region.did_why |= (1 << Why::CONTINUE); cont.did_why |= (1 << Why::CONTINUE);
} }
AST_Jump* j = makeJump(); pushJump(cont.continue_dest, true);
j->target = region.continue_dest;
curblock->connectTo(region.continue_dest, true);
push_back(j);
curblock = NULL;
return; return;
} }
} }
...@@ -184,18 +252,14 @@ private: ...@@ -184,18 +252,14 @@ private:
} }
void doBreak() { void doBreak() {
for (auto& region : llvm::make_range(regions.rbegin(), regions.rend())) { for (auto& cont : llvm::make_range(continuations.rbegin(), continuations.rend())) {
if (region.break_dest) { if (cont.break_dest) {
if (region.say_why) { if (cont.say_why) {
pushAssign(region.why_name, makeNum(Why::BREAK)); pushAssign(cont.why_name, makeNum(Why::BREAK));
region.did_why |= (1 << Why::BREAK); cont.did_why |= (1 << Why::BREAK);
} }
AST_Jump* j = makeJump(); pushJump(cont.break_dest, true);
j->target = region.break_dest;
curblock->connectTo(region.break_dest, true);
push_back(j);
curblock = NULL;
return; return;
} }
} }
...@@ -216,7 +280,7 @@ private: ...@@ -216,7 +280,7 @@ private:
auto name = nodeName(e); auto name = nodeName(e);
pushAssign(name, call); pushAssign(name, call);
return makeName(name, AST_TYPE::Load, e->lineno); return makeLoad(name, e);
} }
AST_Name* remapName(AST_Name* name) { return name; } AST_Name* remapName(AST_Name* name) { return name; }
...@@ -254,21 +318,13 @@ private: ...@@ -254,21 +318,13 @@ private:
pushAssign(iter_name, iter_call); pushAssign(iter_name, iter_call);
// TODO bad to save these like this? // TODO bad to save these like this?
AST_expr* hasnext_attr = makeLoadAttribute(makeName(iter_name, AST_TYPE::Load, node->lineno), AST_expr* hasnext_attr = makeLoadAttribute(makeLoad(iter_name, node), internString("__hasnext__"), true);
internString("__hasnext__"), true); AST_expr* next_attr = makeLoadAttribute(makeLoad(iter_name, node), internString("next"), true);
AST_expr* next_attr
= makeLoadAttribute(makeName(iter_name, AST_TYPE::Load, node->lineno), internString("next"), true);
AST_Jump* j;
CFGBlock* test_block = cfg->addBlock(); CFGBlock* test_block = cfg->addBlock();
test_block->info = "comprehension_test"; test_block->info = "comprehension_test";
// printf("Test block for comp %d is %d\n", i, test_block->idx); // printf("Test block for comp %d is %d\n", i, test_block->idx);
pushJump(test_block);
j = new AST_Jump();
j->target = test_block;
curblock->connectTo(test_block);
push_back(j);
curblock = test_block; curblock = test_block;
AST_expr* test_call = callNonzero(remapExpr(makeCall(hasnext_attr))); AST_expr* test_call = callNonzero(remapExpr(makeCall(hasnext_attr)));
...@@ -293,7 +349,7 @@ private: ...@@ -293,7 +349,7 @@ private:
curblock = body_block; curblock = body_block;
InternedString next_name(nodeName(next_attr)); InternedString next_name(nodeName(next_attr));
pushAssign(next_name, makeCall(next_attr)); pushAssign(next_name, makeCall(next_attr));
pushAssign(c->target, makeName(next_name, AST_TYPE::Load, node->lineno)); pushAssign(c->target, makeLoad(next_name, node));
for (AST_expr* if_condition : c->ifs) { for (AST_expr* if_condition : c->ifs) {
AST_expr* remapped = callNonzero(remapExpr(if_condition)); AST_expr* remapped = callNonzero(remapExpr(if_condition));
...@@ -315,10 +371,7 @@ private: ...@@ -315,10 +371,7 @@ private:
curblock->connectTo(body_continue); curblock->connectTo(body_continue);
curblock = body_tramp; curblock = body_tramp;
j = new AST_Jump(); pushJump(test_block, true);
j->target = test_block;
push_back(j);
curblock->connectTo(test_block, true);
curblock = body_continue; curblock = body_continue;
} }
...@@ -328,21 +381,15 @@ private: ...@@ -328,21 +381,15 @@ private:
assert((finished_block != NULL) == (i != 0)); assert((finished_block != NULL) == (i != 0));
if (finished_block) { if (finished_block) {
curblock = exit_block; curblock = exit_block;
j = new AST_Jump(); pushJump(finished_block, true);
j->target = finished_block;
curblock->connectTo(finished_block, true);
push_back(j);
} }
finished_block = test_block; finished_block = test_block;
curblock = body_end; curblock = body_end;
if (is_innermost) { if (is_innermost) {
push_back(makeExpr(applyComprehensionCall(node, makeName(rtn_name, AST_TYPE::Load, node->lineno)))); push_back(makeExpr(applyComprehensionCall(node, makeLoad(rtn_name, node))));
j = new AST_Jump(); pushJump(test_block, true);
j->target = test_block;
curblock->connectTo(test_block, true);
push_back(j);
assert(exit_blocks.size()); assert(exit_blocks.size());
curblock = exit_blocks[0]; curblock = exit_blocks[0];
...@@ -359,7 +406,7 @@ private: ...@@ -359,7 +406,7 @@ private:
// printf("Exit block for comp %d is %d\n", i, exit_blocks[i]->idx); // printf("Exit block for comp %d is %d\n", i, exit_blocks[i]->idx);
} }
return makeName(rtn_name, AST_TYPE::Load, node->lineno); return makeLoad(rtn_name, node);
} }
AST_expr* makeNum(int n) { AST_expr* makeNum(int n) {
...@@ -369,11 +416,15 @@ private: ...@@ -369,11 +416,15 @@ private:
return node; return node;
} }
AST_Jump* makeJump() { void pushJump(CFGBlock* target, bool allow_backedge = false) {
AST_Jump* rtn = new AST_Jump(); AST_Jump* rtn = new AST_Jump();
return rtn; rtn->target = target;
push_back(rtn);
curblock->connectTo(target, allow_backedge);
curblock = nullptr;
} }
// NB. can generate blocks, because callNonzero can
AST_Branch* makeBranch(AST_expr* test) { AST_Branch* makeBranch(AST_expr* test) {
AST_Branch* rtn = new AST_Branch(); AST_Branch* rtn = new AST_Branch();
rtn->test = callNonzero(test); rtn->test = callNonzero(test);
...@@ -382,6 +433,30 @@ private: ...@@ -382,6 +433,30 @@ private:
return rtn; return rtn;
} }
// NB. this can (but usually doesn't) generate new blocks, which is why we require `iftrue' and `iffalse' to be
// deferred, to avoid heisenbugs. of course, this doesn't allow these branches to be backedges, but that hasn't yet
// been necessary.
void pushBranch(AST_expr* test, CFGBlock* iftrue, CFGBlock* iffalse) {
assert(iftrue->idx == -1 && iffalse->idx == -1);
AST_Branch* branch = makeBranch(test);
branch->iftrue = iftrue;
branch->iffalse = iffalse;
curblock->connectTo(iftrue);
curblock->connectTo(iffalse);
push_back(branch);
curblock = nullptr;
}
void pushReraise(AST* node, InternedString exc_type_name, InternedString exc_value_name,
InternedString exc_traceback_name) {
auto raise = new AST_Raise();
raise->arg0 = makeLoad(exc_type_name, node);
raise->arg1 = makeLoad(exc_value_name, node);
raise->arg2 = makeLoad(exc_traceback_name, node);
push_back(raise);
curblock = nullptr;
}
AST_expr* makeLoadAttribute(AST_expr* base, InternedString name, bool clsonly) { AST_expr* makeLoadAttribute(AST_expr* base, InternedString name, bool clsonly) {
AST_expr* rtn; AST_expr* rtn;
if (clsonly) { if (clsonly) {
...@@ -412,28 +487,34 @@ private: ...@@ -412,28 +487,34 @@ private:
} }
AST_Call* makeCall(AST_expr* func, AST_expr* arg0) { AST_Call* makeCall(AST_expr* func, AST_expr* arg0) {
AST_Call* call = new AST_Call(); auto call = makeCall(func);
call->args.push_back(arg0); call->args.push_back(arg0);
call->starargs = NULL;
call->kwargs = NULL;
call->func = func;
call->col_offset = func->col_offset;
call->lineno = func->lineno;
return call; return call;
} }
AST_Call* makeCall(AST_expr* func, AST_expr* arg0, AST_expr* arg1) { AST_Call* makeCall(AST_expr* func, AST_expr* arg0, AST_expr* arg1) {
AST_Call* call = new AST_Call(); auto call = makeCall(func);
call->args.push_back(arg0); call->args.push_back(arg0);
call->args.push_back(arg1); call->args.push_back(arg1);
call->starargs = NULL;
call->kwargs = NULL;
call->func = func;
call->col_offset = func->col_offset;
call->lineno = func->lineno;
return call; return call;
} }
AST_Call* makeCall(AST_expr* func, AST_expr* arg0, AST_expr* arg1, AST_expr* arg2) {
auto call = makeCall(func);
call->args.push_back(arg0);
call->args.push_back(arg1);
call->args.push_back(arg2);
return call;
}
AST_Compare* makeCompare(AST_TYPE::AST_TYPE oper, AST_expr* left, AST_expr* right) {
auto compare = new AST_Compare();
compare->ops.push_back(AST_TYPE::Eq);
compare->left = left;
compare->comparators.push_back(right);
return compare;
}
void pushAssign(AST_expr* target, AST_expr* val) { void pushAssign(AST_expr* target, AST_expr* val) {
AST_Assign* assign = new AST_Assign(); AST_Assign* assign = new AST_Assign();
assign->value = val; assign->value = val;
...@@ -495,7 +576,7 @@ private: ...@@ -495,7 +576,7 @@ private:
InternedString tmp_name = nodeName(target, "", i); InternedString tmp_name = nodeName(target, "", i);
new_target->elts.push_back(makeName(tmp_name, AST_TYPE::Store, target->lineno)); new_target->elts.push_back(makeName(tmp_name, AST_TYPE::Store, target->lineno));
pushAssign((*elts)[i], makeName(tmp_name, AST_TYPE::Load, target->lineno)); pushAssign((*elts)[i], makeLoad(tmp_name, target));
} }
} else { } else {
RELEASE_ASSERT(0, "%d", target->type); RELEASE_ASSERT(0, "%d", target->type);
...@@ -516,11 +597,10 @@ private: ...@@ -516,11 +597,10 @@ private:
return stmt; return stmt;
} }
InternedString nodeName(AST* node) { InternedString nodeName(AST* node) {
char buf[40]; char buf[40];
snprintf(buf, 40, "#%p", node); int bytes = snprintf(buf, 40, "#%p", node);
assert(bytes < 40); // double-check
// Uncomment this line to check to make sure we never reuse the same nodeName() accidentally. // Uncomment this line to check to make sure we never reuse the same nodeName() accidentally.
// This check is potentially too expensive for even debug mode, since it never frees any memory. // This check is potentially too expensive for even debug mode, since it never frees any memory.
// #define VALIDATE_FAKE_NAMES // #define VALIDATE_FAKE_NAMES
...@@ -537,13 +617,15 @@ private: ...@@ -537,13 +617,15 @@ private:
InternedString nodeName(AST* node, const std::string& suffix) { InternedString nodeName(AST* node, const std::string& suffix) {
char buf[50]; char buf[50];
snprintf(buf, 50, "#%p_%s", node, suffix.c_str()); int bytes = snprintf(buf, 50, "#%p_%s", node, suffix.c_str());
assert(bytes < 50); // double-check
return internString(std::string(buf)); return internString(std::string(buf));
} }
InternedString nodeName(AST* node, const std::string& suffix, int idx) { InternedString nodeName(AST* node, const std::string& suffix, int idx) {
char buf[50]; char buf[50];
snprintf(buf, 50, "#%p_%s_%d", node, suffix.c_str(), idx); int bytes = snprintf(buf, 50, "#%p_%s_%d", node, suffix.c_str(), idx);
assert(bytes < 50); // double-check
return internString(std::string(buf)); return internString(std::string(buf));
} }
...@@ -639,26 +721,19 @@ private: ...@@ -639,26 +721,19 @@ private:
} }
curblock = crit_break_block; curblock = crit_break_block;
AST_Jump* j = new AST_Jump(); pushJump(exit_block);
j->target = exit_block;
push_back(j);
crit_break_block->connectTo(exit_block);
curblock = next_block; curblock = next_block;
} }
AST_expr* final_val = remapExpr(node->values[node->values.size() - 1]); AST_expr* final_val = remapExpr(node->values[node->values.size() - 1]);
pushAssign(name, final_val); pushAssign(name, final_val);
pushJump(exit_block);
AST_Jump* j = new AST_Jump();
push_back(j);
j->target = exit_block;
curblock->connectTo(exit_block);
cfg->placeBlock(exit_block); cfg->placeBlock(exit_block);
curblock = exit_block; curblock = exit_block;
return makeName(name, AST_TYPE::Load, node->lineno); return makeLoad(name, node);
} }
AST_expr* remapCall(AST_Call* node) { AST_expr* remapCall(AST_Call* node) {
...@@ -738,7 +813,7 @@ private: ...@@ -738,7 +813,7 @@ private:
pushAssign(name, val); pushAssign(name, val);
AST_Branch* br = new AST_Branch(); AST_Branch* br = new AST_Branch();
br->test = callNonzero(makeName(name, AST_TYPE::Load, node->lineno)); br->test = callNonzero(makeLoad(name, node));
push_back(br); push_back(br);
CFGBlock* was_block = curblock; CFGBlock* was_block = curblock;
...@@ -751,25 +826,18 @@ private: ...@@ -751,25 +826,18 @@ private:
br->iftrue = next_block; br->iftrue = next_block;
curblock = crit_break_block; curblock = crit_break_block;
AST_Jump* j = new AST_Jump(); pushJump(exit_block);
j->target = exit_block;
push_back(j);
crit_break_block->connectTo(exit_block);
curblock = next_block; curblock = next_block;
left = _dup(right); left = _dup(right);
} }
AST_Jump* j = new AST_Jump(); pushJump(exit_block);
push_back(j);
j->target = exit_block;
curblock->connectTo(exit_block);
cfg->placeBlock(exit_block); cfg->placeBlock(exit_block);
curblock = exit_block; curblock = exit_block;
return makeName(name, AST_TYPE::Load, node->lineno); return makeLoad(name, node);
} }
} }
...@@ -816,7 +884,7 @@ private: ...@@ -816,7 +884,7 @@ private:
loop->target = c->target; loop->target = c->target;
if (i == 0) { if (i == 0) {
loop->iter = makeName(first_generator_name, AST_TYPE::Load, node->lineno); loop->iter = makeLoad(first_generator_name, node);
} else { } else {
loop->iter = c->iter; loop->iter = c->iter;
} }
...@@ -847,50 +915,38 @@ private: ...@@ -847,50 +915,38 @@ private:
call->starargs = NULL; call->starargs = NULL;
call->kwargs = NULL; call->kwargs = NULL;
call->func = makeName(func_name, AST_TYPE::Load, node->lineno); call->func = makeLoad(func_name, node);
call->args.push_back(first); call->args.push_back(first);
return call; return call;
}; };
AST_expr* remapIfExp(AST_IfExp* node) { AST_expr* remapIfExp(AST_IfExp* node) {
InternedString rtn_name = nodeName(node); InternedString rtn_name = nodeName(node);
CFGBlock* iftrue = cfg->addDeferredBlock();
CFGBlock* iffalse = cfg->addDeferredBlock();
CFGBlock* exit_block = cfg->addDeferredBlock();
AST_Branch* br = new AST_Branch(); pushBranch(remapExpr(node->test), iftrue, iffalse);
br->col_offset = node->col_offset;
br->lineno = node->lineno;
br->test = callNonzero(remapExpr(node->test));
push_back(br);
CFGBlock* starting_block = curblock; // if true block
cfg->placeBlock(iftrue);
CFGBlock* iftrue = cfg->addBlock();
iftrue->info = "iftrue";
br->iftrue = iftrue;
starting_block->connectTo(iftrue);
curblock = iftrue; curblock = iftrue;
iftrue->info = "iftrue";
pushAssign(rtn_name, remapExpr(node->body)); pushAssign(rtn_name, remapExpr(node->body));
AST_Jump* jtrue = new AST_Jump(); pushJump(exit_block);
push_back(jtrue);
CFGBlock* endtrue = curblock;
CFGBlock* iffalse = cfg->addBlock(); // if false block
iffalse->info = "iffalse"; cfg->placeBlock(iffalse);
br->iffalse = iffalse;
starting_block->connectTo(iffalse);
curblock = iffalse; curblock = iffalse;
iffalse->info = "iffalse";
pushAssign(rtn_name, remapExpr(node->orelse)); pushAssign(rtn_name, remapExpr(node->orelse));
AST_Jump* jfalse = new AST_Jump(); pushJump(exit_block);
push_back(jfalse);
CFGBlock* endfalse = curblock; // exit block
cfg->placeBlock(exit_block);
CFGBlock* exit_block = cfg->addBlock();
jtrue->target = exit_block;
endtrue->connectTo(exit_block);
jfalse->target = exit_block;
endfalse->connectTo(exit_block);
curblock = exit_block; curblock = exit_block;
return makeName(rtn_name, AST_TYPE::Load, node->lineno); return makeLoad(rtn_name, node);
} }
AST_expr* remapIndex(AST_Index* node) { AST_expr* remapIndex(AST_Index* node) {
...@@ -1012,9 +1068,14 @@ private: ...@@ -1012,9 +1068,14 @@ private:
push_back(makeExpr(new AST_LangPrimitive(AST_LangPrimitive::UNCACHE_EXC_INFO))); push_back(makeExpr(new AST_LangPrimitive(AST_LangPrimitive::UNCACHE_EXC_INFO)));
return makeName(node_name, AST_TYPE::Load, node->lineno); return makeLoad(node_name, node);
} }
// Flattens a nested expression into a flat one, emitting instructions &
// generating temporary variables as needed.
//
// If `wrap_with_assign` is true, it will always return a temporary
// variable.
AST_expr* remapExpr(AST_expr* node, bool wrap_with_assign = true) { AST_expr* remapExpr(AST_expr* node, bool wrap_with_assign = true) {
if (node == NULL) if (node == NULL)
return NULL; return NULL;
...@@ -1100,15 +1161,65 @@ private: ...@@ -1100,15 +1161,65 @@ private:
RELEASE_ASSERT(0, "%d", node->type); RELEASE_ASSERT(0, "%d", node->type);
} }
// this is the part that actually generates temporaries & assigns to them.
if (wrap_with_assign && (rtn->type != AST_TYPE::Name || ast_cast<AST_Name>(rtn)->id.str()[0] != '#')) { if (wrap_with_assign && (rtn->type != AST_TYPE::Name || ast_cast<AST_Name>(rtn)->id.str()[0] != '#')) {
InternedString name = nodeName(node); InternedString name = nodeName(node);
pushAssign(name, rtn); pushAssign(name, rtn);
return makeName(name, AST_TYPE::Load, node->lineno); return makeLoad(name, node);
} else { } else {
return rtn; return rtn;
} }
} }
// helper for visit_{tryfinally,with}
CFGBlock* makeFinallyCont(Why reason, AST_expr* whyexpr, CFGBlock* then_block) {
CFGBlock* otherwise = cfg->addDeferredBlock();
otherwise->info = "finally_otherwise";
pushBranch(makeCompare(AST_TYPE::Eq, whyexpr, makeNum(reason)), then_block, otherwise);
cfg->placeBlock(otherwise);
return otherwise;
}
// Helper for visit_with. Performs the appropriate exit from a with-block, according to the value of `why'.
// NB. `exit_block' is only used if `why' is FALLTHROUGH.
void exitFinally(AST* node, Why why, CFGBlock* exit_block = nullptr) {
switch (why) {
case Why::RETURN:
doReturn(makeLoad(internString(RETURN_NAME), node));
break;
case Why::BREAK:
doBreak();
break;
case Why::CONTINUE:
doContinue();
break;
case Why::FALLTHROUGH:
assert(exit_block);
pushJump(exit_block);
break;
case Why::EXCEPTION:
assert(why != Why::EXCEPTION); // not handled here
break;
}
assert(curblock == nullptr);
}
// helper for visit_{with,tryfinally}. Generates a branch testing the value of `whyexpr' against `why', and
// performing the appropriate exit from the with-block if they are equal.
// NB. `exit_block' is only used if `why' is FALLTHROUGH.
void exitFinallyIf(AST* node, Why why, InternedString whyname, CFGBlock* exit_block = nullptr) {
CFGBlock* do_exit = cfg->addDeferredBlock();
do_exit->info = "with_exit_if";
CFGBlock* otherwise = makeFinallyCont(why, makeLoad(whyname, node), do_exit);
cfg->placeBlock(do_exit);
curblock = do_exit;
exitFinally(node, why, exit_block);
curblock = otherwise;
}
// ---------- public methods ----------
public: public:
void push_back(AST_stmt* node) { void push_back(AST_stmt* node) {
assert(node->type != AST_TYPE::Invoke); assert(node->type != AST_TYPE::Invoke);
...@@ -1145,6 +1256,7 @@ public: ...@@ -1145,6 +1256,7 @@ public:
if (asgn->targets[0]->type == AST_TYPE::Name) { if (asgn->targets[0]->type == AST_TYPE::Name) {
AST_Name* target = ast_cast<AST_Name>(asgn->targets[0]); AST_Name* target = ast_cast<AST_Name>(asgn->targets[0]);
if (target->id.str()[0] != '#') { if (target->id.str()[0] != '#') {
// assigning to a non-temporary
#ifndef NDEBUG #ifndef NDEBUG
if (!(asgn->value->type == AST_TYPE::Name && ast_cast<AST_Name>(asgn->value)->id.str()[0] == '#') if (!(asgn->value->type == AST_TYPE::Name && ast_cast<AST_Name>(asgn->value)->id.str()[0] == '#')
&& asgn->value->type != AST_TYPE::Str && asgn->value->type != AST_TYPE::Num) { && asgn->value->type != AST_TYPE::Str && asgn->value->type != AST_TYPE::Num) {
...@@ -1160,8 +1272,12 @@ public: ...@@ -1160,8 +1272,12 @@ public:
// Assigning from one temporary name to another: // Assigning from one temporary name to another:
curblock->push_back(node); curblock->push_back(node);
return; return;
} else if (asgn->value->type == AST_TYPE::Num || asgn->value->type == AST_TYPE::Str) { } else if (asgn->value->type == AST_TYPE::Num || asgn->value->type == AST_TYPE::Str
|| (asgn->value->type == AST_TYPE::Name
&& ast_cast<AST_Name>(asgn->value)->id.str().compare("None") == 0)) {
// Assigning to a temporary name from an expression that can't throw: // Assigning to a temporary name from an expression that can't throw:
// NB. `None' can't throw in Python, because it's hardcoded
// (seriously, try reassigning "None" in CPython).
curblock->push_back(node); curblock->push_back(node);
return; return;
} }
...@@ -1206,10 +1322,7 @@ public: ...@@ -1206,10 +1322,7 @@ public:
exc_asgn->value = new AST_LangPrimitive(AST_LangPrimitive::LANDINGPAD); exc_asgn->value = new AST_LangPrimitive(AST_LangPrimitive::LANDINGPAD);
curblock->push_back(exc_asgn); curblock->push_back(exc_asgn);
AST_Jump* j = new AST_Jump(); pushJump(exc_info.exc_dest);
j->target = exc_info.exc_dest;
curblock->push_back(j);
curblock->connectTo(exc_info.exc_dest);
if (is_raise) if (is_raise)
curblock = NULL; curblock = NULL;
...@@ -1289,7 +1402,7 @@ public: ...@@ -1289,7 +1402,7 @@ public:
if (a->asname.str().size() == 0) { if (a->asname.str().size() == 0) {
// No asname, so load the top-level module into the name // No asname, so load the top-level module into the name
// (e.g., for `import os.path`, loads the os module into `os`) // (e.g., for `import os.path`, loads the os module into `os`)
pushAssign(internString(getTopModule(a->name.str())), makeName(tmpname, AST_TYPE::Load, node->lineno)); pushAssign(internString(getTopModule(a->name.str())), makeLoad(tmpname, node));
} else { } else {
// If there is an asname, get the bottom-level module by // If there is an asname, get the bottom-level module by
// getting the attributes and load it into asname. // getting the attributes and load it into asname.
...@@ -1303,11 +1416,11 @@ public: ...@@ -1303,11 +1416,11 @@ public:
l = r + 1; l = r + 1;
continue; continue;
} }
pushAssign(tmpname, new AST_Attribute(makeName(tmpname, AST_TYPE::Load, node->lineno), pushAssign(tmpname, new AST_Attribute(makeLoad(tmpname, node), AST_TYPE::Load,
AST_TYPE::Load, internString(a->name.str().substr(l, r)))); internString(a->name.str().substr(l, r))));
l = r + 1; l = r + 1;
} while (l < a->name.str().size()); } while (l < a->name.str().size());
pushAssign(a->asname, makeName(tmpname, AST_TYPE::Load, node->lineno)); pushAssign(a->asname, makeLoad(tmpname, node));
} }
} }
...@@ -1348,7 +1461,7 @@ public: ...@@ -1348,7 +1461,7 @@ public:
AST_LangPrimitive* import_star = new AST_LangPrimitive(AST_LangPrimitive::IMPORT_STAR); AST_LangPrimitive* import_star = new AST_LangPrimitive(AST_LangPrimitive::IMPORT_STAR);
import_star->lineno = node->lineno; import_star->lineno = node->lineno;
import_star->col_offset = node->col_offset; import_star->col_offset = node->col_offset;
import_star->args.push_back(makeName(tmp_module_name, AST_TYPE::Load, node->lineno)); import_star->args.push_back(makeLoad(tmp_module_name, node));
AST_Expr* import_star_expr = new AST_Expr(); AST_Expr* import_star_expr = new AST_Expr();
import_star_expr->value = import_star; import_star_expr->value = import_star;
...@@ -1358,13 +1471,12 @@ public: ...@@ -1358,13 +1471,12 @@ public:
AST_LangPrimitive* import_from = new AST_LangPrimitive(AST_LangPrimitive::IMPORT_FROM); AST_LangPrimitive* import_from = new AST_LangPrimitive(AST_LangPrimitive::IMPORT_FROM);
import_from->lineno = node->lineno; import_from->lineno = node->lineno;
import_from->col_offset = node->col_offset; import_from->col_offset = node->col_offset;
import_from->args.push_back(makeName(tmp_module_name, AST_TYPE::Load, node->lineno)); import_from->args.push_back(makeLoad(tmp_module_name, node));
import_from->args.push_back(new AST_Str(a->name.str())); import_from->args.push_back(new AST_Str(a->name.str()));
InternedString tmp_import_name = nodeName(a); InternedString tmp_import_name = nodeName(a);
pushAssign(tmp_import_name, import_from); pushAssign(tmp_import_name, import_from);
pushAssign(a->asname.str().size() ? a->asname : a->name, pushAssign(a->asname.str().size() ? a->asname : a->name, makeLoad(tmp_import_name, node));
makeName(tmp_import_name, AST_TYPE::Load, node->lineno));
} }
} }
...@@ -1407,17 +1519,10 @@ public: ...@@ -1407,17 +1519,10 @@ public:
CFGBlock* unreachable = cfg->addBlock(); CFGBlock* unreachable = cfg->addBlock();
unreachable->info = "unreachable"; unreachable->info = "unreachable";
curblock->connectTo(unreachable); pushJump(unreachable);
AST_Jump* j = new AST_Jump();
j->target = unreachable;
push_back(j);
curblock = unreachable; curblock = unreachable;
AST_Jump* j2 = new AST_Jump(); pushJump(unreachable, true);
j2->target = unreachable;
push_back(j2);
curblock->connectTo(unreachable, true);
curblock = iftrue; curblock = iftrue;
...@@ -1460,9 +1565,9 @@ public: ...@@ -1460,9 +1565,9 @@ public:
AST_Name* n = ast_cast<AST_Name>(node->target); AST_Name* n = ast_cast<AST_Name>(node->target);
assert(n->ctx_type == AST_TYPE::Store); assert(n->ctx_type == AST_TYPE::Store);
InternedString n_name(nodeName(n)); InternedString n_name(nodeName(n));
pushAssign(n_name, makeName(n->id, AST_TYPE::Load, node->lineno)); pushAssign(n_name, makeLoad(n->id, node));
remapped_target = n; remapped_target = n;
remapped_lhs = makeName(n_name, AST_TYPE::Load, node->lineno); remapped_lhs = makeLoad(n_name, node);
break; break;
} }
case AST_TYPE::Subscript: { case AST_TYPE::Subscript: {
...@@ -1522,7 +1627,7 @@ public: ...@@ -1522,7 +1627,7 @@ public:
InternedString node_name(nodeName(node)); InternedString node_name(nodeName(node));
pushAssign(node_name, binop); pushAssign(node_name, binop);
pushAssign(remapped_target, makeName(node_name, AST_TYPE::Load, node->lineno)); pushAssign(remapped_target, makeLoad(node_name, node));
return true; return true;
} }
...@@ -1637,6 +1742,8 @@ public: ...@@ -1637,6 +1742,8 @@ public:
} }
bool visit_return(AST_Return* node) override { bool visit_return(AST_Return* node) override {
// returns are allowed in functions (of course), and also in eval("...") strings - basically, eval strings get
// an implicit `return'. root_type is AST_TYPE::Expression when we're compiling an eval string.
if (root_type != AST_TYPE::FunctionDef && root_type != AST_TYPE::Lambda && root_type != AST_TYPE::Expression) { if (root_type != AST_TYPE::FunctionDef && root_type != AST_TYPE::Lambda && root_type != AST_TYPE::Expression) {
raiseExcHelper(SyntaxError, "'return' outside function"); raiseExcHelper(SyntaxError, "'return' outside function");
} }
...@@ -1644,10 +1751,7 @@ public: ...@@ -1644,10 +1751,7 @@ public:
if (!curblock) if (!curblock)
return true; return true;
AST_expr* value = remapExpr(node->value); doReturn(node->value ? remapExpr(node->value) : makeLoad(internString("None"), node));
if (value == NULL)
value = makeName(internString("None"), AST_TYPE::Load, node->lineno);
doReturn(value);
return true; return true;
} }
...@@ -1674,10 +1778,7 @@ public: ...@@ -1674,10 +1778,7 @@ public:
node->body[i]->accept(this); node->body[i]->accept(this);
} }
if (curblock) { if (curblock) {
AST_Jump* jtrue = new AST_Jump(); pushJump(exit);
push_back(jtrue);
jtrue->target = exit;
curblock->connectTo(exit);
} }
CFGBlock* iffalse = cfg->addBlock(); CFGBlock* iffalse = cfg->addBlock();
...@@ -1690,10 +1791,7 @@ public: ...@@ -1690,10 +1791,7 @@ public:
node->orelse[i]->accept(this); node->orelse[i]->accept(this);
} }
if (curblock) { if (curblock) {
AST_Jump* jfalse = new AST_Jump(); pushJump(exit);
push_back(jfalse);
jfalse->target = exit;
curblock->connectTo(exit);
} }
if (exit->predecessors.size() == 0) { if (exit->predecessors.size() == 0) {
...@@ -1732,11 +1830,7 @@ public: ...@@ -1732,11 +1830,7 @@ public:
CFGBlock* test_block = cfg->addBlock(); CFGBlock* test_block = cfg->addBlock();
test_block->info = "while_test"; test_block->info = "while_test";
pushJump(test_block);
AST_Jump* j = makeJump();
push_back(j);
j->target = test_block;
curblock->connectTo(test_block);
curblock = test_block; curblock = test_block;
AST_Branch* br = makeBranch(remapExpr(node->test)); AST_Branch* br = makeBranch(remapExpr(node->test));
...@@ -1747,7 +1841,7 @@ public: ...@@ -1747,7 +1841,7 @@ public:
// but we don't want it to be placed until after the orelse. // but we don't want it to be placed until after the orelse.
CFGBlock* end = cfg->addDeferredBlock(); CFGBlock* end = cfg->addDeferredBlock();
end->info = "while_exit"; end->info = "while_exit";
pushLoopRegion(test_block, end); pushLoopContinuation(test_block, end);
CFGBlock* body = cfg->addBlock(); CFGBlock* body = cfg->addBlock();
body->info = "while_body_start"; body->info = "while_body_start";
...@@ -1758,13 +1852,9 @@ public: ...@@ -1758,13 +1852,9 @@ public:
for (int i = 0; i < node->body.size(); i++) { for (int i = 0; i < node->body.size(); i++) {
node->body[i]->accept(this); node->body[i]->accept(this);
} }
if (curblock) { if (curblock)
AST_Jump* jbody = makeJump(); pushJump(test_block, true);
push_back(jbody); popContinuation();
jbody->target = test_block;
curblock->connectTo(test_block, true);
}
popRegion();
CFGBlock* orelse = cfg->addBlock(); CFGBlock* orelse = cfg->addBlock();
orelse->info = "while_orelse_start"; orelse->info = "while_orelse_start";
...@@ -1774,12 +1864,8 @@ public: ...@@ -1774,12 +1864,8 @@ public:
for (int i = 0; i < node->orelse.size(); i++) { for (int i = 0; i < node->orelse.size(); i++) {
node->orelse[i]->accept(this); node->orelse[i]->accept(this);
} }
if (curblock) { if (curblock)
AST_Jump* jend = makeJump(); pushJump(end);
push_back(jend);
jend->target = end;
curblock->connectTo(end);
}
curblock = end; curblock = end;
cfg->placeBlock(end); cfg->placeBlock(end);
...@@ -1804,18 +1890,12 @@ public: ...@@ -1804,18 +1890,12 @@ public:
InternedString itername = internString(itername_buf); InternedString itername = internString(itername_buf);
pushAssign(itername, iter_call); pushAssign(itername, iter_call);
auto hasnext_attr = [&]() { auto hasnext_attr =
return makeLoadAttribute(makeName(itername, AST_TYPE::Load, node->lineno), internString("__hasnext__"), [&]() { return makeLoadAttribute(makeLoad(itername, node), internString("__hasnext__"), true); };
true); AST_expr* next_attr = makeLoadAttribute(makeLoad(itername, node), internString("next"), true);
};
AST_expr* next_attr
= makeLoadAttribute(makeName(itername, AST_TYPE::Load, node->lineno), internString("next"), true);
CFGBlock* test_block = cfg->addBlock(); CFGBlock* test_block = cfg->addBlock();
AST_Jump* jump_to_test = makeJump(); pushJump(test_block);
jump_to_test->target = test_block;
push_back(jump_to_test);
curblock->connectTo(test_block);
curblock = test_block; curblock = test_block;
AST_expr* test_call = makeCall(hasnext_attr()); AST_expr* test_call = makeCall(hasnext_attr());
...@@ -1834,30 +1914,23 @@ public: ...@@ -1834,30 +1914,23 @@ public:
CFGBlock* else_block = cfg->addDeferredBlock(); CFGBlock* else_block = cfg->addDeferredBlock();
curblock = test_true; curblock = test_true;
// TODO simplify the breaking of these crit edges? // TODO simplify the breaking of these crit edges?
AST_Jump* test_true_jump = makeJump(); pushJump(loop_block);
test_true_jump->target = loop_block;
push_back(test_true_jump);
test_true->connectTo(loop_block);
curblock = test_false; curblock = test_false;
AST_Jump* test_false_jump = makeJump(); pushJump(else_block);
test_false_jump->target = else_block;
push_back(test_false_jump);
test_false->connectTo(else_block);
pushLoopRegion(test_block, end_block); pushLoopContinuation(test_block, end_block);
curblock = loop_block; curblock = loop_block;
InternedString next_name(nodeName(next_attr)); InternedString next_name(nodeName(next_attr));
pushAssign(next_name, makeCall(next_attr)); pushAssign(next_name, makeCall(next_attr));
pushAssign(node->target, makeName(next_name, AST_TYPE::Load, node->lineno)); pushAssign(node->target, makeLoad(next_name, node));
for (int i = 0; i < node->body.size(); i++) { for (int i = 0; i < node->body.size(); i++) {
node->body[i]->accept(this); node->body[i]->accept(this);
} }
popRegion(); popContinuation();
if (curblock) { if (curblock) {
AST_expr* end_call = makeCall((hasnext_attr())); AST_expr* end_call = makeCall((hasnext_attr()));
...@@ -1872,16 +1945,10 @@ public: ...@@ -1872,16 +1945,10 @@ public:
curblock->connectTo(end_false); curblock->connectTo(end_false);
curblock = end_true; curblock = end_true;
AST_Jump* end_true_jump = makeJump(); pushJump(loop_block, true);
end_true_jump->target = loop_block;
push_back(end_true_jump);
end_true->connectTo(loop_block, true);
curblock = end_false; curblock = end_false;
AST_Jump* end_false_jump = makeJump(); pushJump(else_block);
end_false_jump->target = else_block;
push_back(end_false_jump);
end_false->connectTo(else_block);
} }
cfg->placeBlock(else_block); cfg->placeBlock(else_block);
...@@ -1890,12 +1957,8 @@ public: ...@@ -1890,12 +1957,8 @@ public:
for (int i = 0; i < node->orelse.size(); i++) { for (int i = 0; i < node->orelse.size(); i++) {
node->orelse[i]->accept(this); node->orelse[i]->accept(this);
} }
if (curblock) { if (curblock)
AST_Jump* else_jump = makeJump(); pushJump(end_block);
push_back(else_jump);
else_jump->target = end_block;
curblock->connectTo(end_block);
}
cfg->placeBlock(end_block); cfg->placeBlock(end_block);
curblock = end_block; curblock = end_block;
...@@ -1956,12 +2019,8 @@ public: ...@@ -1956,12 +2019,8 @@ public:
} }
CFGBlock* join_block = cfg->addDeferredBlock(); CFGBlock* join_block = cfg->addDeferredBlock();
if (curblock) { if (curblock)
AST_Jump* j = new AST_Jump(); pushJump(join_block);
j->target = join_block;
push_back(j);
curblock->connectTo(join_block);
}
if (exc_handler_block->predecessors.size() == 0) { if (exc_handler_block->predecessors.size() == 0) {
delete exc_handler_block; delete exc_handler_block;
...@@ -1970,7 +2029,7 @@ public: ...@@ -1970,7 +2029,7 @@ public:
curblock = exc_handler_block; curblock = exc_handler_block;
// TODO This is supposed to be exc_type_name (value doesn't matter for checking matches) // TODO This is supposed to be exc_type_name (value doesn't matter for checking matches)
AST_expr* exc_obj = makeName(exc_value_name, AST_TYPE::Load, node->lineno); AST_expr* exc_obj = makeLoad(exc_value_name, node);
bool caught_all = false; bool caught_all = false;
for (AST_ExceptHandler* exc_handler : node->handlers) { for (AST_ExceptHandler* exc_handler : node->handlers) {
...@@ -2003,9 +2062,9 @@ public: ...@@ -2003,9 +2062,9 @@ public:
} }
AST_LangPrimitive* set_exc_info = new AST_LangPrimitive(AST_LangPrimitive::SET_EXC_INFO); AST_LangPrimitive* set_exc_info = new AST_LangPrimitive(AST_LangPrimitive::SET_EXC_INFO);
set_exc_info->args.push_back(makeName(exc_type_name, AST_TYPE::Load, node->lineno)); set_exc_info->args.push_back(makeLoad(exc_type_name, node));
set_exc_info->args.push_back(makeName(exc_value_name, AST_TYPE::Load, node->lineno)); set_exc_info->args.push_back(makeLoad(exc_value_name, node));
set_exc_info->args.push_back(makeName(exc_traceback_name, AST_TYPE::Load, node->lineno)); set_exc_info->args.push_back(makeLoad(exc_traceback_name, node));
push_back(makeExpr(set_exc_info)); push_back(makeExpr(set_exc_info));
if (exc_handler->name) { if (exc_handler->name) {
...@@ -2017,10 +2076,7 @@ public: ...@@ -2017,10 +2076,7 @@ public:
} }
if (curblock) { if (curblock) {
AST_Jump* j = new AST_Jump(); pushJump(join_block);
j->target = join_block;
push_back(j);
curblock->connectTo(join_block);
} }
if (exc_next) { if (exc_next) {
...@@ -2033,9 +2089,9 @@ public: ...@@ -2033,9 +2089,9 @@ public:
if (!caught_all) { if (!caught_all) {
AST_Raise* raise = new AST_Raise(); AST_Raise* raise = new AST_Raise();
raise->arg0 = makeName(exc_type_name, AST_TYPE::Load, node->lineno); raise->arg0 = makeLoad(exc_type_name, node);
raise->arg1 = makeName(exc_value_name, AST_TYPE::Load, node->lineno); raise->arg1 = makeLoad(exc_value_name, node);
raise->arg2 = makeName(exc_traceback_name, AST_TYPE::Load, node->lineno); raise->arg2 = makeLoad(exc_traceback_name, node);
push_back(raise); push_back(raise);
curblock = NULL; curblock = NULL;
} }
...@@ -2061,7 +2117,7 @@ public: ...@@ -2061,7 +2117,7 @@ public:
exc_handlers.push_back({ exc_handler_block, exc_type_name, exc_value_name, exc_traceback_name }); exc_handlers.push_back({ exc_handler_block, exc_type_name, exc_value_name, exc_traceback_name });
CFGBlock* finally_block = cfg->addDeferredBlock(); CFGBlock* finally_block = cfg->addDeferredBlock();
pushFinallyRegion(finally_block, exc_why_name); pushFinallyContinuation(finally_block, exc_why_name);
for (AST_stmt* subnode : node->body) { for (AST_stmt* subnode : node->body) {
subnode->accept(this); subnode->accept(this);
...@@ -2069,17 +2125,14 @@ public: ...@@ -2069,17 +2125,14 @@ public:
exc_handlers.pop_back(); exc_handlers.pop_back();
int did_why = regions.back().did_why; // bad to just reach in like this int did_why = continuations.back().did_why; // bad to just reach in like this
popRegion(); // finally region popContinuation(); // finally continuation
if (curblock) { if (curblock) {
// assign the exc_*_name variables to tell irgen that they won't be undefined? // assign the exc_*_name variables to tell irgen that they won't be undefined?
// have an :UNDEF() langprimitive to not have to do any loading there? // have an :UNDEF() langprimitive to not have to do any loading there?
pushAssign(exc_why_name, makeNum(Why::FALLTHROUGH)); pushAssign(exc_why_name, makeNum(Why::FALLTHROUGH));
AST_Jump* j = new AST_Jump(); pushJump(finally_block);
j->target = finally_block;
push_back(j);
curblock->connectTo(finally_block);
} }
if (exc_handler_block->predecessors.size() == 0) { if (exc_handler_block->predecessors.size() == 0) {
...@@ -2087,13 +2140,8 @@ public: ...@@ -2087,13 +2140,8 @@ public:
} else { } else {
cfg->placeBlock(exc_handler_block); cfg->placeBlock(exc_handler_block);
curblock = exc_handler_block; curblock = exc_handler_block;
pushAssign(exc_why_name, makeNum(Why::EXCEPTION)); pushAssign(exc_why_name, makeNum(Why::EXCEPTION));
pushJump(finally_block);
AST_Jump* j = new AST_Jump();
j->target = finally_block;
push_back(j);
curblock->connectTo(finally_block);
} }
cfg->placeBlock(finally_block); cfg->placeBlock(finally_block);
...@@ -2104,99 +2152,19 @@ public: ...@@ -2104,99 +2152,19 @@ public:
} }
if (curblock) { if (curblock) {
// TODO: these 4 cases are pretty copy-pasted from each other: if (did_why & (1 << Why::RETURN))
if (did_why & (1 << Why::RETURN)) { exitFinallyIf(node, Why::RETURN, exc_why_name);
CFGBlock* doreturn = cfg->addBlock(); if (did_why & (1 << Why::BREAK))
CFGBlock* otherwise = cfg->addBlock(); exitFinallyIf(node, Why::BREAK, exc_why_name);
if (did_why & (1 << Why::CONTINUE))
AST_Compare* compare = new AST_Compare(); exitFinallyIf(node, Why::CONTINUE, exc_why_name);
compare->ops.push_back(AST_TYPE::Eq);
compare->left = makeName(exc_why_name, AST_TYPE::Load, node->lineno);
compare->comparators.push_back(makeNum(Why::RETURN));
AST_Branch* br = new AST_Branch();
br->test = callNonzero(compare);
br->iftrue = doreturn;
br->iffalse = otherwise;
curblock->connectTo(doreturn);
curblock->connectTo(otherwise);
push_back(br);
curblock = doreturn;
doReturn(makeName(internString(RETURN_NAME), AST_TYPE::Load, node->lineno));
curblock = otherwise;
}
if (did_why & (1 << Why::BREAK)) {
CFGBlock* doreturn = cfg->addBlock();
CFGBlock* otherwise = cfg->addBlock();
AST_Compare* compare = new AST_Compare(); CFGBlock* reraise = cfg->addDeferredBlock();
compare->ops.push_back(AST_TYPE::Eq); CFGBlock* noexc = makeFinallyCont(Why::EXCEPTION, makeLoad(exc_why_name, node), reraise);
compare->left = makeName(exc_why_name, AST_TYPE::Load, node->lineno);
compare->comparators.push_back(makeNum(Why::BREAK));
AST_Branch* br = new AST_Branch();
br->test = callNonzero(compare);
br->iftrue = doreturn;
br->iffalse = otherwise;
curblock->connectTo(doreturn);
curblock->connectTo(otherwise);
push_back(br);
curblock = doreturn;
doBreak();
curblock = otherwise;
}
if (did_why & (1 << Why::CONTINUE)) {
CFGBlock* doreturn = cfg->addBlock();
CFGBlock* otherwise = cfg->addBlock();
AST_Compare* compare = new AST_Compare();
compare->ops.push_back(AST_TYPE::Eq);
compare->left = makeName(exc_why_name, AST_TYPE::Load, node->lineno);
compare->comparators.push_back(makeNum(Why::CONTINUE));
AST_Branch* br = new AST_Branch();
br->test = callNonzero(compare);
br->iftrue = doreturn;
br->iffalse = otherwise;
curblock->connectTo(doreturn);
curblock->connectTo(otherwise);
push_back(br);
curblock = doreturn;
doContinue();
curblock = otherwise;
}
AST_Compare* compare = new AST_Compare();
compare->ops.push_back(AST_TYPE::Eq);
compare->left = makeName(exc_why_name, AST_TYPE::Load, node->lineno);
compare->comparators.push_back(makeNum(Why::EXCEPTION));
AST_Branch* br = new AST_Branch();
br->test = callNonzero(compare);
CFGBlock* reraise = cfg->addBlock();
CFGBlock* noexc = cfg->addBlock();
br->iftrue = reraise;
br->iffalse = noexc;
curblock->connectTo(reraise);
curblock->connectTo(noexc);
push_back(br);
cfg->placeBlock(reraise);
curblock = reraise; curblock = reraise;
AST_Raise* raise = new AST_Raise(); pushReraise(node, exc_type_name, exc_value_name, exc_traceback_name);
raise->arg0 = makeName(exc_type_name, AST_TYPE::Load, node->lineno);
raise->arg1 = makeName(exc_value_name, AST_TYPE::Load, node->lineno);
raise->arg2 = makeName(exc_traceback_name, AST_TYPE::Load, node->lineno);
push_back(raise);
curblock = noexc; curblock = noexc;
} }
...@@ -2205,105 +2173,137 @@ public: ...@@ -2205,105 +2173,137 @@ public:
} }
bool visit_with(AST_With* node) override { bool visit_with(AST_With* node) override {
char ctxmgrname_buf[80]; // see https://www.python.org/dev/peps/pep-0343/
snprintf(ctxmgrname_buf, 80, "#ctxmgr_%p", node); // section "Specification: the 'with' Statement"
InternedString ctxmgrname = internString(ctxmgrname_buf); // which contains pseudocode for what this implements:
char exitname_buf[80]; //
snprintf(exitname_buf, 80, "#exit_%p", node); // mgr = (EXPR)
InternedString exitname = internString(exitname_buf); // exit = type(mgr).__exit__ # not calling it yet
// value = type(mgr).__enter__(mgr)
// exc = True
// try:
// VAR = value
// BLOCK
// except:
// exc = False
// if not exit(mgr, *sys.exc_info()):
// raise
// finally:
// if exc:
// exit(mgr, None, None, None)
//
// Unfortunately, this pseudocode isn't *quite* correct. We don't actually call type(mgr).__exit__ and
// type(mgr).__enter__; rather, we use Python's "special method lookup rules" to find the appropriate method.
// See https://docs.python.org/2/reference/datamodel.html#new-style-special-lookup. This is one reason we can't
// just translate this into AST_Try{Except,Finally} nodes and recursively visit those. (If there are other
// reasons, I've forgotten them.)
InternedString ctxmgrname = nodeName(node, "ctxmgr");
InternedString exitname = nodeName(node, "exit");
InternedString whyname = nodeName(node, "why");
InternedString exc_type_name = nodeName(node, "exc_type");
InternedString exc_value_name = nodeName(node, "exc_value");
InternedString exc_traceback_name = nodeName(node, "exc_traceback");
InternedString nonename = internString("None"); InternedString nonename = internString("None");
CFGBlock* exit_block = cfg->addDeferredBlock();
exit_block->info = "with_exit";
pushAssign(ctxmgrname, remapExpr(node->context_expr)); pushAssign(ctxmgrname, remapExpr(node->context_expr));
AST_expr* enter // TODO(rntz): for some reason, in the interpreter (but not the JIT), this is looking up __exit__ on the
= makeLoadAttribute(makeName(ctxmgrname, AST_TYPE::Load, node->lineno), internString("__enter__"), true); // instance rather than the class. See test/tests/with_ctxclass_instance_attrs.py.
AST_expr* exit AST_expr* exit = makeLoadAttribute(makeLoad(ctxmgrname, node), internString("__exit__"), true);
= makeLoadAttribute(makeName(ctxmgrname, AST_TYPE::Load, node->lineno), internString("__exit__"), true);
pushAssign(exitname, exit); pushAssign(exitname, exit);
enter = remapExpr(makeCall(enter));
if (node->optional_vars) { // Oddly, this acces to __enter__ doesn't suffer from the same bug. Perhaps it has something to do with
// __enter__ being called immediately?
AST_expr* enter = makeLoadAttribute(makeLoad(ctxmgrname, node), internString("__enter__"), true);
enter = remapExpr(makeCall(enter));
if (node->optional_vars)
pushAssign(node->optional_vars, enter); pushAssign(node->optional_vars, enter);
} else { else
push_back(makeExpr(enter)); push_back(makeExpr(enter));
}
CFGBlock* continue_dest = NULL, * break_dest = NULL;
if (regions.size()) {
continue_dest = cfg->addDeferredBlock();
continue_dest->info = "with_continue";
break_dest = cfg->addDeferredBlock();
break_dest->info = "with_break";
pushLoopRegion(continue_dest, break_dest); // push continuations
} CFGBlock* finally_block = cfg->addDeferredBlock();
finally_block->info = "with_finally";
pushFinallyContinuation(finally_block, whyname);
CFGBlock* return_dest = cfg->addDeferredBlock(); CFGBlock* exc_block = cfg->addDeferredBlock();
return_dest->info = "with_return"; exc_block->info = "with_exc";
pushReturnRegion(return_dest); exc_handlers.push_back({ exc_block, exc_type_name, exc_value_name, exc_traceback_name });
for (int i = 0; i < node->body.size(); i++) { for (int i = 0; i < node->body.size(); i++) {
node->body[i]->accept(this); node->body[i]->accept(this);
} }
popRegion(); // for the retrun exc_handlers.pop_back();
int finally_did_why = continuations.back().did_why;
AST_Call* exit_call = makeCall(makeName(exitname, AST_TYPE::Load, node->lineno)); popContinuation();
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
push_back(makeExpr(exit_call));
CFGBlock* orig_ending_block = curblock;
if (continue_dest) {
popRegion(); // for the loop region
if (continue_dest->predecessors.size() == 0) {
delete continue_dest;
} else {
curblock = continue_dest;
AST_Call* exit_call = makeCall(makeName(exitname, AST_TYPE::Load, node->lineno));
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
push_back(makeExpr(exit_call));
cfg->placeBlock(continue_dest);
doContinue();
}
if (break_dest->predecessors.size() == 0) {
delete break_dest;
} else {
curblock = break_dest;
AST_Call* exit_call = makeCall(makeName(exitname, AST_TYPE::Load, node->lineno));
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno));
push_back(makeExpr(exit_call));
cfg->placeBlock(break_dest); if (curblock) {
doBreak(); // The try-suite finished as normal; jump to the finally block.
} pushAssign(whyname, makeNum(Why::FALLTHROUGH));
curblock = orig_ending_block; pushJump(finally_block);
} }
if (return_dest->predecessors.size() == 0) { // The exception-handling block
delete return_dest; if (exc_block->predecessors.size() == 0) {
// TODO(rntz): test for this case
delete exc_block;
} else { } else {
cfg->placeBlock(return_dest); cfg->placeBlock(exc_block);
curblock = return_dest; curblock = exc_block;
AST_Call* exit_call = makeCall(makeName(exitname, AST_TYPE::Load, node->lineno)); // call the context-manager's exit method
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno)); InternedString suppressname = nodeName(node, "suppress");
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno)); pushAssign(suppressname, makeCall(makeLoad(exitname, node), makeLoad(exc_type_name, node),
exit_call->args.push_back(makeName(nonename, AST_TYPE::Load, node->lineno)); makeLoad(exc_value_name, node), makeLoad(exc_traceback_name, node)));
push_back(makeExpr(exit_call));
// if it returns true, suppress the error and go to our exit block
doReturn(makeName(internString(RETURN_NAME), AST_TYPE::Load, node->lineno)); CFGBlock* reraise_block = cfg->addDeferredBlock();
curblock = orig_ending_block; reraise_block->info = "with_reraise";
// break potential critical edge
CFGBlock* exiter = cfg->addDeferredBlock();
exiter->info = "with_exiter";
pushBranch(makeLoad(suppressname, node), exiter, reraise_block);
cfg->placeBlock(exiter);
curblock = exiter;
pushJump(exit_block);
// otherwise, reraise the exception
cfg->placeBlock(reraise_block);
curblock = reraise_block;
pushReraise(node, exc_type_name, exc_value_name, exc_traceback_name);
}
// The finally block
if (finally_block->predecessors.size() == 0) {
// TODO(rntz): test for this case, "with foo: raise bar"
delete finally_block;
} else {
cfg->placeBlock(finally_block);
curblock = finally_block;
// call the context-manager's exit method, ignoring result
push_back(makeExpr(makeCall(makeLoad(exitname, node), makeLoad(nonename, node), makeLoad(nonename, node),
makeLoad(nonename, node))));
if (finally_did_why & (1 << Why::CONTINUE))
exitFinallyIf(node, Why::CONTINUE, whyname);
if (finally_did_why & (1 << Why::BREAK))
exitFinallyIf(node, Why::BREAK, whyname);
if (finally_did_why & (1 << Why::RETURN))
exitFinallyIf(node, Why::RETURN, whyname);
exitFinally(node, Why::FALLTHROUGH, exit_block);
}
if (exit_block->predecessors.size() == 0) {
// FIXME(rntz): does this ever happen?
// make a test for it!
delete exit_block;
} else {
cfg->placeBlock(exit_block);
curblock = exit_block;
} }
return true; return true;
...@@ -2313,30 +2313,8 @@ public: ...@@ -2313,30 +2313,8 @@ public:
void CFG::print() { void CFG::print() {
printf("CFG:\n"); printf("CFG:\n");
printf("%ld blocks\n", blocks.size()); printf("%ld blocks\n", blocks.size());
PrintVisitor* pv = new PrintVisitor(4); for (int i = 0; i < blocks.size(); i++)
for (int i = 0; i < blocks.size(); i++) { blocks[i]->print();
CFGBlock* b = blocks[i];
printf("Block %d", b->idx);
if (b->info)
printf(" '%s'", b->info);
printf("; Predecessors:");
for (int j = 0; j < b->predecessors.size(); j++) {
printf(" %d", b->predecessors[j]->idx);
}
printf(" Successors:");
for (int j = 0; j < b->successors.size(); j++) {
printf(" %d", b->successors[j]->idx);
}
printf("\n");
for (int j = 0; j < b->body.size(); j++) {
printf(" ");
b->body[j]->accept(pv);
printf("\n");
}
}
delete pv;
} }
CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body) { CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body) {
......
...@@ -56,6 +56,7 @@ public: ...@@ -56,6 +56,7 @@ public:
void unconnectFrom(CFGBlock* successor); void unconnectFrom(CFGBlock* successor);
void push_back(AST_stmt* node) { body.push_back(node); } void push_back(AST_stmt* node) { body.push_back(node); }
void print();
}; };
// Control Flow Graph // Control Flow Graph
...@@ -79,6 +80,9 @@ public: ...@@ -79,6 +80,9 @@ public:
return block; return block;
} }
// Creates a block which must be placed later, using placeBlock().
// Must be placed on same CFG it was created on.
// You can also safely delete it without placing it.
CFGBlock* addDeferredBlock() { CFGBlock* addDeferredBlock() {
CFGBlock* block = new CFGBlock(this, -1); CFGBlock* block = new CFGBlock(this, -1);
return block; return block;
......
...@@ -39,6 +39,8 @@ ...@@ -39,6 +39,8 @@
#define _CAT(A, B) A##B #define _CAT(A, B) A##B
#define CAT(A, B) _CAT(A, B) #define CAT(A, B) _CAT(A, B)
#define ARRAY_LEN(arr) (sizeof(arr) / sizeof((arr)[0]))
// GCC and clang handle always_inline very differently; // GCC and clang handle always_inline very differently;
// we mostly only care about it for the stdlib, so just remove the attributes // we mostly only care about it for the stdlib, so just remove the attributes
// if we're not in clang // if we're not in clang
......
...@@ -52,7 +52,9 @@ bool endswith(const std::string& s, const std::string& pattern); ...@@ -52,7 +52,9 @@ bool endswith(const std::string& s, const std::string& pattern);
void removeDirectoryIfExists(const std::string& path); void removeDirectoryIfExists(const std::string& path);
template <class T1, class T2> void compareKeyset(T1* lhs, T2* rhs) { // Checks that lhs and rhs, which are iterables of InternedStrings, have the
// same set of names in them.
template <class T1, class T2> bool sameKeyset(T1* lhs, T2* rhs) {
std::vector<InternedString> lv, rv; std::vector<InternedString> lv, rv;
for (typename T1::iterator it = lhs->begin(); it != lhs->end(); it++) { for (typename T1::iterator it = lhs->begin(); it != lhs->end(); it++) {
lv.push_back(it->first); lv.push_back(it->first);
...@@ -89,7 +91,7 @@ template <class T1, class T2> void compareKeyset(T1* lhs, T2* rhs) { ...@@ -89,7 +91,7 @@ template <class T1, class T2> void compareKeyset(T1* lhs, T2* rhs) {
} }
good = false; good = false;
} }
assert(good); return good;
} }
} }
......
...@@ -366,6 +366,9 @@ std::string BoxedModule::name() { ...@@ -366,6 +366,9 @@ std::string BoxedModule::name() {
} }
} }
// This mustn't throw; our IR generator generates calls to it without "invoke" even when there are exception handlers /
// finally-blocks in scope.
// TODO: should we use C++11 `noexcept' here?
extern "C" Box* boxCLFunction(CLFunction* f, BoxedClosure* closure, bool isGenerator, extern "C" Box* boxCLFunction(CLFunction* f, BoxedClosure* closure, bool isGenerator,
std::initializer_list<Box*> defaults) { std::initializer_list<Box*> defaults) {
if (closure) if (closure)
......
# fail-if: '-n' in EXTRA_JIT_ARGS or '-O' in EXTRA_JIT_ARGS
# we have an llvm codegen bug that this file triggers when we JIT
class TestException(Exception): class TestException(Exception):
pass pass
......
# expected: fail
# allow-warning: converting unicode literal to str # allow-warning: converting unicode literal to str
import sys import sys
......
...@@ -27,5 +27,6 @@ def f2(): ...@@ -27,5 +27,6 @@ def f2():
print 'here' print 'here'
except: except:
print 'impossible' print 'impossible'
print D
raise raise
f2() f2()
# fail-if: (('-O' in EXTRA_JIT_ARGS) or ('-n' in EXTRA_JIT_ARGS)) and 'release' not in IMAGE
def f(): def f():
try: try:
def foo(): return 0 def foo(): return 0
......
# expected: fail # fail-if: (('-O' in EXTRA_JIT_ARGS) or ('-n' in EXTRA_JIT_ARGS)) and 'release' not in IMAGE
def f(): def f():
C = 23 C = 23
try: try:
......
...@@ -5,7 +5,7 @@ class Mgr(object): ...@@ -5,7 +5,7 @@ class Mgr(object):
def __enter__(self): def __enter__(self):
print 'Mgr.__enter__ accessed' print 'Mgr.__enter__ accessed'
def enterer(*args): def enterer(*args):
print 'Mgr.__enter__%r called' % (args,) print 'Mgr.__enter__ called'
return 23 return 23
return enterer return enterer
...@@ -13,7 +13,7 @@ class Mgr(object): ...@@ -13,7 +13,7 @@ class Mgr(object):
def __exit__(self): def __exit__(self):
print 'Mgr.__exit__ accessed' print 'Mgr.__exit__ accessed'
def exiter(*args): def exiter(*args):
print 'Mgr.__exit__%r called' % (args,) print 'Mgr.__exit__ called'
return False return False
return exiter return exiter
......
# fail-if: (('-O' in EXTRA_JIT_ARGS) or ('-n' in EXTRA_JIT_ARGS)) and 'release' not in IMAGE
def f(): def f():
# originally this exposed a bug in our irgen phase, so even `with None` # this exposes a bug in our irgen phase, so even `with None` bugs out here;
# failed here; the bug happened before actual execution. Just to test more # the bug happens before actual execution. Just to test more things, though,
# things, though, we use an actual contextmanager here. # we use an actual contextmanager here.
with open('/dev/null'): with open('/dev/null'):
def foo(): def foo():
# raises a syntaxerror
pass pass
f() 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