Commit 9e30d6dc authored by Kevin Modzelewski's avatar Kevin Modzelewski

Merge pull request #398 from rntz/with-exceptions

in merge:
- with_ctxclass_instance_attrs now succeeds due to d1387d74
- merge ~conflict in LangPrimitive opcodes
parents 3c042a82 449cb458
...@@ -36,6 +36,7 @@ stdlib*.ll ...@@ -36,6 +36,7 @@ stdlib*.ll
oprofile_data oprofile_data
pprof.jit pprof.jit
tags tags
TAGS
*.pyc *.pyc
perf.data perf.data
......
...@@ -37,6 +37,9 @@ VERBOSE := 0 ...@@ -37,6 +37,9 @@ VERBOSE := 0
ENABLE_INTEL_JIT_EVENTS := 0 ENABLE_INTEL_JIT_EVENTS := 0
CTAGS := ctags
ETAGS := ctags-exuberant -e
# Setting this to 1 will set the Makefile to use binaries from the trunk # Setting this to 1 will set the Makefile to use binaries from the trunk
# directory, even if USE_TEST_LLVM is set to 1. # directory, even if USE_TEST_LLVM is set to 1.
# This is useful if clang isn't installed into the test directory, ex due # This is useful if clang isn't installed into the test directory, ex due
...@@ -408,7 +411,11 @@ all: pyston_dbg pyston_release pyston_prof ext_python ext_pyston unittests ...@@ -408,7 +411,11 @@ all: pyston_dbg pyston_release pyston_prof ext_python ext_pyston unittests
ALL_HEADERS := $(wildcard src/*/*.h) $(wildcard src/*/*/*.h) $(wildcard from_cpython/Include/*.h) ALL_HEADERS := $(wildcard src/*/*.h) $(wildcard src/*/*/*.h) $(wildcard from_cpython/Include/*.h)
tags: $(SRCS) $(OPTIONAL_SRCS) $(FROM_CPYTHON_SRCS) $(ALL_HEADERS) tags: $(SRCS) $(OPTIONAL_SRCS) $(FROM_CPYTHON_SRCS) $(ALL_HEADERS)
$(ECHO) Calculating tags... $(ECHO) Calculating tags...
$(VERB) ctags $^ $(VERB) $(CTAGS) $^
TAGS: $(SRCS) $(OPTIONAL_SRCS) $(FROM_CPYTHON_SRCS) $(ALL_HEADERS)
$(ECHO) Calculating TAGS...
$(VERB) $(ETAGS) $^
NON_ENTRY_OBJS := $(filter-out src/jit.o,$(OBJS)) NON_ENTRY_OBJS := $(filter-out src/jit.o,$(OBJS))
......
...@@ -511,7 +511,9 @@ private: ...@@ -511,7 +511,9 @@ private:
} }
} }
void visit_classdef(AST_ClassDef* node) override { void* visit_makeclass(AST_MakeClass* mkclass) override {
AST_ClassDef* node = mkclass->class_def;
for (auto d : node->decorator_list) { for (auto d : node->decorator_list) {
getType(d); getType(d);
} }
...@@ -521,9 +523,8 @@ private: ...@@ -521,9 +523,8 @@ private:
} }
// TODO should we speculate that classdefs will generally return a class? // TODO should we speculate that classdefs will generally return a class?
// CompilerType* t = typeFromClass(type_cls); // return typeFromClass(type_cls);
CompilerType* t = UNKNOWN; return UNKNOWN;
_doSet(scope_info->mangleName(node->name), t);
} }
void visit_delete(AST_Delete* node) override { void visit_delete(AST_Delete* node) override {
...@@ -551,7 +552,9 @@ private: ...@@ -551,7 +552,9 @@ private:
} }
} }
void visit_functiondef(AST_FunctionDef* node) override { void* visit_makefunction(AST_MakeFunction* mkfn) override {
AST_FunctionDef* node = mkfn->function_def;
for (auto d : node->decorator_list) { for (auto d : node->decorator_list) {
getType(d); getType(d);
} }
...@@ -563,7 +566,7 @@ private: ...@@ -563,7 +566,7 @@ private:
CompilerType* t = UNKNOWN; CompilerType* t = UNKNOWN;
if (node->decorator_list.empty()) if (node->decorator_list.empty())
t = typeFromClass(function_cls); t = typeFromClass(function_cls);
_doSet(scope_info->mangleName(node->name), t); return t;
} }
void visit_global(AST_Global* node) override {} void visit_global(AST_Global* node) override {}
......
...@@ -88,10 +88,8 @@ private: ...@@ -88,10 +88,8 @@ private:
Value visit_assign(AST_Assign* node); Value visit_assign(AST_Assign* node);
Value visit_binop(AST_BinOp* node); Value visit_binop(AST_BinOp* node);
Value visit_call(AST_Call* node); Value visit_call(AST_Call* node);
Value visit_classDef(AST_ClassDef* node);
Value visit_compare(AST_Compare* node); Value visit_compare(AST_Compare* node);
Value visit_delete(AST_Delete* node); Value visit_delete(AST_Delete* node);
Value visit_functionDef(AST_FunctionDef* node);
Value visit_global(AST_Global* node); Value visit_global(AST_Global* node);
Value visit_module(AST_Module* node); Value visit_module(AST_Module* node);
Value visit_print(AST_Print* node); Value visit_print(AST_Print* node);
...@@ -117,6 +115,8 @@ private: ...@@ -117,6 +115,8 @@ private:
Value visit_tuple(AST_Tuple* node); Value visit_tuple(AST_Tuple* node);
Value visit_yield(AST_Yield* node); Value visit_yield(AST_Yield* node);
Value visit_makeClass(AST_MakeClass* node);
Value visit_makeFunction(AST_MakeFunction* node);
// pseudo // pseudo
Value visit_augBinOp(AST_AugBinOp* node); Value visit_augBinOp(AST_AugBinOp* node);
...@@ -605,14 +605,10 @@ Value ASTInterpreter::visit_stmt(AST_stmt* node) { ...@@ -605,14 +605,10 @@ Value ASTInterpreter::visit_stmt(AST_stmt* node) {
return visit_assert((AST_Assert*)node); return visit_assert((AST_Assert*)node);
case AST_TYPE::Assign: case AST_TYPE::Assign:
return visit_assign((AST_Assign*)node); return visit_assign((AST_Assign*)node);
case AST_TYPE::ClassDef:
return visit_classDef((AST_ClassDef*)node);
case AST_TYPE::Delete: case AST_TYPE::Delete:
return visit_delete((AST_Delete*)node); return visit_delete((AST_Delete*)node);
case AST_TYPE::Expr: case AST_TYPE::Expr:
return visit_expr((AST_Expr*)node); return visit_expr((AST_Expr*)node);
case AST_TYPE::FunctionDef:
return visit_functionDef((AST_FunctionDef*)node);
case AST_TYPE::Pass: case AST_TYPE::Pass:
return Value(); // nothing todo return Value(); // nothing todo
case AST_TYPE::Print: case AST_TYPE::Print:
...@@ -690,7 +686,8 @@ Box* ASTInterpreter::createFunction(AST* node, AST_arguments* args, const std::v ...@@ -690,7 +686,8 @@ Box* ASTInterpreter::createFunction(AST* node, AST_arguments* args, const std::v
return boxCLFunction(cl, closure, is_generator, u.il); return boxCLFunction(cl, closure, is_generator, u.il);
} }
Value ASTInterpreter::visit_functionDef(AST_FunctionDef* node) { Value ASTInterpreter::visit_makeFunction(AST_MakeFunction* mkfn) {
AST_FunctionDef* node = mkfn->function_def;
AST_arguments* args = node->args; AST_arguments* args = node->args;
std::vector<Box*, StlCompatAllocator<Box*>> decorators; std::vector<Box*, StlCompatAllocator<Box*>> decorators;
...@@ -702,11 +699,11 @@ Value ASTInterpreter::visit_functionDef(AST_FunctionDef* node) { ...@@ -702,11 +699,11 @@ Value ASTInterpreter::visit_functionDef(AST_FunctionDef* node) {
for (int i = decorators.size() - 1; i >= 0; i--) for (int i = decorators.size() - 1; i >= 0; i--)
func = runtimeCall(decorators[i], ArgPassSpec(1), func, 0, 0, 0, 0); func = runtimeCall(decorators[i], ArgPassSpec(1), func, 0, 0, 0, 0);
doStore(source_info->mangleName(node->name), func); return Value(func);
return Value();
} }
Value ASTInterpreter::visit_classDef(AST_ClassDef* node) { Value ASTInterpreter::visit_makeClass(AST_MakeClass* mkclass) {
AST_ClassDef* node = mkclass->class_def;
ScopeInfo* scope_info = source_info->scoping->getScopeInfoForNode(node); ScopeInfo* scope_info = source_info->scoping->getScopeInfoForNode(node);
assert(scope_info); assert(scope_info);
...@@ -729,8 +726,7 @@ Value ASTInterpreter::visit_classDef(AST_ClassDef* node) { ...@@ -729,8 +726,7 @@ Value ASTInterpreter::visit_classDef(AST_ClassDef* node) {
for (int i = decorators.size() - 1; i >= 0; i--) for (int i = decorators.size() - 1; i >= 0; i--)
classobj = runtimeCall(decorators[i], ArgPassSpec(1), classobj, 0, 0, 0, 0); classobj = runtimeCall(decorators[i], ArgPassSpec(1), classobj, 0, 0, 0, 0);
doStore(source_info->mangleName(node->name), classobj); return Value(classobj);
return Value();
} }
Value ASTInterpreter::visit_raise(AST_Raise* node) { Value ASTInterpreter::visit_raise(AST_Raise* node) {
...@@ -896,6 +892,10 @@ Value ASTInterpreter::visit_expr(AST_expr* node) { ...@@ -896,6 +892,10 @@ Value ASTInterpreter::visit_expr(AST_expr* node) {
return visit_clsAttribute((AST_ClsAttribute*)node); return visit_clsAttribute((AST_ClsAttribute*)node);
case AST_TYPE::LangPrimitive: case AST_TYPE::LangPrimitive:
return visit_langPrimitive((AST_LangPrimitive*)node); return visit_langPrimitive((AST_LangPrimitive*)node);
case AST_TYPE::MakeClass:
return visit_makeClass((AST_MakeClass*)node);
case AST_TYPE::MakeFunction:
return visit_makeFunction((AST_MakeFunction*)node);
default: default:
RELEASE_ASSERT(0, ""); RELEASE_ASSERT(0, "");
}; };
......
...@@ -757,6 +757,8 @@ CompilerVariable* makeFunction(IREmitter& emitter, CLFunction* f, CompilerVariab ...@@ -757,6 +757,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,
......
...@@ -291,7 +291,7 @@ private: ...@@ -291,7 +291,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) {
...@@ -518,6 +518,8 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -518,6 +518,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);
...@@ -596,7 +598,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -596,7 +598,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());
...@@ -652,28 +654,56 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -652,28 +654,56 @@ 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. //
// There are four kinds of AST statements which can set a name:
// - Assign
// - ClassDef
// - FunctionDef
// - Import, ImportFrom
//
// However, all of these get translated away into Assigns, so we only need to worry about those. Also,
// as an invariant, all assigns that can fail assign to a temporary rather than a python name. This
// ensures that we interoperate properly with definedness analysis.
//
// 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
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) {
auto asgn = ast_cast<AST_Assign>(invoke->stmt); // The CFG pass translates away these statements, so we should never encounter them.
// If we did, we'd need to remove a name here.
assert(stmt->type != AST_TYPE::ClassDef);
assert(stmt->type != AST_TYPE::FunctionDef);
assert(stmt->type != AST_TYPE::Import);
assert(stmt->type != AST_TYPE::ImportFrom);
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]); InternedString name = ast_cast<AST_Name>(asgn->targets[0])->id;
assert(name.c_str()[0] == '#'); // it must be a temporary
// TODO: inneficient // You might think I need to check whether `name' is being assigned globally or locally,
// since a global assign doesn't affect the symbol table. However, the CFG pass only
// generates invoke-assigns to temporary variables. Just to be sure, we assert:
assert(!source->getScopeInfo()->refersToGlobal(name));
// TODO: inefficient
sym_table = new SymbolTable(*sym_table); sym_table = new SymbolTable(*sym_table);
ASSERT(sym_table->count(name->id), "%d %s\n", block->idx, name->id.c_str()); ASSERT(sym_table->count(name), "%d %s\n", block->idx, name.c_str());
sym_table->erase(name->id); sym_table->erase(name);
created_new_sym_table = true; created_new_sym_table = true;
} }
} }
...@@ -691,21 +721,25 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -691,21 +721,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:
...@@ -715,6 +749,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -715,6 +749,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();
...@@ -727,7 +762,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -727,7 +762,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
...@@ -739,19 +774,23 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -739,19 +774,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.
...@@ -759,21 +798,22 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -759,21 +798,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]]);
} }
...@@ -785,14 +825,13 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc ...@@ -785,14 +825,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;
...@@ -882,6 +883,7 @@ private: ...@@ -882,6 +883,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) {
...@@ -1087,6 +1089,133 @@ private: ...@@ -1087,6 +1089,133 @@ private:
return new ConcreteCompilerVariable(UNKNOWN, rtn, true); return new ConcreteCompilerVariable(UNKNOWN, rtn, true);
} }
CompilerVariable* evalMakeClass(AST_MakeClass* mkclass, UnwindInfo unw_info) {
assert(mkclass->type == AST_TYPE::MakeClass && mkclass->class_def->type == AST_TYPE::ClassDef);
AST_ClassDef* node = mkclass->class_def;
ScopeInfo* scope_info = irstate->getScopeInfoForNode(node);
assert(scope_info);
std::vector<CompilerVariable*> bases;
for (auto b : node->bases) {
CompilerVariable* base = evalExpr(b, unw_info);
bases.push_back(base);
}
CompilerVariable* _bases_tuple = makeTuple(bases);
for (auto b : bases) {
b->decvref(emitter);
}
ConcreteCompilerVariable* bases_tuple = _bases_tuple->makeConverted(emitter, _bases_tuple->getBoxType());
_bases_tuple->decvref(emitter);
std::vector<CompilerVariable*> decorators;
for (auto d : node->decorator_list) {
decorators.push_back(evalExpr(d, unw_info));
}
CLFunction* cl = wrapFunction(node, nullptr, node->body, irstate->getSourceInfo());
// TODO duplication with _createFunction:
CompilerVariable* created_closure = NULL;
if (scope_info->takesClosure()) {
created_closure = symbol_table[internString(CREATED_CLOSURE_NAME)];
assert(created_closure);
}
// TODO kind of silly to create the function just to usually-delete it afterwards;
// one reason to do this is to pass the closure through if necessary,
// but since the classdef can't create its own closure, shouldn't need to explicitly
// create that scope to pass the closure through.
CompilerVariable* func = makeFunction(emitter, cl, created_closure, false, {});
CompilerVariable* attr_dict = func->call(emitter, getEmptyOpInfo(unw_info), ArgPassSpec(0), {}, NULL);
func->decvref(emitter);
ConcreteCompilerVariable* converted_attr_dict = attr_dict->makeConverted(emitter, attr_dict->getBoxType());
attr_dict->decvref(emitter);
llvm::Value* classobj = emitter.createCall3(unw_info, g.funcs.createUserClass,
embedConstantPtr(&node->name.str(), g.llvm_str_type_ptr),
bases_tuple->getValue(), converted_attr_dict->getValue());
// Note: createuserClass is free to manufacture non-class objects
CompilerVariable* cls = new ConcreteCompilerVariable(UNKNOWN, classobj, true);
for (int i = decorators.size() - 1; i >= 0; i--) {
cls = decorators[i]->call(emitter, getOpInfoForNode(node, unw_info), ArgPassSpec(1), { cls }, NULL);
decorators[i]->decvref(emitter);
}
// do we need to decvref this?
return cls;
}
CompilerVariable* _createFunction(AST* node, UnwindInfo unw_info, AST_arguments* args,
const std::vector<AST_stmt*>& body) {
CLFunction* cl = wrapFunction(node, args, body, irstate->getSourceInfo());
std::vector<ConcreteCompilerVariable*> defaults;
for (auto d : args->defaults) {
CompilerVariable* e = evalExpr(d, unw_info);
ConcreteCompilerVariable* converted = e->makeConverted(emitter, e->getBoxType());
e->decvref(emitter);
defaults.push_back(converted);
}
CompilerVariable* created_closure = NULL;
bool takes_closure;
// Optimization: when compiling a module, it's nice to not have to run analyses into the
// entire module's source code.
// If we call getScopeInfoForNode, that will trigger an analysis of that function tree,
// but we're only using it here to figure out if that function takes a closure.
// Top level functions never take a closure, so we can skip the analysis.
if (irstate->getSourceInfo()->ast->type == AST_TYPE::Module)
takes_closure = false;
else {
takes_closure = irstate->getScopeInfoForNode(node)->takesClosure();
}
bool is_generator = cl->source->is_generator;
if (takes_closure) {
if (irstate->getScopeInfo()->createsClosure()) {
created_closure = symbol_table[internString(CREATED_CLOSURE_NAME)];
} else {
assert(irstate->getScopeInfo()->passesThroughClosure());
created_closure = symbol_table[internString(PASSED_CLOSURE_NAME)];
}
assert(created_closure);
}
CompilerVariable* func = makeFunction(emitter, cl, created_closure, is_generator, defaults);
for (auto d : defaults) {
d->decvref(emitter);
}
return func;
}
CompilerVariable* evalMakeFunction(AST_MakeFunction* mkfn, UnwindInfo unw_info) {
AST_FunctionDef* node = mkfn->function_def;
std::vector<CompilerVariable*> decorators;
for (auto d : node->decorator_list) {
decorators.push_back(evalExpr(d, unw_info));
}
CompilerVariable* func = _createFunction(node, unw_info, node->args, node->body);
for (int i = decorators.size() - 1; i >= 0; i--) {
func = decorators[i]->call(emitter, getOpInfoForNode(node, unw_info), ArgPassSpec(1), { func }, NULL);
decorators[i]->decvref(emitter);
}
return func;
}
ConcreteCompilerVariable* unboxVar(ConcreteCompilerType* t, llvm::Value* v, bool grabbed) { ConcreteCompilerVariable* unboxVar(ConcreteCompilerType* t, llvm::Value* v, bool grabbed) {
if (t == BOXED_INT) { if (t == BOXED_INT) {
llvm::Value* unboxed = emitter.getBuilder()->CreateCall(g.funcs.unboxInt, v); llvm::Value* unboxed = emitter.getBuilder()->CreateCall(g.funcs.unboxInt, v);
...@@ -1172,12 +1301,19 @@ private: ...@@ -1172,12 +1301,19 @@ private:
rtn = evalYield(ast_cast<AST_Yield>(node), unw_info); rtn = evalYield(ast_cast<AST_Yield>(node), unw_info);
break; break;
// pseudo-nodes
case AST_TYPE::ClsAttribute: case AST_TYPE::ClsAttribute:
rtn = evalClsAttribute(ast_cast<AST_ClsAttribute>(node), unw_info); rtn = evalClsAttribute(ast_cast<AST_ClsAttribute>(node), unw_info);
break; break;
case AST_TYPE::LangPrimitive: case AST_TYPE::LangPrimitive:
rtn = evalLangPrimitive(ast_cast<AST_LangPrimitive>(node), unw_info); rtn = evalLangPrimitive(ast_cast<AST_LangPrimitive>(node), unw_info);
break; break;
case AST_TYPE::MakeClass:
rtn = evalMakeClass(ast_cast<AST_MakeClass>(node), unw_info);
break;
case AST_TYPE::MakeFunction:
rtn = evalMakeFunction(ast_cast<AST_MakeFunction>(node), unw_info);
break;
default: default:
printf("Unhandled expr type: %d (irgenerator.cpp:" STRINGIFY(__LINE__) ")\n", node->type); printf("Unhandled expr type: %d (irgenerator.cpp:" STRINGIFY(__LINE__) ")\n", node->type);
exit(1); exit(1);
...@@ -1225,6 +1361,12 @@ private: ...@@ -1225,6 +1361,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 +1385,7 @@ private: ...@@ -1243,6 +1385,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");
...@@ -1390,69 +1533,6 @@ private: ...@@ -1390,69 +1533,6 @@ private:
val->decvref(emitter); val->decvref(emitter);
} }
void doClassDef(AST_ClassDef* node, UnwindInfo unw_info) {
assert(node->type == AST_TYPE::ClassDef);
ScopeInfo* scope_info = irstate->getScopeInfoForNode(node);
assert(scope_info);
std::vector<CompilerVariable*> bases;
for (auto b : node->bases) {
CompilerVariable* base = evalExpr(b, unw_info);
bases.push_back(base);
}
CompilerVariable* _bases_tuple = makeTuple(bases);
for (auto b : bases) {
b->decvref(emitter);
}
ConcreteCompilerVariable* bases_tuple = _bases_tuple->makeConverted(emitter, _bases_tuple->getBoxType());
_bases_tuple->decvref(emitter);
std::vector<CompilerVariable*> decorators;
for (auto d : node->decorator_list) {
decorators.push_back(evalExpr(d, unw_info));
}
CLFunction* cl = wrapFunction(node, nullptr, node->body, irstate->getSourceInfo());
// TODO duplication with _createFunction:
CompilerVariable* created_closure = NULL;
if (scope_info->takesClosure()) {
created_closure = symbol_table[internString(CREATED_CLOSURE_NAME)];
assert(created_closure);
}
// TODO kind of silly to create the function just to usually-delete it afterwards;
// one reason to do this is to pass the closure through if necessary,
// but since the classdef can't create its own closure, shouldn't need to explicitly
// create that scope to pass the closure through.
CompilerVariable* func = makeFunction(emitter, cl, created_closure, false, {});
CompilerVariable* attr_dict = func->call(emitter, getEmptyOpInfo(unw_info), ArgPassSpec(0), {}, NULL);
func->decvref(emitter);
ConcreteCompilerVariable* converted_attr_dict = attr_dict->makeConverted(emitter, attr_dict->getBoxType());
attr_dict->decvref(emitter);
llvm::Value* classobj = emitter.createCall3(unw_info, g.funcs.createUserClass,
embedConstantPtr(&node->name.str(), g.llvm_str_type_ptr),
bases_tuple->getValue(), converted_attr_dict->getValue());
// Note: createuserClass is free to manufacture non-class objects
CompilerVariable* cls = new ConcreteCompilerVariable(UNKNOWN, classobj, true);
for (int i = decorators.size() - 1; i >= 0; i--) {
cls = decorators[i]->call(emitter, getOpInfoForNode(node, unw_info), ArgPassSpec(1), { cls }, NULL);
decorators[i]->decvref(emitter);
}
_doSet(irstate->getSourceInfo()->mangleName(node->name), cls, unw_info);
cls->decvref(emitter);
}
void doDelete(AST_Delete* node, UnwindInfo unw_info) { void doDelete(AST_Delete* node, UnwindInfo unw_info) {
for (AST_expr* target : node->targets) { for (AST_expr* target : node->targets) {
switch (target->type) { switch (target->type) {
...@@ -1544,70 +1624,6 @@ private: ...@@ -1544,70 +1624,6 @@ private:
symbol_table.erase(target->id); symbol_table.erase(target->id);
} }
CompilerVariable* _createFunction(AST* node, UnwindInfo unw_info, AST_arguments* args,
const std::vector<AST_stmt*>& body) {
CLFunction* cl = wrapFunction(node, args, body, irstate->getSourceInfo());
std::vector<ConcreteCompilerVariable*> defaults;
for (auto d : args->defaults) {
CompilerVariable* e = evalExpr(d, unw_info);
ConcreteCompilerVariable* converted = e->makeConverted(emitter, e->getBoxType());
e->decvref(emitter);
defaults.push_back(converted);
}
CompilerVariable* created_closure = NULL;
bool takes_closure;
// Optimization: when compiling a module, it's nice to not have to run analyses into the
// entire module's source code.
// If we call getScopeInfoForNode, that will trigger an analysis of that function tree,
// but we're only using it here to figure out if that function takes a closure.
// Top level functions never take a closure, so we can skip the analysis.
if (irstate->getSourceInfo()->ast->type == AST_TYPE::Module)
takes_closure = false;
else {
takes_closure = irstate->getScopeInfoForNode(node)->takesClosure();
}
bool is_generator = cl->source->is_generator;
if (takes_closure) {
if (irstate->getScopeInfo()->createsClosure()) {
created_closure = symbol_table[internString(CREATED_CLOSURE_NAME)];
} else {
assert(irstate->getScopeInfo()->passesThroughClosure());
created_closure = symbol_table[internString(PASSED_CLOSURE_NAME)];
}
assert(created_closure);
}
CompilerVariable* func = makeFunction(emitter, cl, created_closure, is_generator, defaults);
for (auto d : defaults) {
d->decvref(emitter);
}
return func;
}
void doFunctionDef(AST_FunctionDef* node, UnwindInfo unw_info) {
std::vector<CompilerVariable*> decorators;
for (auto d : node->decorator_list) {
decorators.push_back(evalExpr(d, unw_info));
}
CompilerVariable* func = _createFunction(node, unw_info, node->args, node->body);
for (int i = decorators.size() - 1; i >= 0; i--) {
func = decorators[i]->call(emitter, getOpInfoForNode(node, unw_info), ArgPassSpec(1), { func }, NULL);
decorators[i]->decvref(emitter);
}
_doSet(irstate->getSourceInfo()->mangleName(node->name), func, unw_info);
func->decvref(emitter);
}
void doPrint(AST_Print* node, UnwindInfo unw_info) { void doPrint(AST_Print* node, UnwindInfo unw_info) {
ConcreteCompilerVariable* dest = NULL; ConcreteCompilerVariable* dest = NULL;
if (node->dest) { if (node->dest) {
...@@ -1702,7 +1718,7 @@ private: ...@@ -1702,7 +1718,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();
...@@ -1976,18 +1992,12 @@ private: ...@@ -1976,18 +1992,12 @@ private:
case AST_TYPE::Assign: case AST_TYPE::Assign:
doAssign(ast_cast<AST_Assign>(node), unw_info); doAssign(ast_cast<AST_Assign>(node), unw_info);
break; break;
case AST_TYPE::ClassDef:
doClassDef(ast_cast<AST_ClassDef>(node), unw_info);
break;
case AST_TYPE::Delete: case AST_TYPE::Delete:
doDelete(ast_cast<AST_Delete>(node), unw_info); doDelete(ast_cast<AST_Delete>(node), unw_info);
break; break;
case AST_TYPE::Expr: case AST_TYPE::Expr:
doExpr(ast_cast<AST_Expr>(node), unw_info); doExpr(ast_cast<AST_Expr>(node), unw_info);
break; break;
case AST_TYPE::FunctionDef:
doFunctionDef(ast_cast<AST_FunctionDef>(node), unw_info);
break;
// case AST_TYPE::If: // case AST_TYPE::If:
// doIf(ast_cast<AST_If>(node)); // doIf(ast_cast<AST_If>(node));
// break; // break;
...@@ -2065,9 +2075,9 @@ private: ...@@ -2065,9 +2075,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;
...@@ -2078,7 +2088,7 @@ private: ...@@ -2078,7 +2088,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);
...@@ -2213,6 +2223,8 @@ public: ...@@ -2213,6 +2223,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());
...@@ -2350,12 +2362,24 @@ public: ...@@ -2350,12 +2362,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,
......
...@@ -953,7 +953,6 @@ void AST_Branch::accept_stmt(StmtVisitor* v) { ...@@ -953,7 +953,6 @@ void AST_Branch::accept_stmt(StmtVisitor* v) {
v->visit_branch(this); v->visit_branch(this);
} }
void AST_Jump::accept(ASTVisitor* v) { void AST_Jump::accept(ASTVisitor* v) {
bool skip = v->visit_jump(this); bool skip = v->visit_jump(this);
if (skip) if (skip)
...@@ -976,7 +975,29 @@ void* AST_ClsAttribute::accept_expr(ExprVisitor* v) { ...@@ -976,7 +975,29 @@ void* AST_ClsAttribute::accept_expr(ExprVisitor* v) {
return v->visit_clsattribute(this); return v->visit_clsattribute(this);
} }
void AST_MakeFunction::accept(ASTVisitor* v) {
bool skip = v->visit_makefunction(this);
if (skip)
return;
function_def->accept(v);
}
void* AST_MakeFunction::accept_expr(ExprVisitor* v) {
return v->visit_makefunction(this);
}
void AST_MakeClass::accept(ASTVisitor* v) {
bool skip = v->visit_makeclass(this);
if (skip)
return;
class_def->accept(v);
}
void* AST_MakeClass::accept_expr(ExprVisitor* v) {
return v->visit_makeclass(this);
}
void print_ast(AST* ast) { void print_ast(AST* ast) {
PrintVisitor v; PrintVisitor v;
...@@ -1851,6 +1872,16 @@ bool PrintVisitor::visit_clsattribute(AST_ClsAttribute* node) { ...@@ -1851,6 +1872,16 @@ bool PrintVisitor::visit_clsattribute(AST_ClsAttribute* node) {
return true; return true;
} }
bool PrintVisitor::visit_makefunction(AST_MakeFunction* node) {
printf("make_");
return false;
}
bool PrintVisitor::visit_makeclass(AST_MakeClass* node) {
printf("make_");
return false;
}
class FlattenVisitor : public ASTVisitor { class FlattenVisitor : public ASTVisitor {
private: private:
std::vector<AST*>* output; std::vector<AST*>* output;
...@@ -2098,6 +2129,15 @@ public: ...@@ -2098,6 +2129,15 @@ public:
output->push_back(node); output->push_back(node);
return false; return false;
} }
virtual bool visit_makeclass(AST_MakeClass* node) {
output->push_back(node);
return false;
}
virtual bool visit_makefunction(AST_MakeFunction* node) {
output->push_back(node);
return false;
}
}; };
void flatten(const std::vector<AST_stmt*>& roots, std::vector<AST*>& output, bool expand_scopes) { void flatten(const std::vector<AST_stmt*>& roots, std::vector<AST*>& output, bool expand_scopes) {
......
...@@ -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.
SetComp = 89, SetComp = 89,
// Pseudo-nodes that are specific to this compiler: // Pseudo-nodes that are specific to this compiler:
...@@ -128,6 +128,8 @@ enum AST_TYPE { ...@@ -128,6 +128,8 @@ enum AST_TYPE {
AugBinOp = 203, AugBinOp = 203,
Invoke = 204, Invoke = 204,
LangPrimitive = 205, LangPrimitive = 205,
MakeClass = 206, // wraps a ClassDef to make it an expr
MakeFunction = 207, // wraps a FunctionDef to make it an expr
// These aren't real AST types, but since we use AST types to represent binexp types // These aren't real AST types, but since we use AST types to represent binexp types
// and divmod+truediv are essentially types of binops, we add them here (at least for now): // and divmod+truediv are essentially types of binops, we add them here (at least for now):
...@@ -951,6 +953,31 @@ public: ...@@ -951,6 +953,31 @@ public:
static const AST_TYPE::AST_TYPE TYPE = AST_TYPE::Yield; static const AST_TYPE::AST_TYPE TYPE = AST_TYPE::Yield;
}; };
class AST_MakeFunction : public AST_expr {
public:
AST_FunctionDef* function_def;
virtual void accept(ASTVisitor* v);
virtual void* accept_expr(ExprVisitor* v);
AST_MakeFunction(AST_FunctionDef* fd)
: AST_expr(AST_TYPE::MakeFunction, fd->lineno, fd->col_offset), function_def(fd) {}
static const AST_TYPE::AST_TYPE TYPE = AST_TYPE::MakeFunction;
};
class AST_MakeClass : public AST_expr {
public:
AST_ClassDef* class_def;
virtual void accept(ASTVisitor* v);
virtual void* accept_expr(ExprVisitor* v);
AST_MakeClass(AST_ClassDef* cd) : AST_expr(AST_TYPE::MakeClass, cd->lineno, cd->col_offset), class_def(cd) {}
static const AST_TYPE::AST_TYPE TYPE = AST_TYPE::MakeClass;
};
// AST pseudo-nodes that will get added during CFG-construction. These don't exist in the input AST, but adding them in // AST pseudo-nodes that will get added during CFG-construction. These don't exist in the input AST, but adding them in
// lets us avoid creating a completely new IR for this phase // lets us avoid creating a completely new IR for this phase
...@@ -1019,14 +1046,14 @@ public: ...@@ -1019,14 +1046,14 @@ public:
class AST_LangPrimitive : public AST_expr { class AST_LangPrimitive : public AST_expr {
public: public:
enum Opcodes { enum Opcodes {
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' and so forth
CHECK_EXC_MATCH, CHECK_EXC_MATCH,
SET_EXC_INFO, SET_EXC_INFO,
UNCACHE_EXC_INFO, UNCACHE_EXC_INFO,
...@@ -1115,6 +1142,8 @@ public: ...@@ -1115,6 +1142,8 @@ public:
virtual bool visit_with(AST_With* node) { RELEASE_ASSERT(0, ""); } virtual bool visit_with(AST_With* node) { RELEASE_ASSERT(0, ""); }
virtual bool visit_yield(AST_Yield* node) { RELEASE_ASSERT(0, ""); } virtual bool visit_yield(AST_Yield* node) { RELEASE_ASSERT(0, ""); }
virtual bool visit_makeclass(AST_MakeClass* node) { RELEASE_ASSERT(0, ""); }
virtual bool visit_makefunction(AST_MakeFunction* node) { RELEASE_ASSERT(0, ""); }
virtual bool visit_branch(AST_Branch* node) { RELEASE_ASSERT(0, ""); } virtual bool visit_branch(AST_Branch* node) { RELEASE_ASSERT(0, ""); }
virtual bool visit_jump(AST_Jump* node) { RELEASE_ASSERT(0, ""); } virtual bool visit_jump(AST_Jump* node) { RELEASE_ASSERT(0, ""); }
}; };
...@@ -1187,6 +1216,8 @@ public: ...@@ -1187,6 +1216,8 @@ public:
virtual bool visit_branch(AST_Branch* node) { return false; } virtual bool visit_branch(AST_Branch* node) { return false; }
virtual bool visit_jump(AST_Jump* node) { return false; } virtual bool visit_jump(AST_Jump* node) { return false; }
virtual bool visit_makeclass(AST_MakeClass* node) { return false; }
virtual bool visit_makefunction(AST_MakeFunction* node) { return false; }
}; };
class ExprVisitor { class ExprVisitor {
...@@ -1223,6 +1254,8 @@ public: ...@@ -1223,6 +1254,8 @@ public:
virtual void* visit_tuple(AST_Tuple* node) { RELEASE_ASSERT(0, ""); } virtual void* visit_tuple(AST_Tuple* node) { RELEASE_ASSERT(0, ""); }
virtual void* visit_unaryop(AST_UnaryOp* node) { RELEASE_ASSERT(0, ""); } virtual void* visit_unaryop(AST_UnaryOp* node) { RELEASE_ASSERT(0, ""); }
virtual void* visit_yield(AST_Yield* node) { RELEASE_ASSERT(0, ""); } virtual void* visit_yield(AST_Yield* node) { RELEASE_ASSERT(0, ""); }
virtual void* visit_makeclass(AST_MakeClass* node) { RELEASE_ASSERT(0, ""); }
virtual void* visit_makefunction(AST_MakeFunction* node) { RELEASE_ASSERT(0, ""); }
}; };
class StmtVisitor { class StmtVisitor {
...@@ -1332,6 +1365,8 @@ public: ...@@ -1332,6 +1365,8 @@ public:
virtual bool visit_branch(AST_Branch* node); virtual bool visit_branch(AST_Branch* node);
virtual bool visit_jump(AST_Jump* node); virtual bool visit_jump(AST_Jump* node);
virtual bool visit_makefunction(AST_MakeFunction* node);
virtual bool visit_makeclass(AST_MakeClass* node);
}; };
// Given an AST node, return a vector of the node plus all its descendents. // Given an AST node, return a vector of the node plus all its descendents.
......
...@@ -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;
friend CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body); friend CFG* computeCFG(SourceInfo* source, std::vector<AST_stmt*> body);
...@@ -78,32 +186,12 @@ public: ...@@ -78,32 +186,12 @@ public:
~CFGVisitor() { ~CFGVisitor() {
// if we're being destroyed due to an exception, our internal invariants may be violated, but that's okay; the // if we're being destroyed due to an exception, our internal invariants may be violated, but that's okay; the
// CFG isn't going to get used anyway. (Maybe we should check that it won't be used somehow?) // CFG isn't going to get used anyway. (Maybe we should check that it won't be used somehow?)
assert(regions.size() == 0 || std::uncaught_exception()); assert(continuations.size() == 0 || std::uncaught_exception());
assert(exc_handlers.size() == 0 || std::uncaught_exception()); assert(exc_handlers.size() == 0 || std::uncaught_exception());
} }
// ---------- 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));
} }
...@@ -113,49 +201,33 @@ private: ...@@ -113,49 +201,33 @@ 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 {
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) { void pushLoopContinuation(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);
assert(curblock); assert(curblock);
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;
} }
} }
...@@ -170,18 +242,14 @@ private: ...@@ -170,18 +242,14 @@ private:
void doContinue() { void doContinue() {
assert(curblock); assert(curblock);
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;
} }
} }
...@@ -191,18 +259,14 @@ private: ...@@ -191,18 +259,14 @@ private:
void doBreak() { void doBreak() {
assert(curblock); assert(curblock);
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;
} }
} }
...@@ -223,7 +287,7 @@ private: ...@@ -223,7 +287,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; }
...@@ -256,19 +320,12 @@ private: ...@@ -256,19 +320,12 @@ private:
InternedString iter_name = nodeName(node, "lc_iter", i); InternedString iter_name = nodeName(node, "lc_iter", i);
pushAssign(iter_name, iter_call); pushAssign(iter_name, iter_call);
AST_expr* next_attr AST_expr* next_attr = makeLoadAttribute(makeLoad(iter_name, node), internString("next"), true);
= 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_LangPrimitive* test_call = new AST_LangPrimitive(AST_LangPrimitive::HASNEXT); AST_LangPrimitive* test_call = new AST_LangPrimitive(AST_LangPrimitive::HASNEXT);
...@@ -295,7 +352,7 @@ private: ...@@ -295,7 +352,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));
...@@ -317,10 +374,7 @@ private: ...@@ -317,10 +374,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;
} }
...@@ -330,21 +384,15 @@ private: ...@@ -330,21 +384,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];
...@@ -361,7 +409,7 @@ private: ...@@ -361,7 +409,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) {
...@@ -371,11 +419,15 @@ private: ...@@ -371,11 +419,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);
...@@ -384,6 +436,30 @@ private: ...@@ -384,6 +436,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) {
...@@ -414,28 +490,34 @@ private: ...@@ -414,28 +490,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;
...@@ -497,7 +579,7 @@ private: ...@@ -497,7 +579,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);
...@@ -518,11 +600,10 @@ private: ...@@ -518,11 +600,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
...@@ -539,13 +620,15 @@ private: ...@@ -539,13 +620,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));
} }
...@@ -643,26 +726,19 @@ private: ...@@ -643,26 +726,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) {
...@@ -744,7 +820,7 @@ private: ...@@ -744,7 +820,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;
...@@ -757,25 +833,18 @@ private: ...@@ -757,25 +833,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);
} }
} }
...@@ -799,7 +868,7 @@ private: ...@@ -799,7 +868,7 @@ private:
// Generates a FunctionDef which produces scope for `node'. The function produced is empty, so you'd better fill it. // Generates a FunctionDef which produces scope for `node'. The function produced is empty, so you'd better fill it.
// `node' had better be a kind of node that scoping_analysis thinks can carry scope (see the switch (node->type) // `node' had better be a kind of node that scoping_analysis thinks can carry scope (see the switch (node->type)
// block in ScopingAnalysis::processNameUsages in analysis/scoping_analysis.cpp); e.g. a Lambda or GeneratorExp. // block in ScopingAnalysis::processNameUsages in analysis/scoping_analysis.cpp); e.g. a Lambda or GeneratorExp.
AST_FunctionDef* makeFunctionForScope(AST* node) { AST_MakeFunction* makeFunctionForScope(AST* node) {
AST_FunctionDef* func = new AST_FunctionDef(); AST_FunctionDef* func = new AST_FunctionDef();
func->lineno = node->lineno; func->lineno = node->lineno;
func->col_offset = node->col_offset; func->col_offset = node->col_offset;
...@@ -809,7 +878,7 @@ private: ...@@ -809,7 +878,7 @@ private:
func->args->vararg = internString(""); func->args->vararg = internString("");
func->args->kwarg = internString(""); func->args->kwarg = internString("");
scoping_analysis->registerScopeReplacement(node, func); // critical bit scoping_analysis->registerScopeReplacement(node, func); // critical bit
return func; return new AST_MakeFunction(func);
} }
// This is a helper function used for generator expressions and comprehensions. // This is a helper function used for generator expressions and comprehensions.
...@@ -851,18 +920,18 @@ private: ...@@ -851,18 +920,18 @@ private:
AST_expr* first = remapExpr(node->generators[0]->iter); AST_expr* first = remapExpr(node->generators[0]->iter);
InternedString first_generator_name = nodeName(node->generators[0]); InternedString first_generator_name = nodeName(node->generators[0]);
AST_FunctionDef* func = makeFunctionForScope(node); AST_MakeFunction* func = makeFunctionForScope(node);
func->args->args.push_back(makeName(first_generator_name, AST_TYPE::Param, node->lineno)); func->function_def->args->args.push_back(makeName(first_generator_name, AST_TYPE::Param, node->lineno));
emitComprehensionLoops(&func->body, node->generators, emitComprehensionLoops(&func->function_def->body, node->generators,
makeName(first_generator_name, AST_TYPE::Load, node->lineno), makeName(first_generator_name, AST_TYPE::Load, node->lineno),
[this, node](std::vector<AST_stmt*>* insert_point) { [this, node](std::vector<AST_stmt*>* insert_point) {
auto y = new AST_Yield(); auto y = new AST_Yield();
y->value = node->elt; y->value = node->elt;
insert_point->push_back(makeExpr(y)); insert_point->push_back(makeExpr(y));
}); });
push_back(func); pushAssign(func->function_def->name, func);
return makeCall(makeName(func->name, AST_TYPE::Load, node->lineno), first); return makeCall(makeLoad(func->function_def->name, node), first);
} }
void emitComprehensionYield(AST_DictComp* node, InternedString dict_name, std::vector<AST_stmt*>* insert_point) { void emitComprehensionYield(AST_DictComp* node, InternedString dict_name, std::vector<AST_stmt*>* insert_point) {
...@@ -883,70 +952,58 @@ private: ...@@ -883,70 +952,58 @@ private:
AST_expr* first = remapExpr(node->generators[0]->iter); AST_expr* first = remapExpr(node->generators[0]->iter);
InternedString first_generator_name = nodeName(node->generators[0]); InternedString first_generator_name = nodeName(node->generators[0]);
AST_FunctionDef* func = makeFunctionForScope(node); AST_MakeFunction* func = makeFunctionForScope(node);
func->args->args.push_back(makeName(first_generator_name, AST_TYPE::Param, node->lineno)); func->function_def->args->args.push_back(makeName(first_generator_name, AST_TYPE::Param, node->lineno));
InternedString rtn_name = nodeName(node); InternedString rtn_name = nodeName(node);
auto asgn = new AST_Assign(); auto asgn = new AST_Assign();
asgn->targets.push_back(makeName(rtn_name, AST_TYPE::Store, node->lineno)); asgn->targets.push_back(makeName(rtn_name, AST_TYPE::Store, node->lineno));
asgn->value = new ResultType(); asgn->value = new ResultType();
func->body.push_back(asgn); func->function_def->body.push_back(asgn);
auto lambda = auto lambda =
[&](std::vector<AST_stmt*>* insert_point) { emitComprehensionYield(node, rtn_name, insert_point); }; [&](std::vector<AST_stmt*>* insert_point) { emitComprehensionYield(node, rtn_name, insert_point); };
AST_Name* first_name = makeName(first_generator_name, AST_TYPE::Load, node->lineno); AST_Name* first_name = makeName(first_generator_name, AST_TYPE::Load, node->lineno);
emitComprehensionLoops(&func->body, node->generators, first_name, lambda); emitComprehensionLoops(&func->function_def->body, node->generators, first_name, lambda);
auto rtn = new AST_Return(); auto rtn = new AST_Return();
rtn->value = makeName(rtn_name, AST_TYPE::Load, node->lineno); rtn->value = makeName(rtn_name, AST_TYPE::Load, node->lineno);
func->body.push_back(rtn); func->function_def->body.push_back(rtn);
push_back(func); pushAssign(func->function_def->name, func);
return makeCall(makeName(func->name, AST_TYPE::Load, node->lineno), first); return makeCall(makeName(func->function_def->name, AST_TYPE::Load, node->lineno), first);
} }
AST_expr* remapIfExp(AST_IfExp* node) { AST_expr* remapIfExp(AST_IfExp* node) {
assert(curblock); assert(curblock);
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;
CFGBlock* iftrue = cfg->addBlock(); // if true block
iftrue->info = "iftrue"; cfg->placeBlock(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) {
...@@ -957,14 +1014,25 @@ private: ...@@ -957,14 +1014,25 @@ private:
return rtn; return rtn;
} }
AST_expr* remapLambda(AST_Lambda* node) { AST_arguments* remapArguments(AST_arguments* args) {
// Remap in place: see note in visit_functiondef for why. auto rtn = new AST_arguments();
rtn = new AST_arguments();
for (int i = 0; i < node->args->defaults.size(); i++) { // don't remap args, they're not evaluated. NB. expensive vector copy.
node->args->defaults[i] = remapExpr(node->args->defaults[i]); rtn->args = args->args;
} rtn->kwarg = args->kwarg;
rtn->vararg = args->vararg;
for (auto expr : args->defaults)
rtn->defaults.push_back(remapExpr(expr));
return rtn;
}
return node; AST_expr* remapLambda(AST_Lambda* node) {
auto rtn = new AST_Lambda();
rtn->body = node->body; // don't remap now; will be CFG'ed later
rtn->args = remapArguments(node->args);
// lambdas create scope, need to register as replacement
scoping_analysis->registerScopeReplacement(node, rtn);
return rtn;
} }
AST_expr* remapLangPrimitive(AST_LangPrimitive* node) { AST_expr* remapLangPrimitive(AST_LangPrimitive* node) {
...@@ -1068,9 +1136,14 @@ private: ...@@ -1068,9 +1136,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;
...@@ -1159,15 +1232,65 @@ private: ...@@ -1159,15 +1232,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);
...@@ -1204,6 +1327,7 @@ public: ...@@ -1204,6 +1327,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) {
...@@ -1219,8 +1343,12 @@ public: ...@@ -1219,8 +1343,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;
} }
...@@ -1265,10 +1393,7 @@ public: ...@@ -1265,10 +1393,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;
...@@ -1277,42 +1402,46 @@ public: ...@@ -1277,42 +1402,46 @@ public:
} }
bool visit_classdef(AST_ClassDef* node) override { bool visit_classdef(AST_ClassDef* node) override {
// Remap in place: see note in visit_functiondef for why. // waitaminute, who deallocates `node'?
auto def = new AST_ClassDef();
// Decorators are evaluated before the defaults: def->lineno = node->lineno;
for (int i = 0; i < node->decorator_list.size(); i++) { def->col_offset = node->col_offset;
node->decorator_list[i] = remapExpr(node->decorator_list[i]); def->name = node->name;
} def->body = node->body; // expensive vector copy
for (int i = 0; i < node->bases.size(); i++) { // Decorators are evaluated before bases:
node->bases[i] = remapExpr(node->bases[i]); for (auto expr : node->decorator_list)
} def->decorator_list.push_back(remapExpr(expr));
for (auto expr : node->bases)
def->bases.push_back(remapExpr(expr));
scoping_analysis->registerScopeReplacement(node, def);
auto tmp = nodeName(node);
pushAssign(tmp, new AST_MakeClass(def));
// is this name mangling correct?
pushAssign(source->mangleName(def->name), makeName(tmp, AST_TYPE::Load, node->lineno));
push_back(node);
return true; return true;
} }
bool visit_functiondef(AST_FunctionDef* node) override { bool visit_functiondef(AST_FunctionDef* node) override {
// As much as I don't like it, for now we're remapping these in place. auto def = new AST_FunctionDef();
// This is because we do certain analyses pre-remapping, and associate the def->name = node->name;
// results with the node. We can either do some refactoring and have a way def->body = node->body; // expensive vector copy
// of associating the new node with the same results, or just do the remapping // Decorators are evaluated before the defaults, so this *must* go before remapArguments().
// in-place. // TODO(rntz): do we have a test for this
// Doing it in-place seems ugly, but I can't think of anything it should break, for (auto expr : node->decorator_list)
// so just do that for now. def->decorator_list.push_back(remapExpr(expr));
// TODO If we remap these (functiondefs, lambdas, classdefs) in place, we should probably def->args = remapArguments(node->args);
// remap everything in place?
scoping_analysis->registerScopeReplacement(node, def);
// Decorators are evaluated before the defaults:
for (int i = 0; i < node->decorator_list.size(); i++) { auto tmp = nodeName(node);
node->decorator_list[i] = remapExpr(node->decorator_list[i]); pushAssign(tmp, new AST_MakeFunction(def));
} // is this name mangling correct?
pushAssign(source->mangleName(def->name), makeName(tmp, AST_TYPE::Load, node->lineno));
for (int i = 0; i < node->args->defaults.size(); i++) {
node->args->defaults[i] = remapExpr(node->args->defaults[i]);
}
push_back(node);
return true; return true;
} }
...@@ -1357,7 +1486,7 @@ public: ...@@ -1357,7 +1486,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.
...@@ -1371,12 +1500,11 @@ public: ...@@ -1371,12 +1500,11 @@ public:
l = r + 1; l = r + 1;
continue; continue;
} }
pushAssign(tmpname, pushAssign(tmpname, new AST_Attribute(makeLoad(tmpname, node), AST_TYPE::Load,
new AST_Attribute(makeName(tmpname, AST_TYPE::Load, node->lineno), AST_TYPE::Load, internString(a->name.str().substr(l, r - l))));
internString(a->name.str().substr(l, r - l))));
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));
} }
} }
...@@ -1417,7 +1545,7 @@ public: ...@@ -1417,7 +1545,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;
...@@ -1429,13 +1557,12 @@ public: ...@@ -1429,13 +1557,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));
} }
} }
...@@ -1480,17 +1607,10 @@ public: ...@@ -1480,17 +1607,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;
...@@ -1533,9 +1653,9 @@ public: ...@@ -1533,9 +1653,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: {
...@@ -1595,7 +1715,7 @@ public: ...@@ -1595,7 +1715,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;
} }
...@@ -1710,6 +1830,8 @@ public: ...@@ -1710,6 +1830,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.
assert(curblock); assert(curblock);
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) {
...@@ -1719,10 +1841,7 @@ public: ...@@ -1719,10 +1841,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;
} }
...@@ -1750,10 +1869,7 @@ public: ...@@ -1750,10 +1869,7 @@ public:
break; break;
} }
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();
...@@ -1768,10 +1884,7 @@ public: ...@@ -1768,10 +1884,7 @@ public:
break; break;
} }
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) {
...@@ -1807,11 +1920,7 @@ public: ...@@ -1807,11 +1920,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));
...@@ -1822,7 +1931,7 @@ public: ...@@ -1822,7 +1931,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";
...@@ -1835,13 +1944,9 @@ public: ...@@ -1835,13 +1944,9 @@ public:
if (!curblock) if (!curblock)
break; break;
} }
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";
...@@ -1853,12 +1958,8 @@ public: ...@@ -1853,12 +1958,8 @@ public:
if (!curblock) if (!curblock)
break; break;
} }
if (curblock) { if (curblock)
AST_Jump* jend = makeJump(); pushJump(end);
push_back(jend);
jend->target = end;
curblock->connectTo(end);
}
if (end->predecessors.size() == 0) { if (end->predecessors.size() == 0) {
delete end; delete end;
...@@ -1887,14 +1988,10 @@ public: ...@@ -1887,14 +1988,10 @@ public:
InternedString itername = internString(itername_buf); InternedString itername = internString(itername_buf);
pushAssign(itername, iter_call); pushAssign(itername, iter_call);
AST_expr* next_attr AST_expr* next_attr = makeLoadAttribute(makeLoad(itername, node), internString("next"), true);
= 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_LangPrimitive* test_call = new AST_LangPrimitive(AST_LangPrimitive::HASNEXT); AST_LangPrimitive* test_call = new AST_LangPrimitive(AST_LangPrimitive::HASNEXT);
...@@ -1914,32 +2011,25 @@ public: ...@@ -1914,32 +2011,25 @@ 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);
if (!curblock) if (!curblock)
break; break;
} }
popRegion(); popContinuation();
if (curblock) { if (curblock) {
AST_LangPrimitive* end_call = new AST_LangPrimitive(AST_LangPrimitive::HASNEXT); AST_LangPrimitive* end_call = new AST_LangPrimitive(AST_LangPrimitive::HASNEXT);
...@@ -1955,16 +2045,10 @@ public: ...@@ -1955,16 +2045,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);
...@@ -1975,12 +2059,8 @@ public: ...@@ -1975,12 +2059,8 @@ public:
if (!curblock) if (!curblock)
break; break;
} }
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);
}
if (end_block->predecessors.size() == 0) { if (end_block->predecessors.size() == 0) {
delete end_block; delete end_block;
...@@ -2058,12 +2138,8 @@ public: ...@@ -2058,12 +2138,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;
...@@ -2072,7 +2148,7 @@ public: ...@@ -2072,7 +2148,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) {
...@@ -2103,9 +2179,9 @@ public: ...@@ -2103,9 +2179,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) {
...@@ -2119,10 +2195,7 @@ public: ...@@ -2119,10 +2195,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) {
...@@ -2135,9 +2208,9 @@ public: ...@@ -2135,9 +2208,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;
} }
...@@ -2165,7 +2238,7 @@ public: ...@@ -2165,7 +2238,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);
...@@ -2175,17 +2248,14 @@ public: ...@@ -2175,17 +2248,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) {
...@@ -2193,13 +2263,8 @@ public: ...@@ -2193,13 +2263,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);
...@@ -2212,99 +2277,19 @@ public: ...@@ -2212,99 +2277,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();
compare->ops.push_back(AST_TYPE::Eq);
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* reraise = cfg->addDeferredBlock();
CFGBlock* doreturn = cfg->addBlock(); CFGBlock* noexc = makeFinallyCont(Why::EXCEPTION, makeLoad(exc_why_name, node), reraise);
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;
} }
...@@ -2313,44 +2298,65 @@ public: ...@@ -2313,44 +2298,65 @@ public:
} }
bool visit_with(AST_With* node) override { bool visit_with(AST_With* node) override {
// see https://www.python.org/dev/peps/pep-0343/
// section "Specification: the 'with' Statement"
// which contains pseudocode for what this implements:
//
// mgr = (EXPR)
// 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.)
assert(curblock); assert(curblock);
InternedString ctxmgrname = nodeName(node, "ctxmgr");
char ctxmgrname_buf[80]; InternedString exitname = nodeName(node, "exit");
snprintf(ctxmgrname_buf, 80, "#ctxmgr_%p", node); InternedString whyname = nodeName(node, "why");
InternedString ctxmgrname = internString(ctxmgrname_buf); InternedString exc_type_name = nodeName(node, "exc_type");
char exitname_buf[80]; InternedString exc_value_name = nodeName(node, "exc_value");
snprintf(exitname_buf, 80, "#exit_%p", node); InternedString exc_traceback_name = nodeName(node, "exc_traceback");
InternedString exitname = internString(exitname_buf);
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; // push continuations
if (regions.size()) { CFGBlock* finally_block = cfg->addDeferredBlock();
continue_dest = cfg->addDeferredBlock(); finally_block->info = "with_finally";
continue_dest->info = "with_continue"; pushFinallyContinuation(finally_block, whyname);
break_dest = cfg->addDeferredBlock();
break_dest->info = "with_break";
pushLoopRegion(continue_dest, break_dest);
}
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);
...@@ -2358,64 +2364,74 @@ public: ...@@ -2358,64 +2364,74 @@ public:
break; break;
} }
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;
...@@ -2425,30 +2441,8 @@ public: ...@@ -2425,30 +2441,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
......
...@@ -55,7 +55,9 @@ bool endswith(const std::string& s, const std::string& pattern); ...@@ -55,7 +55,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);
...@@ -92,7 +94,7 @@ template <class T1, class T2> void compareKeyset(T1* lhs, T2* rhs) { ...@@ -92,7 +94,7 @@ template <class T1, class T2> void compareKeyset(T1* lhs, T2* rhs) {
} }
good = false; good = false;
} }
assert(good); return good;
} }
} }
......
...@@ -800,10 +800,6 @@ Box* fileEnter(BoxedFile* self) { ...@@ -800,10 +800,6 @@ Box* fileEnter(BoxedFile* self) {
Box* fileExit(BoxedFile* self, Box* exc_type, Box* exc_val, Box** args) { Box* fileExit(BoxedFile* self, Box* exc_type, Box* exc_val, Box** args) {
Box* exc_tb = args[0]; Box* exc_tb = args[0];
assert(self->cls == file_cls); assert(self->cls == file_cls);
assert(exc_type == None);
assert(exc_val == None);
assert(exc_tb == None);
fileClose(self); fileClose(self);
return None; return None;
} }
......
...@@ -390,6 +390,9 @@ std::string BoxedModule::name() { ...@@ -390,6 +390,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)
......
# expected: fail
# - with statements
class TestException(Exception): class TestException(Exception):
pass pass
...@@ -361,7 +358,11 @@ def f12(): ...@@ -361,7 +358,11 @@ def f12():
except Exception as l[0]: except Exception as l[0]:
print "shouldnt get here" print "shouldnt get here"
except Exception as e2: except Exception as e2:
print e2 # print it to stderr, so that our tester's error message substituter can
# deal with differences between error messages between different Python
# versions.
import traceback
traceback.print_exc()
l = [] l = []
try: try:
......
# expected: fail
import sys
print sys.stdout.closed
# these may seem pointless, but they exercise a family of corner cases in our
# CFG generation pass (cfg.cpp).
def returner():
try:
try:
print '2 + 2'
return
finally:
print 'finally'
except:
print 'exception'
returner()
def continuer():
for x in [1]:
try:
try:
print '2 + 2'
continue
finally:
print 'finally'
except:
print 'exception'
continuer()
def breaker():
for x in [1]:
try:
try:
print '2 + 2'
break
finally:
print 'finally'
except:
print 'exception'
breaker()
def raiser():
try:
try:
print '2 + 2'
raise Exception('blaaargh')
finally:
print 'finally'
except:
print 'exception'
raiser()
def alltogethernow():
for x in [1,2,3,4]:
try:
try:
print '2 + 2'
if x == 1: break
if x == 2: continue
if x == 3: raise Exception('blaargh')
if x == 4: return
finally:
print 'finally'
except:
print 'exception'
alltogethernow()
# we have one test at global scope, another at local.
# they behave differently in codegen; there have been points at which either was bugged when the other was not.
try:
class C(object):
print 'here'
finally:
print 'finally'
def f():
try:
class C(object):
print 'here'
finally:
print 'finally'
f()
try:
class D(object):
print 'here'
except:
print 'impossible'
raise
def f2():
try:
class D(object):
print 'here'
except:
print 'impossible'
print D
raise
f2()
# expected: fail
# Syntax error to have a continue outside a loop.
def foo():
try: continue
finally: pass
def f():
try:
def foo(): return 0
except:
print 'except'
finally:
print 'finally'
f()
def f2():
try:
def foo(): return 0
except:
print 'except'
f2()
def f3():
try:
def foo(): return 0
finally:
print 'finally'
f3()
def f():
try:
# Looks like this returns from the function, but it needs to go to the finally block
return
finally:
pass
f()
def f():
# originally this exposed a bug in our irgen phase, so even `with None`
# failed here; the bug happened before actual execution. Just to test more
# things, though, we use an actual contextmanager here.
with open('/dev/null'):
class C(object):
print 'hello'
f()
def f():
C = 23
try:
class C(object): pass
except:
print 'except: C = %s' % C
raise
finally:
print 'finally: C = %s' % C
print 'end: C = %s' % C
f()
# should_error
class Mgr(object):
def __enter__(self):
print 'entered!'
def __exit__(self, *args):
print 'exited!'
with Mgr() as m:
continue
class Mgr(object):
def __init__(self): self.__enter__ = 'sucks to be you'
def __enter__(self): pass
def __exit__(self, typ, val, tback): pass
with Mgr() as m:
print 'boom boom boom boom'
class Mgr2(object):
def __init__(self): self.__exit__ = 'screwed!'
def __enter__(self): pass
def __exit__(self, typ, val, tback): pass
with Mgr2() as m:
print 'bang bang bang bang'
class Mgr(object):
def __init__(self): print 'Mgr.__init__()'
@property
def __enter__(self):
print 'Mgr.__enter__ accessed'
def enterer(*args):
print 'Mgr.__enter__ called'
return 23
return enterer
@property
def __exit__(self):
print 'Mgr.__exit__ accessed'
def exiter(*args):
print 'Mgr.__exit__ called'
return False
return exiter
with Mgr() as m:
print 'hello I am a with block'
print 'm: %s' % m
try:
with Mgr() as m:
1/0
print "you can't get there from here"
except ZeroDivisionError, e:
print e
f = open('/dev/null')
try:
with f:
1/0
except ZeroDivisionError as e:
print e
try:
f.readline()
except ValueError as e:
print e
def f():
# this exposes a bug in our irgen phase, so even `with None` bugs out here;
# the bug happens before actual execution. Just to test more things, though,
# we use an actual contextmanager here.
with open('/dev/null'):
def foo():
pass
f()
...@@ -115,6 +115,7 @@ def canonicalize_stderr(stderr): ...@@ -115,6 +115,7 @@ def canonicalize_stderr(stderr):
("NameError: global name '", "NameError: name '"), ("NameError: global name '", "NameError: name '"),
("AttributeError: '(\w+)' object attribute '(\w+)' is read-only", "AttributeError: \\2"), ("AttributeError: '(\w+)' object attribute '(\w+)' is read-only", "AttributeError: \\2"),
(r"TypeError: object.__new__\(\) takes no parameters", "TypeError: object() takes no parameters"), (r"TypeError: object.__new__\(\) takes no parameters", "TypeError: object() takes no parameters"),
("IndexError: list assignment index out of range", "IndexError: list index out of range"),
] ]
for pattern, subst_with in substitutions: for pattern, subst_with in substitutions:
......
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