diff --git a/minibenchmarks/django_template.py b/minibenchmarks/django_template.py index afcba6c61..83e3cd6c4 100644 --- a/minibenchmarks/django_template.py +++ b/minibenchmarks/django_template.py @@ -1,6 +1,6 @@ import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "../test/integration/django")) +sys.path.append(os.path.join(os.path.dirname(__file__), "../test/testsuite/lib/django")) from django.template.base import Origin, Template, Context, TemplateDoesNotExist from django.conf import settings diff --git a/minibenchmarks/pyxl_bench.py b/minibenchmarks/pyxl_bench.py index 1c79fccc7..e654328ba 100644 --- a/minibenchmarks/pyxl_bench.py +++ b/minibenchmarks/pyxl_bench.py @@ -1,7 +1,7 @@ import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "../test/integration/pyxl/")) +sys.path.append(os.path.join(os.path.dirname(__file__), "../test/testsuite/lib/pyxl/")) from pyxl.codec.register import pyxl_transform_string diff --git a/minibenchmarks/sqlalchemy_declarative.py b/minibenchmarks/sqlalchemy_declarative.py index 5160839e4..741073b55 100644 --- a/minibenchmarks/sqlalchemy_declarative.py +++ b/minibenchmarks/sqlalchemy_declarative.py @@ -2,7 +2,7 @@ import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "../test/integration/sqlalchemy/lib")) +sys.path.append(os.path.join(os.path.dirname(__file__), "../test/testsuite/lib/sqlalchemy/lib")) from sqlalchemy import Column, ForeignKey, Integer, String from sqlalchemy.ext.declarative import declarative_base diff --git a/minibenchmarks/sqlalchemy_imperative.py b/minibenchmarks/sqlalchemy_imperative.py index a41db943c..e6b9dbc89 100644 --- a/minibenchmarks/sqlalchemy_imperative.py +++ b/minibenchmarks/sqlalchemy_imperative.py @@ -1,6 +1,6 @@ import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "../test/integration/sqlalchemy/lib")) +sys.path.append(os.path.join(os.path.dirname(__file__), "../test/testsuite/lib/sqlalchemy/lib")) from sqlalchemy import Column, ForeignKey, Integer, String, Table, MetaData from sqlalchemy.ext.declarative import declarative_base diff --git a/src/codegen/ast_interpreter.cpp b/src/codegen/ast_interpreter.cpp index a02b25d2a..9f80c5f43 100644 --- a/src/codegen/ast_interpreter.cpp +++ b/src/codegen/ast_interpreter.cpp @@ -743,12 +743,19 @@ Box* ASTInterpreter::doOSR(AST_Jump* node) { UNAVOIDABLE_STAT_TIMER(t0, "us_timer_in_jitted_code"); CompiledFunction* partial_func = compilePartialFuncInternal(&exit); + auto arg_tuple = getTupleFromArgsArray(&arg_array[0], arg_array.size()); Box* r = partial_func->call(std::get<0>(arg_tuple), std::get<1>(arg_tuple), std::get<2>(arg_tuple), std::get<3>(arg_tuple)); - assert(r); - return r; + if (partial_func->exception_style == CXX) { + assert(r); + return r; + } else { + if (!r) + throwCAPIException(); + return r; + } } Value ASTInterpreter::visit_invoke(AST_Invoke* node) { @@ -1725,14 +1732,24 @@ Box* astInterpretFunction(CLFunction* clfunc, int nargs, Box* closure, Box* gene clfunc->dependent_interp_callsites.invalidateAll(); UNAVOIDABLE_STAT_TIMER(t0, "us_timer_in_jitted_code"); + Box* r; if (closure && generator) - return optimized->closure_generator_call((BoxedClosure*)closure, (BoxedGenerator*)generator, arg1, arg2, - arg3, args); + r = optimized->closure_generator_call((BoxedClosure*)closure, (BoxedGenerator*)generator, arg1, arg2, arg3, + args); else if (closure) - return optimized->closure_call((BoxedClosure*)closure, arg1, arg2, arg3, args); + r = optimized->closure_call((BoxedClosure*)closure, arg1, arg2, arg3, args); else if (generator) - return optimized->generator_call((BoxedGenerator*)generator, arg1, arg2, arg3, args); - return optimized->call(arg1, arg2, arg3, args); + r = optimized->generator_call((BoxedGenerator*)generator, arg1, arg2, arg3, args); + else + r = optimized->call(arg1, arg2, arg3, args); + + if (optimized->exception_style == CXX) + return r; + else { + if (!r) + throwCAPIException(); + return r; + } } Box** vregs = NULL; diff --git a/src/codegen/compvars.cpp b/src/codegen/compvars.cpp index ee1f6297b..ca47e1afb 100644 --- a/src/codegen/compvars.cpp +++ b/src/codegen/compvars.cpp @@ -337,9 +337,7 @@ class UnknownType : public ConcreteCompilerType { CompilerVariable* slice) override { ConcreteCompilerVariable* converted_slice = slice->makeConverted(emitter, slice->getBoxType()); - ExceptionStyle target_exception_style = CXX; - if (FORCE_LLVM_CAPI || info.unw_info.capi_exc_dest) - target_exception_style = CAPI; + ExceptionStyle target_exception_style = info.preferredExceptionStyle(); bool do_patchpoint = ENABLE_ICGETITEMS; llvm::Value* rtn; @@ -500,9 +498,7 @@ CompilerVariable* UnknownType::getattr(IREmitter& emitter, const OpInfo& info, C llvm::Value* rtn_val = NULL; - ExceptionStyle target_exception_style = CXX; - if (info.unw_info.capi_exc_dest || (!cls_only && FORCE_LLVM_CAPI)) - target_exception_style = CAPI; + ExceptionStyle target_exception_style = cls_only ? CXX : info.preferredExceptionStyle(); llvm::Value* llvm_func; void* raw_func; @@ -656,9 +652,7 @@ CompilerVariable* UnknownType::call(IREmitter& emitter, const OpInfo& info, Conc bool pass_keywords = (argspec.num_keywords != 0); int npassed_args = argspec.totalPassed(); - ExceptionStyle exception_style = ((FORCE_LLVM_CAPI && !info.unw_info.cxx_exc_dest) || info.unw_info.capi_exc_dest) - ? ExceptionStyle::CAPI - : ExceptionStyle::CXX; + ExceptionStyle exception_style = info.preferredExceptionStyle(); llvm::Value* func; if (pass_keywords) @@ -691,10 +685,7 @@ CompilerVariable* UnknownType::callattr(IREmitter& emitter, const OpInfo& info, bool pass_keywords = (flags.argspec.num_keywords != 0); int npassed_args = flags.argspec.totalPassed(); - ExceptionStyle exception_style = ((FORCE_LLVM_CAPI && !info.unw_info.cxx_exc_dest && !flags.null_on_nonexistent) - || info.unw_info.capi_exc_dest) - ? ExceptionStyle::CAPI - : ExceptionStyle::CXX; + ExceptionStyle exception_style = flags.null_on_nonexistent ? CXX : info.preferredExceptionStyle(); if (exception_style == CAPI) assert(!flags.null_on_nonexistent); // Will conflict with CAPI's null-on-exception @@ -1522,7 +1513,7 @@ class NormalObjectType : public ConcreteCompilerType { if (canStaticallyResolveGetattrs()) { Box* rtattr = typeLookup(cls, attr, nullptr); if (rtattr == NULL) { - ExceptionStyle exception_style = (FORCE_LLVM_CAPI || info.unw_info.capi_exc_dest) ? CAPI : CXX; + ExceptionStyle exception_style = info.preferredExceptionStyle(); llvm::Value* raise_func = exception_style == CXX ? g.funcs.raiseAttributeErrorStr : g.funcs.raiseAttributeErrorStrCapi; llvm::CallSite call = emitter.createCall3( @@ -1619,15 +1610,13 @@ class NormalObjectType : public ConcreteCompilerType { "%d", info.unw_info.current_stmt->lineno); CompiledFunction* cf = NULL; + CompiledFunction* best_exception_mismatch = NULL; bool found = false; // TODO have to find the right version.. similar to resolveclfunc? for (int i = 0; i < cl->versions.size(); i++) { cf = cl->versions[i]; assert(cf->spec->arg_types.size() == cl->numReceivedArgs()); - if (cf->exception_style != exception_style) - continue; - bool fits = true; for (int j = 0; j < args.size(); j++) { if (!args[j]->canConvertTo(cf->spec->arg_types[j + 1])) { @@ -1638,14 +1627,22 @@ class NormalObjectType : public ConcreteCompilerType { if (!fits) continue; + if (cf->exception_style != exception_style) { + if (!best_exception_mismatch) + best_exception_mismatch = cf; + continue; + } + found = true; break; } - if (!found && exception_style == CAPI) { - std::string name = g.func_addr_registry.getFuncNameAtAddress(cl->versions[0]->code, true); - RELEASE_ASSERT(0, "Please define a capi variant for %s", name.c_str()); + if (!found) { + assert(best_exception_mismatch); + cf = best_exception_mismatch; + found = true; } + RELEASE_ASSERT(found, ""); RELEASE_ASSERT(cf->code, ""); @@ -1717,10 +1714,7 @@ class NormalObjectType : public ConcreteCompilerType { CompilerVariable* callattr(IREmitter& emitter, const OpInfo& info, ConcreteCompilerVariable* var, BoxedString* attr, CallattrFlags flags, const std::vector& args, const std::vector* keyword_names) override { - ExceptionStyle exception_style = CXX; - // Not safe to force-capi here since most of the functions won't have capi variants: - if (/*FORCE_LLVM_CAPI ||*/ info.unw_info.capi_exc_dest) - exception_style = CAPI; + ExceptionStyle exception_style = info.preferredExceptionStyle(); ConcreteCompilerVariable* called_constant = tryCallattrConstant( emitter, info, var, attr, flags.cls_only, flags.argspec, args, keyword_names, NULL, exception_style); @@ -1783,9 +1777,7 @@ class NormalObjectType : public ConcreteCompilerType { static BoxedString* attr = internStringImmortal("__getitem__"); bool no_attribute = false; - ExceptionStyle exception_style = CXX; - if (FORCE_LLVM_CAPI || info.unw_info.capi_exc_dest) - exception_style = CAPI; + ExceptionStyle exception_style = info.preferredExceptionStyle(); ConcreteCompilerVariable* called_constant = tryCallattrConstant( emitter, info, var, attr, true, ArgPassSpec(1, 0, 0, 0), { slice }, NULL, &no_attribute, exception_style); @@ -2293,9 +2285,7 @@ class TupleType : public ValuedCompilerType rtn->incvref(); return rtn; } else { - ExceptionStyle target_exception_style = CXX; - if (FORCE_LLVM_CAPI || info.unw_info.capi_exc_dest) - target_exception_style = CAPI; + ExceptionStyle target_exception_style = info.preferredExceptionStyle(); if (target_exception_style == CAPI) { llvm::CallSite call = emitter.createCall(info.unw_info, g.funcs.raiseIndexErrorStrCapi, diff --git a/src/codegen/irgen.cpp b/src/codegen/irgen.cpp index 5da9929f3..53a27b4df 100644 --- a/src/codegen/irgen.cpp +++ b/src/codegen/irgen.cpp @@ -495,6 +495,7 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc std::unordered_map phi_ending_symbol_tables; typedef std::unordered_map> PHITable; std::unordered_map created_phis; + std::unordered_map> incoming_exception_state; CFGBlock* initial_block = NULL; if (entry_descriptor) { @@ -759,6 +760,11 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc } } + auto exc_it = incoming_exception_state.find(block); + if (exc_it != incoming_exception_state.end()) { + generator->setIncomingExceptionState(exc_it->second); + } + // Generate loop safepoints on backedges. for (CFGBlock* predecessor : block->predecessors) { if (predecessor->idx > block->idx) { @@ -777,6 +783,15 @@ static void emitBBs(IRGenState* irstate, TypeAnalysis* types, const OSREntryDesc phi_ending_symbol_tables[block] = ending_st.phi_symbol_table; llvm_exit_blocks[block] = ending_st.ending_block; + if (ending_st.exception_state.size()) { + AST_stmt* last_stmt = block->body.back(); + assert(last_stmt->type == AST_TYPE::Invoke); + CFGBlock* exc_block = ast_cast(last_stmt)->exc_dest; + assert(!incoming_exception_state.count(exc_block)); + + incoming_exception_state.insert(std::make_pair(exc_block, ending_st.exception_state)); + } + if (into_hax.count(block)) ASSERT(ending_st.symbol_table->size() == 0, "%d", block->idx); } @@ -952,7 +967,7 @@ static std::string getUniqueFunctionName(std::string nameprefix, EffortLevel eff CompiledFunction* doCompile(CLFunction* clfunc, SourceInfo* source, ParamNames* param_names, const OSREntryDescriptor* entry_descriptor, EffortLevel effort, - FunctionSpecialization* spec, std::string nameprefix) { + ExceptionStyle exception_style, FunctionSpecialization* spec, std::string nameprefix) { Timer _t("in doCompile"); Timer _t2; long irgen_us = 0; @@ -1015,8 +1030,7 @@ CompiledFunction* doCompile(CLFunction* clfunc, SourceInfo* source, ParamNames* } } - - CompiledFunction* cf = new CompiledFunction(NULL, spec, NULL, effort, ExceptionStyle::CXX, entry_descriptor); + CompiledFunction* cf = new CompiledFunction(NULL, spec, NULL, effort, exception_style, entry_descriptor); // Make sure that the instruction memory keeps the module object alive. // TODO: implement this for real diff --git a/src/codegen/irgen.h b/src/codegen/irgen.h index 28769527a..a1f82b7c8 100644 --- a/src/codegen/irgen.h +++ b/src/codegen/irgen.h @@ -20,6 +20,7 @@ #include "llvm/IR/Intrinsics.h" #include "llvm/IR/IRBuilder.h" +#include "core/options.h" #include "core/types.h" namespace pyston { @@ -33,18 +34,18 @@ struct UnwindInfo { public: AST_stmt* current_stmt; - llvm::BasicBlock* capi_exc_dest; - llvm::BasicBlock* cxx_exc_dest; + llvm::BasicBlock* exc_dest; - bool hasHandler() const { return cxx_exc_dest != NULL || capi_exc_dest != NULL; } + bool hasHandler() const { return exc_dest != NULL; } - UnwindInfo(AST_stmt* current_stmt, llvm::BasicBlock* capi_exc_dest, llvm::BasicBlock* cxx_exc_dest) - : current_stmt(current_stmt), capi_exc_dest(capi_exc_dest), cxx_exc_dest(cxx_exc_dest) {} + UnwindInfo(AST_stmt* current_stmt, llvm::BasicBlock* exc_dest) : current_stmt(current_stmt), exc_dest(exc_dest) {} + + ExceptionStyle preferredExceptionStyle() const; // Risky! This means that we can't unwind from this location, and should be used in the // rare case that there are language-specific reasons that the statement should not unwind // (ex: loading function arguments into the appropriate scopes). - static UnwindInfo cantUnwind() { return UnwindInfo(NULL, NULL, NULL); } + static UnwindInfo cantUnwind() { return UnwindInfo(NULL, NULL); } }; // TODO get rid of this @@ -112,7 +113,7 @@ bool isIsDefinedName(llvm::StringRef name); CompiledFunction* doCompile(CLFunction* clfunc, SourceInfo* source, ParamNames* param_names, const OSREntryDescriptor* entry_descriptor, EffortLevel effort, - FunctionSpecialization* spec, std::string nameprefix); + ExceptionStyle exception_style, FunctionSpecialization* spec, std::string nameprefix); // A common pattern is to branch based off whether a variable is defined but only if it is // potentially-undefined. If it is potentially-undefined, we have to generate control-flow @@ -146,6 +147,8 @@ class OpInfo { : effort(effort), type_recorder(type_recorder), unw_info(unw_info) {} TypeRecorder* getTypeRecorder() const { return type_recorder; } + + ExceptionStyle preferredExceptionStyle() const { return unw_info.preferredExceptionStyle(); } }; } diff --git a/src/codegen/irgen/hooks.cpp b/src/codegen/irgen/hooks.cpp index bac6892dc..27a8a979a 100644 --- a/src/codegen/irgen/hooks.cpp +++ b/src/codegen/irgen/hooks.cpp @@ -197,6 +197,12 @@ CompiledFunction* compileFunction(CLFunction* f, FunctionSpecialization* spec, E ASSERT(f->versions.size() < 20, "%s %ld", name.c_str(), f->versions.size()); + ExceptionStyle exception_style = CXX; + if (FORCE_LLVM_CAPI_THROWS) + exception_style = CAPI; + if (name == "next") + exception_style = CAPI; + if (VERBOSITY("irgen") >= 1) { std::string s; llvm::raw_string_ostream ss(s); @@ -223,7 +229,8 @@ CompiledFunction* compileFunction(CLFunction* f, FunctionSpecialization* spec, E ss << "\033[" << colors[(int)effort] << ";1mDoing OSR-entry partial compile of " << source->fn << ":" << name << ", starting with backedge to block " << entry_descriptor->backedge->target->idx; } - ss << " at effort level " << (int)effort << '\n'; + ss << " at effort level " << (int)effort << " with exception style " + << (exception_style == CXX ? "C++" : "CAPI") << '\n'; if (entry_descriptor && VERBOSITY("irgen") >= 2) { for (const auto& p : entry_descriptor->args) { @@ -241,8 +248,7 @@ CompiledFunction* compileFunction(CLFunction* f, FunctionSpecialization* spec, E } - - CompiledFunction* cf = doCompile(f, source, &f->param_names, entry_descriptor, effort, spec, name); + CompiledFunction* cf = doCompile(f, source, &f->param_names, entry_descriptor, effort, exception_style, spec, name); compileIR(cf, effort); f->addVersion(cf); @@ -822,7 +828,9 @@ extern "C" CompiledFunction* reoptCompiledFuncInternal(CompiledFunction* cf) { extern "C" char* reoptCompiledFunc(CompiledFunction* cf) { - return (char*)reoptCompiledFuncInternal(cf)->code; + CompiledFunction* new_cf = reoptCompiledFuncInternal(cf); + assert(new_cf->exception_style == cf->exception_style); + return (char*)new_cf->code; } CLFunction* createRTFunction(int num_args, int num_defaults, bool takes_varargs, bool takes_kwargs, diff --git a/src/codegen/irgen/irgenerator.cpp b/src/codegen/irgen/irgenerator.cpp index 3e70ce8bb..dd74d75bf 100644 --- a/src/codegen/irgen/irgenerator.cpp +++ b/src/codegen/irgen/irgenerator.cpp @@ -94,67 +94,24 @@ llvm::Value* IRGenState::getScratchSpace(int min_bytes) { return scratch_space; } -// This function is where we decide whether to have a certain operation use CAPI or CXX exceptions. -// FIXME It's a bit messy at the moment because this requires coordinating between a couple different -// parts: we need to make sure that the associated landingpad will catch the right kind of exception, -// and we need to make sure that we can actually emit this statement using capi-exceptions. -// It doesn't really belong on the IRGenState, but it's here so that we can access this state from -// separate basic blocks (the IRGenerator only exists for a single bb). -ExceptionStyle IRGenState::getLandingpadStyle(AST_Invoke* invoke) { - assert(!landingpad_styles.count(invoke->exc_dest)); - ExceptionStyle& r = landingpad_styles[invoke->exc_dest]; - // printf("Added %d\n", invoke->exc_dest->idx); - - r = CXX; // default - - assert(invoke->stmt->cxx_exception_count == 0); // could be ok but would be unexpected - - // First, check if we think it makes sense: - bool should = (invoke->cxx_exception_count >= 10 || invoke->stmt->type == AST_TYPE::Raise); - if (!should) - return r; - - // Second, check if we are able to do it: - // (not all code paths support capi exceptions yet) - if (invoke->stmt->type == AST_TYPE::Raise) { - AST_Raise* raise_stmt = ast_cast(invoke->stmt); - // Currently can't do a re-raise with a capi exception: - if (raise_stmt->arg0 && !raise_stmt->arg2) - r = CAPI; - else - r = CXX; - return r; - } +ExceptionStyle UnwindInfo::preferredExceptionStyle() const { + if (FORCE_LLVM_CAPI_CALLS) + return CAPI; - AST_expr* expr = NULL; - - if (invoke->stmt->type == AST_TYPE::Assign) { - expr = ast_cast(invoke->stmt)->value; - } else if (invoke->stmt->type == AST_TYPE::Expr) { - expr = ast_cast(invoke->stmt)->value; - } - - if (!expr) - return r; - - if (expr->type == AST_TYPE::Call) { - r = CAPI; - return r; - } - - if (expr->type == AST_TYPE::Attribute || expr->type == AST_TYPE::Subscript) { - r = CAPI; - return r; - } - - // Some expression type we haven't added yet -- might be worth looking into. - r = CXX; - return r; -} - -ExceptionStyle IRGenState::getLandingpadStyle(CFGBlock* block) { - ASSERT(landingpad_styles.count(block), "%d", block->idx); - return landingpad_styles[block]; + // TODO: I think this makes more sense as a relative percentage rather + // than an absolute threshold, but currently we don't count how many + // times a statement was executed but didn't throw. + // + // In theory this means that eventually anything that throws will be viewed + // as a highly-throwing statement, but I think that this is less bad than + // it might be because the denominator will be roughly fixed since we will + // tend to run this check after executing the statement a somewhat-fixed + // number of times. + // We might want to zero these out after we are done compiling, though. + if (current_stmt->cxx_exception_count >= 10) + return CAPI; + + return CXX; } static llvm::Value* getClosureParentGep(IREmitter& emitter, llvm::Value* closure) { @@ -296,13 +253,26 @@ class IREmitterImpl : public IREmitter { llvm::CallSite emitCall(const UnwindInfo& unw_info, llvm::Value* callee, const std::vector& args, ExceptionStyle target_exception_style) { - if (unw_info.hasHandler() && target_exception_style == CXX) { - assert(unw_info.cxx_exc_dest); + if (target_exception_style == CXX && (unw_info.hasHandler() || irstate->getExceptionStyle() == CAPI)) { + // Create the invoke: llvm::BasicBlock* normal_dest = llvm::BasicBlock::Create(g.context, curblock->getName(), irstate->getLLVMFunction()); + + llvm::BasicBlock* final_exc_dest; + if (unw_info.hasHandler()) { + final_exc_dest = unw_info.exc_dest; + } else { + assert(irstate->getExceptionStyle() == CAPI && "shoudn't have bothered creating an invoke"); + + final_exc_dest = NULL; // signal to reraise as a capi exception + } + + llvm::BasicBlock* exc_dest = irgenerator->getCXXExcDest(final_exc_dest); normal_dest->moveAfter(curblock); - llvm::InvokeInst* rtn = getBuilder()->CreateInvoke(callee, normal_dest, unw_info.cxx_exc_dest, args); + llvm::InvokeInst* rtn = getBuilder()->CreateInvoke(callee, normal_dest, exc_dest, args); + + // Normal case: getBuilder()->SetInsertPoint(normal_dest); curblock = normal_dest; return rtn; @@ -493,37 +463,12 @@ class IREmitterImpl : public IREmitter { = llvm::BasicBlock::Create(g.context, curblock->getName(), irstate->getLLVMFunction()); normal_dest->moveAfter(curblock); - llvm::BasicBlock* exc_dest; - bool exc_caught; - if (unw_info.capi_exc_dest) { - exc_dest = unw_info.capi_exc_dest; - exc_caught = true; - } else { - exc_dest = llvm::BasicBlock::Create(g.context, curblock->getName() + "_exc", irstate->getLLVMFunction()); - exc_dest->moveAfter(curblock); - exc_caught = false; - } + llvm::BasicBlock* exc_dest = irgenerator->getCAPIExcDest(unw_info.exc_dest, unw_info.current_stmt); assert(returned_val->getType() == exc_val->getType()); llvm::Value* check_val = getBuilder()->CreateICmpEQ(returned_val, exc_val); llvm::BranchInst* nullcheck = getBuilder()->CreateCondBr(check_val, exc_dest, normal_dest); - setCurrentBasicBlock(exc_dest); - getBuilder()->CreateCall2(g.funcs.capiExcCaughtInJit, - embedRelocatablePtr(unw_info.current_stmt, g.llvm_aststmt_type_ptr), - embedRelocatablePtr(irstate->getSourceInfo(), g.i8_ptr)); - - if (!exc_caught) { - if (unw_info.cxx_exc_dest) { - // TODO: I'm not sure this gets the tracebacks quite right. this is only for testing though: - assert(FORCE_LLVM_CAPI && "this shouldn't happen in non-FORCE mode"); - createCall(unw_info, g.funcs.reraiseJitCapiExc); - } else { - getBuilder()->CreateCall(g.funcs.reraiseJitCapiExc); - } - getBuilder()->CreateUnreachable(); - } - setCurrentBasicBlock(normal_dest); } @@ -582,6 +527,19 @@ class IRGeneratorImpl : public IRGenerator { CFGBlock* myblock; TypeAnalysis* types; + // These are some special values used for passing exception data between blocks; + // this transfer is not explicitly represented in the CFG which is why it has special + // handling here. ie these variables are how we handle the special "invoke->landingpad" + // value transfer, which doesn't involve the normal symbol name handling. + // + // These are the values that are incoming to a landingpad block: + llvm::SmallVector incoming_exc_state; + // These are the values that are outgoing of an invoke block: + llvm::SmallVector outgoing_exc_state; + llvm::DenseMap cxx_exc_dests; + llvm::DenseMap capi_exc_dests; + llvm::DenseMap capi_current_statements; + enum State { RUNNING, // normal DEAD, // passed a Return statement; still syntatically valid but the code should not be compiled @@ -645,7 +603,7 @@ class IRGeneratorImpl : public IRGenerator { curblock = deopt_bb; emitter.getBuilder()->SetInsertPoint(curblock); - llvm::Value* v = emitter.createCall2(UnwindInfo(current_statement, NULL, NULL), g.funcs.deopt, + llvm::Value* v = emitter.createCall2(UnwindInfo(current_statement, NULL), g.funcs.deopt, embedRelocatablePtr(node, g.llvm_aststmt_type_ptr), node_value); emitter.getBuilder()->CreateRet(v); @@ -695,62 +653,46 @@ class IRGeneratorImpl : public IRGenerator { return boolFromI1(emitter, v); } case AST_LangPrimitive::LANDINGPAD: { - llvm::Value* exc_type; - llvm::Value* exc_value; - llvm::Value* exc_traceback; - if (irstate->getLandingpadStyle(myblock) == CXX) { - // llvm::Function* _personality_func = g.stdlib_module->getFunction("__py_personality_v0"); - llvm::Function* _personality_func = g.stdlib_module->getFunction("__gxx_personality_v0"); - assert(_personality_func); - llvm::Value* personality_func = g.cur_module->getOrInsertFunction( - _personality_func->getName(), _personality_func->getFunctionType()); - assert(personality_func); - llvm::LandingPadInst* landing_pad = emitter.getBuilder()->CreateLandingPad( - llvm::StructType::create(std::vector{ g.i8_ptr, g.i64 }), personality_func, 1); - landing_pad->addClause(getNullPtr(g.i8_ptr)); - - llvm::Value* cxaexc_pointer = emitter.getBuilder()->CreateExtractValue(landing_pad, { 0 }); - - llvm::Function* std_module_catch = g.stdlib_module->getFunction("__cxa_begin_catch"); - auto begin_catch_func = g.cur_module->getOrInsertFunction(std_module_catch->getName(), - std_module_catch->getFunctionType()); - assert(begin_catch_func); - - llvm::Value* excinfo_pointer = emitter.getBuilder()->CreateCall(begin_catch_func, cxaexc_pointer); - llvm::Value* excinfo_pointer_casted - = emitter.getBuilder()->CreateBitCast(excinfo_pointer, g.llvm_excinfo_type->getPointerTo()); - - auto* builder = emitter.getBuilder(); - exc_type = builder->CreateLoad(builder->CreateConstInBoundsGEP2_32(excinfo_pointer_casted, 0, 0)); - exc_value = builder->CreateLoad(builder->CreateConstInBoundsGEP2_32(excinfo_pointer_casted, 0, 1)); - exc_traceback - = builder->CreateLoad(builder->CreateConstInBoundsGEP2_32(excinfo_pointer_casted, 0, 2)); + ConcreteCompilerVariable* exc_type; + ConcreteCompilerVariable* exc_value; + ConcreteCompilerVariable* exc_tb; + + if (this->incoming_exc_state.size()) { + if (incoming_exc_state.size() == 1) { + exc_type = this->incoming_exc_state[0].exc_type; + exc_value = this->incoming_exc_state[0].exc_value; + exc_tb = this->incoming_exc_state[0].exc_tb; + } else { + llvm::PHINode* phi_exc_type + = emitter.getBuilder()->CreatePHI(g.llvm_value_type_ptr, incoming_exc_state.size()); + llvm::PHINode* phi_exc_value + = emitter.getBuilder()->CreatePHI(g.llvm_value_type_ptr, incoming_exc_state.size()); + llvm::PHINode* phi_exc_tb + = emitter.getBuilder()->CreatePHI(g.llvm_value_type_ptr, incoming_exc_state.size()); + for (auto e : this->incoming_exc_state) { + phi_exc_type->addIncoming(e.exc_type->getValue(), e.from_block); + phi_exc_value->addIncoming(e.exc_value->getValue(), e.from_block); + phi_exc_tb->addIncoming(e.exc_tb->getValue(), e.from_block); + } + exc_type = new ConcreteCompilerVariable(UNKNOWN, phi_exc_type, true); + exc_value = new ConcreteCompilerVariable(UNKNOWN, phi_exc_value, true); + exc_tb = new ConcreteCompilerVariable(UNKNOWN, phi_exc_tb, true); + } } else { - llvm::Value* exc_type_ptr - = new llvm::AllocaInst(g.llvm_value_type_ptr, getConstantInt(1, g.i64), "exc_type", - irstate->getLLVMFunction()->getEntryBlock().getFirstInsertionPt()); - llvm::Value* exc_value_ptr - = new llvm::AllocaInst(g.llvm_value_type_ptr, getConstantInt(1, g.i64), "exc_value", - irstate->getLLVMFunction()->getEntryBlock().getFirstInsertionPt()); - llvm::Value* exc_traceback_ptr - = new llvm::AllocaInst(g.llvm_value_type_ptr, getConstantInt(1, g.i64), "exc_traceback", - irstate->getLLVMFunction()->getEntryBlock().getFirstInsertionPt()); - emitter.getBuilder()->CreateCall3(g.funcs.PyErr_Fetch, exc_type_ptr, exc_value_ptr, - exc_traceback_ptr); - // TODO: I think we should be doing this on a python raise() or when we enter a python catch: - emitter.getBuilder()->CreateCall3(g.funcs.PyErr_NormalizeException, exc_type_ptr, exc_value_ptr, - exc_traceback_ptr); - exc_type = emitter.getBuilder()->CreateLoad(exc_type_ptr); - exc_value = emitter.getBuilder()->CreateLoad(exc_value_ptr); - exc_traceback = emitter.getBuilder()->CreateLoad(exc_traceback_ptr); + // There can be no incoming exception if the irgenerator was able to prove that + // an exception would not get thrown. + // For example, the cfg code will conservatively assume that any name-access can + // trigger an exception, but the irgenerator will know that definitely-defined + // local symbols will not throw. + exc_type = undefVariable(); + exc_value = undefVariable(); + exc_tb = undefVariable(); } - assert(exc_type->getType() == g.llvm_value_type_ptr); - assert(exc_value->getType() == g.llvm_value_type_ptr); - assert(exc_traceback->getType() == g.llvm_value_type_ptr); - return makeTuple({ new ConcreteCompilerVariable(UNKNOWN, exc_type, true), - new ConcreteCompilerVariable(UNKNOWN, exc_value, true), - new ConcreteCompilerVariable(UNKNOWN, exc_traceback, true) }); + // clear this out to signal that we consumed them: + this->incoming_exc_state.clear(); + + return makeTuple({ exc_type, exc_value, exc_tb }); } case AST_LangPrimitive::LOCALS: { return new ConcreteCompilerVariable(UNKNOWN, irstate->getBoxedLocalsVar(), true); @@ -2355,7 +2297,7 @@ class IRGeneratorImpl : public IRGenerator { // but ommitting the first argument is *not* the same as passing None. ExceptionStyle target_exception_style = CXX; - if (unw_info.capi_exc_dest || (FORCE_LLVM_CAPI && node->arg0 && !node->arg2)) + if (unw_info.preferredExceptionStyle() == CAPI && (node->arg0 && !node->arg2)) target_exception_style = CAPI; if (node->arg0 == NULL) { @@ -2451,15 +2393,7 @@ class IRGeneratorImpl : public IRGenerator { assert(!unw_info.hasHandler()); AST_Invoke* invoke = ast_cast(node); - ExceptionStyle landingpad_style = irstate->getLandingpadStyle(invoke); - - if (landingpad_style == CXX) - doStmt(invoke->stmt, UnwindInfo(node, NULL, entry_blocks[invoke->exc_dest])); - else { - // print_ast(invoke); - // printf(" (%d exceptions)\n", invoke->cxx_exception_count); - doStmt(invoke->stmt, UnwindInfo(node, entry_blocks[invoke->exc_dest], NULL)); - } + doStmt(invoke->stmt, UnwindInfo(node, entry_blocks[invoke->exc_dest])); assert(state == RUNNING || state == DEAD); if (state == RUNNING) { @@ -2634,17 +2568,20 @@ class IRGeneratorImpl : public IRGenerator { SymbolTable* st = new SymbolTable(symbol_table); ConcreteSymbolTable* phi_st = new ConcreteSymbolTable(); + // This should have been consumed: + assert(incoming_exc_state.empty()); + if (myblock->successors.size() == 0) { for (auto& p : *st) { p.second->decvref(emitter); } st->clear(); symbol_table.clear(); - return EndingState(st, phi_st, curblock); + return EndingState(st, phi_st, curblock, outgoing_exc_state); } else if (myblock->successors.size() > 1) { // Since there are no critical edges, all successors come directly from this node, // so there won't be any required phis. - return EndingState(st, phi_st, curblock); + return EndingState(st, phi_st, curblock, outgoing_exc_state); } assert(myblock->successors.size() == 1); // other cases should have been handled @@ -2654,7 +2591,7 @@ class IRGeneratorImpl : public IRGenerator { // If the next block has a single predecessor, don't have to // emit any phis. // Should probably not emit no-op jumps like this though. - return EndingState(st, phi_st, curblock); + return EndingState(st, phi_st, curblock, outgoing_exc_state); } // We have one successor, but they have more than one predecessor. @@ -2687,7 +2624,7 @@ class IRGeneratorImpl : public IRGenerator { ++it; } } - return EndingState(st, phi_st, curblock); + return EndingState(st, phi_st, curblock, outgoing_exc_state); } void giveLocalSymbol(InternedString name, CompilerVariable* var) override { @@ -2834,7 +2771,7 @@ class IRGeneratorImpl : public IRGenerator { doSafePoint(block->body[i]); #endif - doStmt(block->body[i], UnwindInfo(block->body[i], NULL, NULL)); + doStmt(block->body[i], UnwindInfo(block->body[i], NULL)); } if (VERBOSITY("irgenerator") >= 2) { // print ending symbol table printf(" %d fini:", block->idx); @@ -2847,7 +2784,137 @@ class IRGeneratorImpl : public IRGenerator { void doSafePoint(AST_stmt* next_statement) override { // Unwind info is always needed in allowGLReadPreemption if it has any chance of // running arbitrary code like finalizers. - emitter.createCall(UnwindInfo(next_statement, NULL, NULL), g.funcs.allowGLReadPreemption); + emitter.createCall(UnwindInfo(next_statement, NULL), g.funcs.allowGLReadPreemption); + } + + // Create a (or reuse an existing) block that will catch a CAPI exception, and then forward + // it to the "final_dest" block. ie final_dest is a block corresponding to the IR level + // LANDINGPAD, and this function will createa helper block that fetches the exception. + // As a special-case, a NULL value for final_dest means that this helper block should + // instead propagate the exception out of the function. + llvm::BasicBlock* getCAPIExcDest(llvm::BasicBlock* final_dest, AST_stmt* current_stmt) { + llvm::BasicBlock*& capi_exc_dest = capi_exc_dests[final_dest]; + if (capi_exc_dest) { + assert(capi_current_statements[final_dest] == current_stmt); + return capi_exc_dest; + } + + llvm::BasicBlock* orig_block = curblock; + + capi_exc_dest = llvm::BasicBlock::Create(g.context, "", irstate->getLLVMFunction()); + capi_current_statements[final_dest] = current_stmt; + + emitter.setCurrentBasicBlock(capi_exc_dest); + emitter.getBuilder()->CreateCall2(g.funcs.capiExcCaughtInJit, + embedRelocatablePtr(current_stmt, g.llvm_aststmt_type_ptr), + embedRelocatablePtr(irstate->getSourceInfo(), g.i8_ptr)); + + if (!final_dest) { + // Propagate the exception out of the function: + if (irstate->getExceptionStyle() == CXX) { + emitter.getBuilder()->CreateCall(g.funcs.reraiseJitCapiExc); + emitter.getBuilder()->CreateUnreachable(); + } else { + emitter.getBuilder()->CreateRet(getNullPtr(g.llvm_value_type_ptr)); + } + } else { + // Catch the exception and forward to final_dest: + llvm::Value* exc_type_ptr + = new llvm::AllocaInst(g.llvm_value_type_ptr, getConstantInt(1, g.i64), "exc_type", + irstate->getLLVMFunction()->getEntryBlock().getFirstInsertionPt()); + llvm::Value* exc_value_ptr + = new llvm::AllocaInst(g.llvm_value_type_ptr, getConstantInt(1, g.i64), "exc_value", + irstate->getLLVMFunction()->getEntryBlock().getFirstInsertionPt()); + llvm::Value* exc_traceback_ptr + = new llvm::AllocaInst(g.llvm_value_type_ptr, getConstantInt(1, g.i64), "exc_traceback", + irstate->getLLVMFunction()->getEntryBlock().getFirstInsertionPt()); + emitter.getBuilder()->CreateCall3(g.funcs.PyErr_Fetch, exc_type_ptr, exc_value_ptr, exc_traceback_ptr); + // TODO: I think we should be doing this on a python raise() or when we enter a python catch: + emitter.getBuilder()->CreateCall3(g.funcs.PyErr_NormalizeException, exc_type_ptr, exc_value_ptr, + exc_traceback_ptr); + llvm::Value* exc_type = emitter.getBuilder()->CreateLoad(exc_type_ptr); + llvm::Value* exc_value = emitter.getBuilder()->CreateLoad(exc_value_ptr); + llvm::Value* exc_traceback = emitter.getBuilder()->CreateLoad(exc_traceback_ptr); + + addOutgoingExceptionState( + IRGenerator::ExceptionState(capi_exc_dest, new ConcreteCompilerVariable(UNKNOWN, exc_type, true), + new ConcreteCompilerVariable(UNKNOWN, exc_value, true), + new ConcreteCompilerVariable(UNKNOWN, exc_traceback, true))); + + emitter.getBuilder()->CreateBr(final_dest); + } + + emitter.setCurrentBasicBlock(orig_block); + + return capi_exc_dest; + } + + llvm::BasicBlock* getCXXExcDest(llvm::BasicBlock* final_dest) { + llvm::BasicBlock*& cxx_exc_dest = cxx_exc_dests[final_dest]; + if (cxx_exc_dest) + return cxx_exc_dest; + + llvm::BasicBlock* orig_block = curblock; + + cxx_exc_dest = llvm::BasicBlock::Create(g.context, "", irstate->getLLVMFunction()); + + emitter.getBuilder()->SetInsertPoint(cxx_exc_dest); + + llvm::Function* _personality_func = g.stdlib_module->getFunction("__gxx_personality_v0"); + assert(_personality_func); + llvm::Value* personality_func + = g.cur_module->getOrInsertFunction(_personality_func->getName(), _personality_func->getFunctionType()); + assert(personality_func); + llvm::LandingPadInst* landing_pad = emitter.getBuilder()->CreateLandingPad( + llvm::StructType::create(std::vector{ g.i8_ptr, g.i64 }), personality_func, 1); + landing_pad->addClause(getNullPtr(g.i8_ptr)); + + llvm::Value* cxaexc_pointer = emitter.getBuilder()->CreateExtractValue(landing_pad, { 0 }); + + llvm::Function* std_module_catch = g.stdlib_module->getFunction("__cxa_begin_catch"); + auto begin_catch_func + = g.cur_module->getOrInsertFunction(std_module_catch->getName(), std_module_catch->getFunctionType()); + assert(begin_catch_func); + + llvm::Value* excinfo_pointer = emitter.getBuilder()->CreateCall(begin_catch_func, cxaexc_pointer); + llvm::Value* excinfo_pointer_casted + = emitter.getBuilder()->CreateBitCast(excinfo_pointer, g.llvm_excinfo_type->getPointerTo()); + + auto* builder = emitter.getBuilder(); + llvm::Value* exc_type = builder->CreateLoad(builder->CreateConstInBoundsGEP2_32(excinfo_pointer_casted, 0, 0)); + llvm::Value* exc_value = builder->CreateLoad(builder->CreateConstInBoundsGEP2_32(excinfo_pointer_casted, 0, 1)); + llvm::Value* exc_traceback + = builder->CreateLoad(builder->CreateConstInBoundsGEP2_32(excinfo_pointer_casted, 0, 2)); + + if (final_dest) { + // Catch the exception and forward to final_dest: + addOutgoingExceptionState(ExceptionState(cxx_exc_dest, + new ConcreteCompilerVariable(UNKNOWN, exc_type, true), + new ConcreteCompilerVariable(UNKNOWN, exc_value, true), + new ConcreteCompilerVariable(UNKNOWN, exc_traceback, true))); + + builder->CreateBr(final_dest); + } else { + // Propagate the exception out of the function. + // We shouldn't be hitting this case if the current function is CXX-style; then we should have + // just not created an Invoke and let the exception machinery propagate it for us. + assert(irstate->getExceptionStyle() == CAPI); + builder->CreateCall3(g.funcs.PyErr_Restore, exc_type, exc_value, exc_traceback); + builder->CreateRet(getNullPtr(g.llvm_value_type_ptr)); + } + + emitter.setCurrentBasicBlock(orig_block); + + return cxx_exc_dest; + } + + void addOutgoingExceptionState(ExceptionState exception_state) override { + this->outgoing_exc_state.push_back(exception_state); + } + + void setIncomingExceptionState(llvm::SmallVector exc_state) override { + assert(this->incoming_exc_state.empty()); + this->incoming_exc_state = std::move(exc_state); } }; diff --git a/src/codegen/irgen/irgenerator.h b/src/codegen/irgen/irgenerator.h index f62030b05..7f591ba93 100644 --- a/src/codegen/irgen/irgenerator.h +++ b/src/codegen/irgen/irgenerator.h @@ -72,8 +72,6 @@ class IRGenState { llvm::Value* frame_info_arg; int scratch_size; - llvm::DenseMap landingpad_styles; - public: IRGenState(CLFunction* clfunc, CompiledFunction* cf, SourceInfo* source_info, std::unique_ptr phis, ParamNames* param_names, GCBuilder* gc, llvm::MDNode* func_dbg_info); @@ -82,6 +80,8 @@ class IRGenState { CompiledFunction* getCurFunction() { return cf; } CLFunction* getCL() { return clfunc; } + ExceptionStyle getExceptionStyle() { return cf->exception_style; } + llvm::Function* getLLVMFunction() { return cf->func; } EffortLevel getEffortLevel() { return cf->effort; } @@ -107,15 +107,19 @@ class IRGenState { ParamNames* getParamNames() { return param_names; } void setFrameInfoArgument(llvm::Value* v) { frame_info_arg = v; } - - ExceptionStyle getLandingpadStyle(AST_Invoke* invoke); - ExceptionStyle getLandingpadStyle(CFGBlock* block); }; // turns CFGBlocks into LLVM IR class IRGenerator { private: public: + struct ExceptionState { + llvm::BasicBlock* from_block; + ConcreteCompilerVariable* exc_type, *exc_value, *exc_tb; + ExceptionState(llvm::BasicBlock* from_block, ConcreteCompilerVariable* exc_type, + ConcreteCompilerVariable* exc_value, ConcreteCompilerVariable* exc_tb) + : from_block(from_block), exc_type(exc_type), exc_value(exc_value), exc_tb(exc_tb) {} + }; 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. @@ -123,8 +127,14 @@ class IRGenerator { SymbolTable* symbol_table; ConcreteSymbolTable* phi_symbol_table; llvm::BasicBlock* ending_block; - EndingState(SymbolTable* symbol_table, ConcreteSymbolTable* phi_symbol_table, llvm::BasicBlock* ending_block) - : symbol_table(symbol_table), phi_symbol_table(phi_symbol_table), ending_block(ending_block) {} + llvm::SmallVector exception_state; + + EndingState(SymbolTable* symbol_table, ConcreteSymbolTable* phi_symbol_table, llvm::BasicBlock* ending_block, + llvm::ArrayRef exception_state) + : symbol_table(symbol_table), + phi_symbol_table(phi_symbol_table), + ending_block(ending_block), + exception_state(exception_state.begin(), exception_state.end()) {} }; virtual ~IRGenerator() {} @@ -139,6 +149,10 @@ class IRGenerator { virtual void doSafePoint(AST_stmt* next_statement) = 0; virtual void addFrameStackmapArgs(PatchpointInfo* pp, AST_stmt* current_stmt, std::vector& stackmap_args) = 0; + virtual void addOutgoingExceptionState(ExceptionState exception_state) = 0; + virtual void setIncomingExceptionState(llvm::SmallVector exc_state) = 0; + virtual llvm::BasicBlock* getCXXExcDest(llvm::BasicBlock* final_dest) = 0; + virtual llvm::BasicBlock* getCAPIExcDest(llvm::BasicBlock* final_dest, AST_stmt* current_stmt) = 0; }; class IREmitter; diff --git a/src/codegen/runtime_hooks.cpp b/src/codegen/runtime_hooks.cpp index 15200940e..c142c4aa2 100644 --- a/src/codegen/runtime_hooks.cpp +++ b/src/codegen/runtime_hooks.cpp @@ -311,6 +311,7 @@ void initGlobalFuncs(GlobalState& g) { GET(raise3_capi); GET(PyErr_Fetch); GET(PyErr_NormalizeException); + GET(PyErr_Restore); GET(capiExcCaughtInJit); GET(reraiseJitCapiExc); GET(deopt); diff --git a/src/codegen/runtime_hooks.h b/src/codegen/runtime_hooks.h index 4be8fffe2..7e247d10c 100644 --- a/src/codegen/runtime_hooks.h +++ b/src/codegen/runtime_hooks.h @@ -51,7 +51,7 @@ struct GlobalFuncs { llvm::Value* __cxa_end_catch; llvm::Value* raise0, *raise3, *raise3_capi; - llvm::Value* PyErr_Fetch, *PyErr_NormalizeException, *capiExcCaughtInJit, *reraiseJitCapiExc; + llvm::Value* PyErr_Fetch, *PyErr_NormalizeException, *PyErr_Restore, *capiExcCaughtInJit, *reraiseJitCapiExc; llvm::Value* deopt; llvm::Value* div_float_float, *floordiv_float_float, *mod_float_float, *pow_float_float; diff --git a/src/core/options.cpp b/src/core/options.cpp index b1eec587d..8d0aa5c5e 100644 --- a/src/core/options.cpp +++ b/src/core/options.cpp @@ -45,8 +45,10 @@ bool USE_REGALLOC_BASIC = true; bool PAUSE_AT_ABORT = false; bool ENABLE_TRACEBACKS = true; // Forces the llvm jit to use capi exceptions whenever it can, as opposed to whenever it thinks -// it is faster: -bool FORCE_LLVM_CAPI = false; +// it is faster. The CALLS version is for calls that the llvm jit will make, and the THROWS version +// is for the exceptions it will throw. +bool FORCE_LLVM_CAPI_CALLS = false; +bool FORCE_LLVM_CAPI_THROWS = false; int OSR_THRESHOLD_INTERPRETER = 25; int REOPT_THRESHOLD_INTERPRETER = 25; diff --git a/src/core/options.h b/src/core/options.h index eabe4108d..429335b85 100644 --- a/src/core/options.h +++ b/src/core/options.h @@ -39,7 +39,7 @@ extern int MAX_OBJECT_CACHE_ENTRIES; extern bool SHOW_DISASM, FORCE_INTERPRETER, FORCE_OPTIMIZE, PROFILE, DUMPJIT, TRAP, USE_STRIPPED_STDLIB, CONTINUE_AFTER_FATAL, ENABLE_INTERPRETER, ENABLE_BASELINEJIT, ENABLE_PYPA_PARSER, USE_REGALLOC_BASIC, - PAUSE_AT_ABORT, ENABLE_TRACEBACKS, ASSEMBLY_LOGGING, FORCE_LLVM_CAPI; + PAUSE_AT_ABORT, ENABLE_TRACEBACKS, ASSEMBLY_LOGGING, FORCE_LLVM_CAPI_CALLS, FORCE_LLVM_CAPI_THROWS; extern bool ENABLE_ICS, ENABLE_ICGENERICS, ENABLE_ICGETITEMS, ENABLE_ICSETITEMS, ENABLE_ICDELITEMS, ENABLE_ICBINEXPS, ENABLE_ICNONZEROS, ENABLE_ICCALLSITES, ENABLE_ICSETATTRS, ENABLE_ICGETATTRS, ENALBE_ICDELATTRS, ENABLE_ICGETGLOBALS, diff --git a/src/runtime/generator.cpp b/src/runtime/generator.cpp index f7582ddc0..9f78fe74a 100644 --- a/src/runtime/generator.cpp +++ b/src/runtime/generator.cpp @@ -117,19 +117,33 @@ Box* generatorIter(Box* s) { } // called from both generatorHasNext and generatorSend/generatorNext (but only if generatorHasNext hasn't been called) -static void generatorSendInternal(BoxedGenerator* self, Box* v) { +template static bool generatorSendInternal(BoxedGenerator* self, Box* v) noexcept(S == CAPI) { STAT_TIMER(t0, "us_timer_generator_switching", 0); - if (!self->returnContext && v != None) - raiseExcHelper(TypeError, "can't send non-None value to a just-started generator"); + if (!self->returnContext && v != None) { + if (S == CAPI) { + PyErr_SetString(TypeError, "can't send non-None value to a just-started generator"); + return true; + } else + raiseExcHelper(TypeError, "can't send non-None value to a just-started generator"); + } - if (self->running) - raiseExcHelper(ValueError, "generator already executing"); + if (self->running) { + if (S == CAPI) { + PyErr_SetString(ValueError, "generator already executing"); + return true; + } else + raiseExcHelper(ValueError, "generator already executing"); + } // check if the generator already exited if (self->entryExited) { freeGeneratorStack(self); - raiseExcHelper(StopIteration, (const char*)nullptr); + if (S == CAPI) { + PyErr_SetObject(StopIteration, None); + return true; + } else + raiseExcHelper(StopIteration, (const char*)nullptr); } self->returnValue = v; @@ -158,9 +172,14 @@ static void generatorSendInternal(BoxedGenerator* self, Box* v) { if (self->exception.type) { freeGeneratorStack(self); // don't raise StopIteration exceptions because those are handled specially. - if (!self->exception.matches(StopIteration)) - throw self->exception; - return; + if (!self->exception.matches(StopIteration)) { + if (S == CAPI) { + setCAPIException(self->exception); + return true; + } else + throw self->exception; + } + return false; } if (self->entryExited) { @@ -169,18 +188,21 @@ static void generatorSendInternal(BoxedGenerator* self, Box* v) { // We could directly create the StopIteration exception but we delay creating it because often the caller is not // interested in the exception (=generatorHasnext). If we really need it we will create it inside generatorSend. self->exception = ExcInfo(NULL, NULL, NULL); - return; + return false; } + return false; } -Box* generatorSend(Box* s, Box* v) { +template static Box* generatorSend(Box* s, Box* v) noexcept(S == CAPI) { assert(s->cls == generator_cls); BoxedGenerator* self = static_cast(s); if (self->iterated_from__hasnext__) Py_FatalError(".throw called on generator last advanced with __hasnext__"); - generatorSendInternal(self, v); + bool exc = generatorSendInternal(self, v); + if (S == CAPI && exc) + return NULL; // throw StopIteration if the generator exited if (self->entryExited) { @@ -195,9 +217,19 @@ Box* generatorSend(Box* s, Box* v) { ExcInfo old_exc = self->exception; // Clear the exception for GC purposes: self->exception = ExcInfo(nullptr, nullptr, nullptr); - if (old_exc.type == NULL) - raiseExcHelper(StopIteration, (const char*)nullptr); - throw old_exc; + if (old_exc.type == NULL) { + if (S == CAPI) { + PyErr_SetObject(StopIteration, None); + return NULL; + } else + raiseExcHelper(StopIteration, (const char*)nullptr); + } else { + if (S == CAPI) { + setCAPIException(old_exc); + return NULL; + } else + throw old_exc; + } } return self->returnValue; @@ -223,7 +255,7 @@ Box* generatorThrow(Box* s, BoxedClass* exc_cls, Box* exc_val = nullptr, Box** a throw exc_info; self->exception = exc_info; - return generatorSend(self, None); + return generatorSend(self, None); } Box* generatorClose(Box* s) { @@ -245,7 +277,7 @@ Box* generatorClose(Box* s) { assert(0); // unreachable } -Box* generatorNext(Box* s) { +template static Box* generatorNext(Box* s) noexcept(S == CAPI) { assert(s->cls == generator_cls); BoxedGenerator* self = static_cast(s); @@ -254,7 +286,7 @@ Box* generatorNext(Box* s) { return self->returnValue; } - return generatorSend(s, None); + return generatorSend(s, None); } i1 generatorHasnextUnboxed(Box* s) { @@ -262,7 +294,7 @@ i1 generatorHasnextUnboxed(Box* s) { BoxedGenerator* self = static_cast(s); if (!self->iterated_from__hasnext__) { - generatorSendInternal(self, None); + generatorSendInternal(self, None); self->iterated_from__hasnext__ = true; } @@ -455,13 +487,16 @@ void setupGenerator() { new BoxedFunction(boxRTFunction((void*)generatorIter, typeFromClass(generator_cls), 1))); generator_cls->giveAttr("close", new BoxedFunction(boxRTFunction((void*)generatorClose, UNKNOWN, 1))); - generator_cls->giveAttr("next", new BoxedFunction(boxRTFunction((void*)generatorNext, UNKNOWN, 1))); + + auto generator_next = boxRTFunction((void*)generatorNext, UNKNOWN, 1, ParamNames::empty(), CXX); + addRTFunction(generator_next, (void*)generatorNext, UNKNOWN, CAPI); + generator_cls->giveAttr("next", new BoxedFunction(generator_next)); CLFunction* hasnext = boxRTFunction((void*)generatorHasnextUnboxed, BOOL, 1); addRTFunction(hasnext, (void*)generatorHasnext, BOXED_BOOL); generator_cls->giveAttr("__hasnext__", new BoxedFunction(hasnext)); - generator_cls->giveAttr("send", new BoxedFunction(boxRTFunction((void*)generatorSend, UNKNOWN, 2))); + generator_cls->giveAttr("send", new BoxedFunction(boxRTFunction((void*)generatorSend, UNKNOWN, 2))); auto gthrow = new BoxedFunction(boxRTFunction((void*)generatorThrow, UNKNOWN, 4, 2, false, false), { NULL, NULL }); generator_cls->giveAttr("throw", gthrow); diff --git a/src/runtime/inline/link_forcer.cpp b/src/runtime/inline/link_forcer.cpp index 4e4e64fb1..642666237 100644 --- a/src/runtime/inline/link_forcer.cpp +++ b/src/runtime/inline/link_forcer.cpp @@ -129,6 +129,7 @@ void force() { FORCE(raise3_capi); FORCE(PyErr_Fetch); FORCE(PyErr_NormalizeException); + FORCE(PyErr_Restore); FORCE(capiExcCaughtInJit); FORCE(reraiseJitCapiExc); FORCE(deopt); diff --git a/src/runtime/util.cpp b/src/runtime/util.cpp index 50ce57d30..f85528eff 100644 --- a/src/runtime/util.cpp +++ b/src/runtime/util.cpp @@ -262,6 +262,10 @@ extern "C" void dumpEx(void* p, int levels) { printf("Has %ld function versions\n", cl->versions.size()); for (CompiledFunction* cf : cl->versions) { bool got_name; + if (cf->exception_style == CXX) + printf("CXX style: "); + else + printf("CAPI style: "); std::string name = g.func_addr_registry.getFuncNameAtAddress(cf->code, true, &got_name); if (got_name) printf("%s\n", name.c_str());