diff --git a/gen/irstate.h b/gen/irstate.h index 7c67e4f2..13420561 100644 --- a/gen/irstate.h +++ b/gen/irstate.h @@ -136,8 +136,11 @@ struct IRState llvm::BasicBlock* scopeend(); bool scopereturned(); + // landing pads for try statements + typedef std::vector BBVec; + BBVec landingPads; + // loop blocks - typedef std::vector BBVec; typedef std::vector LoopScopeVec; LoopScopeVec loopbbs; diff --git a/gen/runtime.cpp b/gen/runtime.cpp index 33dee756..29113154 100644 --- a/gen/runtime.cpp +++ b/gen/runtime.cpp @@ -880,4 +880,30 @@ static void LLVM_D_BuildRuntimeModule() llvm::Function::Create(fty, llvm::GlobalValue::ExternalLinkage, fname, M); llvm::Function::Create(fty, llvm::GlobalValue::ExternalLinkage, fname2, M); } + + ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + ///////////////////////////////////////////////////////////////////////////////////// + + // int _d_eh_personality(int ver, int actions, ulong eh_class, ptr eh_info, ptr context) + { + std::string fname("_d_eh_personality"); + std::vector types; + types.push_back(intTy); + types.push_back(intTy); + types.push_back(longTy); + types.push_back(voidPtrTy); + types.push_back(voidPtrTy); + const llvm::FunctionType* fty = llvm::FunctionType::get(intTy, types, false); + llvm::Function::Create(fty, llvm::GlobalValue::ExternalLinkage, fname, M); + } + + // void _d_eh_resume_unwind(ptr exc_struct) + { + std::string fname("_d_eh_resume_unwind"); + std::vector types; + types.push_back(voidPtrTy); + const llvm::FunctionType* fty = llvm::FunctionType::get(voidTy, types, false); + llvm::Function::Create(fty, llvm::GlobalValue::ExternalLinkage, fname, M); + } } diff --git a/gen/statements.cpp b/gen/statements.cpp index 34f7d57c..58113042 100644 --- a/gen/statements.cpp +++ b/gen/statements.cpp @@ -496,14 +496,20 @@ void TryFinallyStatement::toIR(IRState* p) llvm::BasicBlock* trybb = llvm::BasicBlock::Create("try", p->topfunc(), oldend); llvm::BasicBlock* finallybb = llvm::BasicBlock::Create("finally", p->topfunc(), oldend); + // the landing pad for statements in the try block + // only reached via eh-unwinding, a call to resume unwinding is appended + llvm::BasicBlock* unwindfinallybb = llvm::BasicBlock::Create("unwindfinally", p->topfunc(), oldend); llvm::BasicBlock* endbb = llvm::BasicBlock::Create("endtryfinally", p->topfunc(), oldend); // pass the previous BB into this assert(!gIR->scopereturned()); llvm::BranchInst::Create(trybb, p->scopebb()); + // // do the try block + // p->scope() = IRScope(trybb,finallybb); + p->landingPads.push_back(unwindfinallybb); assert(body); body->toIR(p); @@ -512,16 +518,54 @@ void TryFinallyStatement::toIR(IRState* p) if (!p->scopereturned()) llvm::BranchInst::Create(finallybb, p->scopebb()); + p->landingPads.pop_back(); + + // // do finally block - p->scope() = IRScope(finallybb,endbb); + // + p->scope() = IRScope(finallybb,unwindfinallybb); assert(finalbody); finalbody->toIR(p); // terminate finally + //TODO: isn't it an error to have a 'returned' finally block? if (!gIR->scopereturned()) { llvm::BranchInst::Create(endbb, p->scopebb()); } + // + // do landing pad + // + p->scope() = IRScope(unwindfinallybb,endbb); + + // eh_ptr = llvm.eh.exception(); + llvm::Function* eh_exception_fn = GET_INTRINSIC_DECL(eh_exception); + LLValue* eh_ptr = gIR->ir->CreateCall(eh_exception_fn); + + // eh_sel = llvm.eh.selector(eh_ptr, cast(byte*)&_d_eh_personality, 0); + llvm::Function* eh_selector_fn; + if (global.params.is64bit) + eh_selector_fn = GET_INTRINSIC_DECL(eh_selector_i64); + else + eh_selector_fn = GET_INTRINSIC_DECL(eh_selector_i32); + llvm::Function* personality_fn = LLVM_D_GetRuntimeFunction(gIR->module, "_d_eh_personality"); + LLValue* personality_fn_arg = gIR->ir->CreateBitCast(personality_fn, getPtrToType(LLType::Int8Ty)); + LLValue* eh_sel = gIR->ir->CreateCall3(eh_selector_fn, eh_ptr, personality_fn_arg, llvm::ConstantInt::get(LLType::Int32Ty, 0)); + + // emit finally code + finalbody->toIR(p); + + // finally code may not be terminated! + if (gIR->scopereturned()) { + error("finally blocks may not be terminated", loc.toChars()); + fatal(); + } + + llvm::Function* unwind_resume_fn = LLVM_D_GetRuntimeFunction(gIR->module, "_d_eh_resume_unwind"); + gIR->ir->CreateCall(unwind_resume_fn, eh_ptr); + + gIR->ir->CreateUnreachable(); + // rewrite the scope p->scope() = IRScope(endbb,oldend); } @@ -533,8 +577,6 @@ void TryCatchStatement::toIR(IRState* p) Logger::println("TryCatchStatement::toIR(): %s", loc.toChars()); LOG_SCOPE; - Logger::attention(loc, "try-catch is not yet fully implemented"); - if (global.params.symdebug) DtoDwarfStopPoint(loc.linnum); @@ -542,30 +584,114 @@ void TryCatchStatement::toIR(IRState* p) llvm::BasicBlock* oldend = p->scopeend(); llvm::BasicBlock* trybb = llvm::BasicBlock::Create("try", p->topfunc(), oldend); - llvm::BasicBlock* catchbb = llvm::BasicBlock::Create("catch", p->topfunc(), oldend); + // the landing pad will be responsible for branching to the correct catch block + llvm::BasicBlock* landingpadbb = llvm::BasicBlock::Create("landingpad", p->topfunc(), oldend); llvm::BasicBlock* endbb = llvm::BasicBlock::Create("endtrycatch", p->topfunc(), oldend); // pass the previous BB into this assert(!gIR->scopereturned()); llvm::BranchInst::Create(trybb, p->scopebb()); + // // do the try block - p->scope() = IRScope(trybb,catchbb); + // + p->scope() = IRScope(trybb,landingpadbb); + p->landingPads.push_back(landingpadbb); + assert(body); body->toIR(p); if (!gIR->scopereturned()) llvm::BranchInst::Create(endbb, p->scopebb()); - // do catch - p->scope() = IRScope(catchbb,oldend); - llvm::BranchInst::Create(endbb, p->scopebb()); - /*assert(catches); - for(size_t i=0; idim; ++i) + p->landingPads.pop_back(); + + // + // do catches + // + assert(catches); + + // get storage for exception var + const LLType* objectTy = DtoType(ClassDeclaration::object->type); + llvm::AllocaInst* catch_var = new llvm::AllocaInst(objectTy,"catchvar",p->topallocapoint()); + + // for further reference in landing pad + LLSmallVector catch_bbs; + + for (int i = 0; i < catches->dim; i++) { - Catch* c = (Catch*)catches->data[i]; + Catch *c = (Catch *)catches->data[i]; + + llvm::BasicBlock* catchbb = llvm::BasicBlock::Create("catch", p->topfunc(), oldend); + catch_bbs.push_back(catchbb); + p->scope() = IRScope(catchbb,oldend); + + // assign storage to catch var + if(c->var) { + assert(!c->var->ir.irLocal); + c->var->ir.irLocal = new IrLocal(c->var); + c->var->ir.irLocal->value = gIR->ir->CreateBitCast(catch_var, getPtrToType(DtoType(c->var->type))); + } + + // emit handler + assert(c->handler); c->handler->toIR(p); - }*/ + + if (!gIR->scopereturned()) + llvm::BranchInst::Create(endbb, p->scopebb()); + } + + // + // do landing pad + // + p->scope() = IRScope(landingpadbb,endbb); + + // eh_ptr = llvm.eh.exception(); + llvm::Function* eh_exception_fn = GET_INTRINSIC_DECL(eh_exception); + LLValue* eh_ptr = gIR->ir->CreateCall(eh_exception_fn); + + // store eh_ptr in catch_var + gIR->ir->CreateStore(gIR->ir->CreateBitCast(eh_ptr, objectTy), catch_var); + + // eh_sel = llvm.eh.selector(eh_ptr, cast(byte*)&_d_eh_personality, ); + llvm::Function* eh_selector_fn; + if (global.params.is64bit) + eh_selector_fn = GET_INTRINSIC_DECL(eh_selector_i64); + else + eh_selector_fn = GET_INTRINSIC_DECL(eh_selector_i32); + + LLSmallVector args; + args.push_back(eh_ptr); + llvm::Function* personality_fn = LLVM_D_GetRuntimeFunction(gIR->module, "_d_eh_personality"); + args.push_back(gIR->ir->CreateBitCast(personality_fn, getPtrToType(LLType::Int8Ty))); + for (int i = 0; i < catches->dim; i++) + { + Catch *c = (Catch *)catches->data[i]; + assert(c->type); + ClassDeclaration* cdecl = c->type->isClassHandle(); + assert(cdecl); + assert(cdecl->ir.irStruct); + args.push_back(cdecl->ir.irStruct->classInfo); + } + + LLValue* eh_sel = gIR->ir->CreateCall(eh_selector_fn, args.begin(), args.end()); + + // switch on eh_sel and branch to correct case + + // setup default target + llvm::BasicBlock* defaulttarget = llvm::BasicBlock::Create("default", p->topfunc(), oldend); + //TODO: Error handling? + new llvm::UnreachableInst(defaulttarget); + + llvm::SwitchInst* sw = p->ir->CreateSwitch(eh_sel, defaulttarget, catch_bbs.size()); + + // add all catches as cases + for(unsigned int c = 0; c < catch_bbs.size(); ++c) + { + llvm::BasicBlock* casebb = llvm::BasicBlock::Create("case", p->topfunc(), oldend); + llvm::BranchInst::Create(catch_bbs[c], casebb); + sw->addCase(llvm::ConstantInt::get(LLType::Int32Ty, c+1), casebb); + } // rewrite the scope p->scope() = IRScope(endbb,oldend); @@ -578,8 +704,6 @@ void ThrowStatement::toIR(IRState* p) Logger::println("ThrowStatement::toIR(): %s", loc.toChars()); LOG_SCOPE; - Logger::attention(loc, "throw is not yet fully implemented"); - if (global.params.symdebug) DtoDwarfStopPoint(loc.linnum); diff --git a/gen/toir.cpp b/gen/toir.cpp index e420bf63..f36620ee 100644 --- a/gen/toir.cpp +++ b/gen/toir.cpp @@ -1246,37 +1246,81 @@ DValue* CallExp::toElem(IRState* p) //Logger::cout() << "Calling: " << *funcval << '\n'; // call the function - llvm::CallInst* call = llvm::CallInst::Create(funcval, llargs.begin(), llargs.end(), varname, p->scopebb()); - LLValue* retllval = (retinptr) ? llargs[0] : call; + LLValue* retllval; + if(p->landingPads.empty()) + { + llvm::CallInst* call = llvm::CallInst::Create(funcval, llargs.begin(), llargs.end(), varname, p->scopebb()); - if (retinptr && dfn && dfn->func && dfn->func->runTimeHack) { - const LLType* rettype = getPtrToType(DtoType(type)); - if (retllval->getType() != rettype) { - Logger::println("llvmRunTimeHack==true - force casting return value"); - Logger::cout() << "from: " << *retllval->getType() << " to: " << *rettype << '\n'; - retllval = DtoBitCast(retllval, rettype); + retllval = (retinptr) ? llargs[0] : call; + + if (retinptr && dfn && dfn->func && dfn->func->runTimeHack) { + const LLType* rettype = getPtrToType(DtoType(type)); + if (retllval->getType() != rettype) { + Logger::println("llvmRunTimeHack==true - force casting return value"); + Logger::cout() << "from: " << *retllval->getType() << " to: " << *rettype << '\n'; + retllval = DtoBitCast(retllval, rettype); + } } - } - // set calling convention - if (dfn && dfn->func) { - int li = dfn->func->llvmInternal; - if (li != LLVMintrinsic && li != LLVMva_start && li != LLVMva_intrinsic) { + // set calling convention + if (dfn && dfn->func) { + int li = dfn->func->llvmInternal; + if (li != LLVMintrinsic && li != LLVMva_start && li != LLVMva_intrinsic) { + call->setCallingConv(DtoCallingConv(dlink)); + } + } + /*else if (delegateCall) { + call->setCallingConv(DtoCallingConv(dlink)); + }*/ + else if (dfn && dfn->cc != (unsigned)-1) { + call->setCallingConv(dfn->cc); + } + else { call->setCallingConv(DtoCallingConv(dlink)); } - } - /*else if (delegateCall) { - call->setCallingConv(DtoCallingConv(dlink)); - }*/ - else if (dfn && dfn->cc != (unsigned)-1) { - call->setCallingConv(dfn->cc); - } - else { - call->setCallingConv(DtoCallingConv(dlink)); - } - // param attrs - call->setParamAttrs(palist); + // param attrs + call->setParamAttrs(palist); + } + else + { + llvm::BasicBlock* postinvoke = llvm::BasicBlock::Create("postinvoke", p->topfunc(), p->scopeend()); + llvm::InvokeInst* call = llvm::InvokeInst::Create(funcval, postinvoke, *p->landingPads.rbegin(), llargs.begin(), llargs.end(), varname, p->scopebb()); + p->scope() = IRScope(postinvoke, p->scopeend()); + + + //FIXME: Code duplication! + retllval = (retinptr) ? llargs[0] : call; + + if (retinptr && dfn && dfn->func && dfn->func->runTimeHack) { + const LLType* rettype = getPtrToType(DtoType(type)); + if (retllval->getType() != rettype) { + Logger::println("llvmRunTimeHack==true - force casting return value"); + Logger::cout() << "from: " << *retllval->getType() << " to: " << *rettype << '\n'; + retllval = DtoBitCast(retllval, rettype); + } + } + + // set calling convention + if (dfn && dfn->func) { + int li = dfn->func->llvmInternal; + if (li != LLVMintrinsic && li != LLVMva_start && li != LLVMva_intrinsic) { + call->setCallingConv(DtoCallingConv(dlink)); + } + } + /*else if (delegateCall) { + call->setCallingConv(DtoCallingConv(dlink)); + }*/ + else if (dfn && dfn->cc != (unsigned)-1) { + call->setCallingConv(dfn->cc); + } + else { + call->setCallingConv(DtoCallingConv(dlink)); + } + + // param attrs + call->setParamAttrs(palist); + } return new DImValue(type, retllval, isInPlace); } diff --git a/tango/lib/compiler/llvmdc/eh.d b/tango/lib/compiler/llvmdc/eh.d index 7918db4b..adae4376 100644 --- a/tango/lib/compiler/llvmdc/eh.d +++ b/tango/lib/compiler/llvmdc/eh.d @@ -4,14 +4,295 @@ import util.console; +//debug = EH_personality; + private extern(C) void abort(); +private extern(C) int printf(char*, ...); + +// D runtime functions +extern(C) { + int _d_isbaseof(ClassInfo oc, ClassInfo c); +} + +// libunwind stuff +extern(C) +{ + enum _Unwind_Reason_Code + { + NO_REASON = 0, + FOREIGN_EXCEPTION_CAUGHT = 1, + FATAL_PHASE2_ERROR = 2, + FATAL_PHASE1_ERROR = 3, + NORMAL_STOP = 4, + END_OF_STACK = 5, + HANDLER_FOUND = 6, + INSTALL_CONTEXT = 7, + CONTINUE_UNWIND = 8 + } + + enum _Unwind_Action + { + SEARCH_PHASE = 1, + CLEANUP_PHASE = 2, + HANDLER_PHASE = 3, + FORCE_UNWIND = 4 + } + + alias void* _Unwind_Context_Ptr; + + alias void function(_Unwind_Reason_Code, _Unwind_Exception*) _Unwind_Exception_Cleanup_Fn; + + struct _Unwind_Exception + { + char[8] exception_class; + _Unwind_Exception_Cleanup_Fn exception_cleanup; + int private_1; + int private_2; + } + + void _Unwind_Resume(_Unwind_Exception*); + _Unwind_Reason_Code _Unwind_RaiseException(_Unwind_Exception*); + ulong _Unwind_GetLanguageSpecificData(_Unwind_Context_Ptr context); + ulong _Unwind_GetIP(_Unwind_Context_Ptr context); + ulong _Unwind_SetIP(_Unwind_Context_Ptr context, ulong new_value); + ulong _Unwind_SetGR(_Unwind_Context_Ptr context, int index, ulong new_value); + ulong _Unwind_GetRegionStart(_Unwind_Context_Ptr context); +} + + +// helpers +private ubyte* get_uleb128(ubyte* addr, ref size_t res) +{ + res = 0; + size_t bitsize = 0; + + // read as long as high bit is set + while(*addr & 0x80) { + res |= (*addr & 0x7f) << bitsize; + bitsize += 7; + addr += 1; + if(bitsize >= size_t.sizeof*8) + throw new Exception("tried to read uleb128 that exceeded size of size_t"); + } + // read last + if(bitsize != 0 && *addr >= 1 << size_t.sizeof*8 - bitsize) + throw new Exception("tried to read uleb128 that exceeded size of size_t"); + res |= (*addr) << bitsize; + + return addr + 1; +} + +private ubyte* get_sleb128(ubyte* addr, ref ptrdiff_t res) +{ + res = 0; + size_t bitsize = 0; + + // read as long as high bit is set + while(*addr & 0x80) { + res |= (*addr & 0x7f) << bitsize; + bitsize += 7; + addr += 1; + if(bitsize >= size_t.sizeof*8) + throw new Exception("tried to read sleb128 that exceeded size of size_t"); + } + // read last + if(bitsize != 0 && *addr >= 1 << size_t.sizeof*8 - bitsize) + throw new Exception("tried to read sleb128 that exceeded size of size_t"); + res |= (*addr) << bitsize; + + // take care of sign + if(bitsize < size_t.sizeof*8 && ((*addr) & 0x40)) + res |= cast(ptrdiff_t)(-1) ^ ((1 << (bitsize+7)) - 1); + + return addr + 1; +} + + + +struct _d_exception +{ + Object exception_object; + _Unwind_Exception unwind_info; +} + +char[8] _d_exception_class = "LLDCD1\0\0"; + +//TODO: cleanup handling +extern(C) _Unwind_Reason_Code _d_eh_personality(int ver, _Unwind_Action actions, ulong exception_class, _Unwind_Exception* exception_info, _Unwind_Context_Ptr context) +{ + // check ver: the C++ Itanium ABI only allows ver == 1 + if(ver != 1) + return _Unwind_Reason_Code.FATAL_PHASE1_ERROR; + + // check exceptionClass + //TODO: Treat foreign exceptions with more respect + if((cast(char*)&exception_class)[0..8] != _d_exception_class) + return _Unwind_Reason_Code.FATAL_PHASE1_ERROR; + + // find call site table, action table and classinfo table + // Note: callsite and action tables do not contain static-length + // data and will be parsed as needed + // Note: classinfo_table points past the end of the table + ubyte* callsite_table; + ubyte* action_table; + ClassInfo* classinfo_table; + _d_getLanguageSpecificTables(context, callsite_table, action_table, classinfo_table); + + + /* + find landing pad and action table index belonging to ip by walking + the callsite_table + */ + ubyte* callsite_walker = callsite_table; + + // get the instruction pointer + // will be used to find the right entry in the callsite_table + // -1 because it will point past the last instruction + ulong ip = _Unwind_GetIP(context) - 1; + + // address block_start is relative to + ulong region_start = _Unwind_GetRegionStart(context); + + // table entries + uint block_start_offset, block_size; + ulong landing_pad; + size_t action_offset; + + while(true) { + // if we've gone through the list and found nothing... + if(callsite_walker >= action_table) + return _Unwind_Reason_Code.CONTINUE_UNWIND; + + block_start_offset = *cast(uint*)callsite_walker; + block_size = *(cast(uint*)callsite_walker + 1); + landing_pad = *(cast(uint*)callsite_walker + 2); + if(landing_pad) + landing_pad += region_start; + callsite_walker = get_uleb128(callsite_walker + 3*uint.sizeof, action_offset); + + debug(EH_personality_verbose) printf("%d %d %d\n", block_start_offset, block_size, landing_pad); + + // since the list is sorted, as soon as we're past the ip + // there's no handler to be found + if(ip < region_start + block_start_offset) + return _Unwind_Reason_Code.CONTINUE_UNWIND; + + // if we've found our block, exit + if(ip < region_start + block_start_offset + block_size) + break; + } + + debug(EH_personality) printf("Found correct landing pad and actionOffset %d\n", action_offset); + + // now we need the exception's classinfo to find a handler + // the exceptionObject is actually a member of a larger struct that + // the runtime allocated. get that now + _d_exception* exception_struct = cast(_d_exception*)(cast(ubyte*)exception_info - _d_exception.unwind_info.offsetof); + + // if there's no actionOffset and no landingpad, continue unwinding + if(!action_offset && !landing_pad) + return _Unwind_Reason_Code.CONTINUE_UNWIND; + + // if there's no action offset but a landing pad, this is a cleanup handler + else if(!action_offset && landing_pad) { + // but only if we're asked to! + if(!(actions & _Unwind_Action.CLEANUP_PHASE)) + return _Unwind_Reason_Code.CONTINUE_UNWIND; + + debug(EH_personality) printf("Calling cleanup routine...\n"); + + _Unwind_SetGR(context, 0, cast(ulong)exception_struct); + _Unwind_SetIP(context, landing_pad); + return _Unwind_Reason_Code.INSTALL_CONTEXT; + } + + /* + walk action table chain, comparing classinfos using _d_isbaseof + */ + ubyte* action_walker = action_table + action_offset - 1; + + ptrdiff_t ti_offset, next_action_offset; + while(true) { + action_walker = get_sleb128(action_walker, ti_offset); + // it is intentional that we not modify action_walker here + // next_action_offset is from current action_walker position + get_sleb128(action_walker, next_action_offset); + + // negative are 'filters' which we don't use + assert(ti_offset >= 0); + + //TODO: Implement cleanups + assert(ti_offset != 0); + + // get classinfo for action and check if the one in the + // exception structure is a base + ClassInfo catch_ci = classinfo_table[-ti_offset]; + debug(EH_personality) printf("Comparing catch %s to exception %s\n", catch_ci.name.ptr, exception_struct.exception_object.classinfo.name.ptr); + if(_d_isbaseof(exception_struct.exception_object.classinfo, catch_ci)) + return _d_eh_success(actions, ti_offset, landing_pad, exception_struct, context); + + // we've walked through all actions and found nothing... + if(next_action_offset == 0) + return _Unwind_Reason_Code.CONTINUE_UNWIND; + else + action_walker += next_action_offset; + } + + assert(false); +} + +private _Unwind_Reason_Code _d_eh_success(_Unwind_Action actions, ptrdiff_t switchval, ulong landing_pad, _d_exception* exception_struct, _Unwind_Context_Ptr context) +{ + debug(EH_personality) printf("Found catch clause!\n"); + + if(actions & _Unwind_Action.SEARCH_PHASE) + return _Unwind_Reason_Code.HANDLER_FOUND; + else if(actions & _Unwind_Action.HANDLER_PHASE) + { + //TODO: Set sensible value for eh_ptr + _Unwind_SetGR(context, 0, cast(ulong)cast(void*)(exception_struct.exception_object)); + _Unwind_SetGR(context, 2, switchval); + _Unwind_SetIP(context, landing_pad); + return _Unwind_Reason_Code.INSTALL_CONTEXT; + } + + assert(false); +} + +private void _d_getLanguageSpecificTables(_Unwind_Context_Ptr context, ref ubyte* callsite, ref ubyte* action, ref ClassInfo* ci) +{ + ubyte* data = cast(ubyte*)_Unwind_GetLanguageSpecificData(context); + + //TODO: Do proper DWARF reading here + assert(*data++ == 0xff); + + assert(*data++ == 0x00); + size_t cioffset; + data = get_uleb128(data, cioffset); + ci = cast(ClassInfo*)(data + cioffset); + + assert(*data++ == 0x03); + size_t callsitelength; + data = get_uleb128(data, callsitelength); + action = data + callsitelength; + + callsite = data; +} extern(C) void _d_throw_exception(Object e) { - console("Exception: "); if (e !is null) { - console(e.toString())("\n"); + _d_exception* exc_struct = new _d_exception; + exc_struct.unwind_info.exception_class[] = _d_exception_class; + exc_struct.exception_object = e; + _Unwind_Reason_Code ret = _Unwind_RaiseException(&exc_struct.unwind_info); + console("_Unwind_RaiseException failed with reason code: ")(ret)("\n"); } abort(); } + +extern(C) void _d_eh_resume_unwind(_d_exception* exception_struct) +{ + _Unwind_Resume(&exception_struct.unwind_info); +}