From 0268c378754990e9d2669cfe050622e6b6ad149d Mon Sep 17 00:00:00 2001 From: Demetrius Kanios Date: Sun, 28 Jun 2026 01:21:27 -0700 Subject: [PATCH] Add support for Wasm EH --- gen/irstate.cpp | 4 + gen/irstate.h | 1 + gen/runtime.cpp | 12 ++ gen/trycatchfinally.cpp | 233 ++++++++++++++++++++++--- gen/trycatchfinally.h | 29 +-- runtime/CMakeLists.txt | 8 +- runtime/druntime/src/ldc/eh_asm.S | 15 ++ runtime/druntime/src/ldc/eh_wasm.d | 35 ---- runtime/druntime/src/ldc/intrinsics.di | 3 + runtime/druntime/src/rt/dwarfeh.d | 187 +++++++++++++------- 10 files changed, 402 insertions(+), 125 deletions(-) delete mode 100644 runtime/druntime/src/ldc/eh_wasm.d diff --git a/gen/irstate.cpp b/gen/irstate.cpp index 7051ed89e17..f639ea3cd12 100644 --- a/gen/irstate.cpp +++ b/gen/irstate.cpp @@ -349,3 +349,7 @@ IRBuilder<> *IRBuilderHelper::operator->() { bool useMSVCEH() { return global.params.targetTriple->isWindowsMSVCEnvironment(); } + +bool useWasmEH() { + return global.params.targetTriple->isWasm(); +} diff --git a/gen/irstate.h b/gen/irstate.h index 0e8983a9f71..0bfcf3cbe81 100644 --- a/gen/irstate.h +++ b/gen/irstate.h @@ -289,3 +289,4 @@ struct IRState { void Statement_toIR(Statement *s, IRState *irs); bool useMSVCEH(); +bool useWasmEH(); diff --git a/gen/runtime.cpp b/gen/runtime.cpp index 0ba32edb8c1..16c71d9fcaf 100644 --- a/gen/runtime.cpp +++ b/gen/runtime.cpp @@ -730,6 +730,10 @@ static void buildRuntimeModule() { // (int state, ptr ucb, ptr context) createFwdDecl(LINK::c, intTy, {"_d_eh_personality"}, {intTy, voidPtrTy, voidPtrTy}); + } else if (global.params.targetTriple->isWasm()) { + // (int ver, int actions, ulong eh_class, ptr eh_info, ptr context) + createFwdDecl(LINK::c, intTy, {"__gxx_wasm_personality_v0"}, + {intTy, intTy, ulongTy, voidPtrTy, voidPtrTy}); } else { // (int ver, int actions, ulong eh_class, ptr eh_info, ptr context) createFwdDecl(LINK::c, intTy, {"_d_eh_personality"}, @@ -747,6 +751,14 @@ static void buildRuntimeModule() { // Throwable _d_eh_enter_catch(ptr exception, ClassInfo catchType) createFwdDecl(LINK::c, throwableTy, {"_d_eh_enter_catch"}, {voidPtrTy, classInfoTy}, {}); + } else if (useWasmEH()) { + // Throwable _d_eh_enter_catch(ptr) + createFwdDecl(LINK::c, throwableTy, {"_d_eh_enter_catch"}, {voidPtrTy}, {}, + Attr_NoUnwind); + + // void* __cxa_begin_catch(ptr) + createFwdDecl(LINK::c, voidPtrTy, {"__cxa_begin_catch"}, {voidPtrTy}, {}, + Attr_NoUnwind); } else { // void _Unwind_Resume(ptr) createFwdDecl(LINK::c, voidTy, {getUnwindResumeFunctionName()}, {voidPtrTy}, diff --git a/gen/trycatchfinally.cpp b/gen/trycatchfinally.cpp index c3e36648602..2ba423c1c92 100644 --- a/gen/trycatchfinally.cpp +++ b/gen/trycatchfinally.cpp @@ -9,6 +9,9 @@ #include "gen/trycatchfinally.h" +#include "llvm/IR/IntrinsicsWebAssembly.h" + +#include "dmd/identifier.h" #include "dmd/errors.h" #include "dmd/expression.h" #include "dmd/statement.h" @@ -42,6 +45,10 @@ TryCatchScope::TryCatchScope(IRState &irs, llvm::Value *ehPtrSlot, return true; }); + if (useWasmEH()) { + emitCatchBodiesWasm(irs, ehPtrSlot); + return; + } if (useMSVCEH()) { emitCatchBodiesMSVC(irs, ehPtrSlot); return; @@ -309,11 +316,163 @@ void TryCatchScope::emitCatchBodiesMSVC(IRState &irs, llvm::Value *) { } } +void TryCatchScope::emitCatchBodiesWasm(IRState &irs, llvm::Value *) { + // Clang CGException.cpp: + // > Wasm uses Windows-style EH instructions, but it merges all catch clauses into + // > one big catchpad, within which we use Itanium's landingpad-style selector + // > comparison instructions. + + assert(catchBlocks.empty()); + + auto &scopes = irs.funcGen().scopes; + + auto catchSwitchBlock = irs.insertBBBefore(endbb, "catch.dispatch"); + llvm::BasicBlock *unwindto = + scopes.currentCleanupScope() > 0 ? scopes.getLandingPad() : nullptr; + auto catchSwitchInst = llvm::CatchSwitchInst::Create( + llvm::ConstantTokenNone::get(irs.context()), unwindto, + stmt->catches->length, "", catchSwitchBlock); + + auto catchStartBlock = irs.insertBBBefore(endbb, "catch.start"); + catchSwitchInst->addHandler(catchStartBlock); + + llvm::SmallVector catchTypes; + for (auto c : *stmt->catches) { + ClassDeclaration *cd = c->type->toBasetype()->isClassHandle(); + + DtoResolveClass(cd); + + LLGlobalVariable *ci; + if (cd->isCPPclass()) { + // Wrap std::type_info pointers inside a __cpp_type_info_ptr class + // instance so that the personality routine may differentiate C++ catch + // clauses from D ones. + const auto wrapperMangle = + getIRMangledAggregateName(cd, "18_cpp_type_info_ptr"); + + ci = irs.module.getGlobalVariable(wrapperMangle); + if (!ci) { + const char *name = target.cpp.typeInfoMangle(cd); + auto cpp_ti = declareGlobal( + cd->loc, irs.module, getOpaquePtrType(), name, + /*isConstant*/ true, false, /*useDLLImport*/cd->isExport()); + + const auto cppTypeInfoPtrType = getCppTypeInfoPtrType(); + RTTIBuilder b(cppTypeInfoPtrType); + b.push(cpp_ti); + + auto wrapperType = llvm::cast( + getIrType(cppTypeInfoPtrType)->isClass()->getMemoryLLType()); + auto wrapperInit = b.get_constant(wrapperType); + + ci = defineGlobal(cd->loc, irs.module, wrapperMangle, wrapperInit, + LLGlobalValue::LinkOnceODRLinkage, + /*isConstant=*/true); + } + } else { + ci = getIrAggr(cd)->getClassInfoSymbol(); + } + + catchTypes.push_back(ci); + } + + irs.ir->SetInsertPoint(catchStartBlock); + + auto catchpad = irs.ir->CreateCatchPad(catchSwitchInst, catchTypes, ""); + + llvm::Value *ehPtr = irs.ir->CreateCall( + GET_INTRINSIC_DECL(wasm_get_exception, {}), catchpad); + llvm::Value *ehPtrSlot = scopes.getOrCreateEhPtrSlot(); + irs.ir->CreateStore(ehPtr, ehPtrSlot); + + llvm::Value *ehSelector = irs.ir->CreateCall( + GET_INTRINSIC_DECL(wasm_get_ehselector, {}), catchpad); + const auto ehSelectorType = ehSelector->getType(); + llvm::Value *ehSelectorSlot = scopes.getOrCreateEhSelectorSlot(ehSelectorType); + irs.ir->CreateStore(ehSelector, ehSelectorSlot); + + size_t catchIdx = 0; + for (auto c : *stmt->catches) { + auto catchBB = + irs.insertBBBefore(endbb, llvm::Twine("catch.") + c->type->toChars()); + + llvm::BasicBlock *mismatchBB = + irs.insertBB(catchBB->getName() + llvm::Twine(".mismatch")); + + // "Call" llvm.eh.typeid.for, which gives us the eh selector value to + // compare the landing pad selector value with. + llvm::Value *ehTypeId = irs.ir->CreateCall( + GET_INTRINSIC_DECL(eh_typeid_for, catchTypes[catchIdx]->getType()), catchTypes[catchIdx]); + + // Compare the selector value from the unwinder against the expected + // one and branch accordingly. + irs.ir->CreateCondBr( + irs.ir->CreateICmpEQ( + irs.ir->CreateLoad(ehSelectorType, ehSelectorSlot), ehTypeId), + catchBB, mismatchBB); + + irs.ir->SetInsertPoint(catchBB); + irs.DBuilder.EmitBlockStart(c->loc); + + const auto cd = c->type->toBasetype()->isClassHandle(); + const bool isCPPclass = cd->isCPPclass(); + + const auto enterCatchFn = getRuntimeFunction( + c->loc, irs.module, + isCPPclass ? "__cxa_begin_catch" : "_d_eh_enter_catch"); + const auto ptr = DtoLoad(getOpaquePtrType(), ehPtrSlot); + const auto throwableObj = irs.ir->CreateCall(enterCatchFn, ptr, {llvm::OperandBundleDef("funclet", catchpad)}); + + if (c->var) { + // This will alloca if we haven't already and take care of nested refs + // if there are any. + DtoVarDeclaration(c->var); + + // Copy the exception reference over from the _d_eh_enter_catch return + // value. + DtoStore(DtoBitCast(throwableObj, DtoType(c->var->type)), + getIrLocal(c->var)->value); + } + + llvm::BasicBlock *catchHandlerBB = irs.insertBB(catchBB->getName() + llvm::Twine(".handler")); + llvm::CatchReturnInst::Create(catchpad, catchHandlerBB, irs.scopebb()); + irs.ir->SetInsertPoint(catchHandlerBB); + + // Emit handler, if there is one. The handler is zero, for instance, + // when building 'catch { debug foo(); }' in non-debug mode. + if (c->handler) + Statement_toIR(c->handler, &irs); + + if (!irs.scopereturned()) + irs.ir->CreateBr(endbb); + + irs.DBuilder.EmitBlockEnd(); + + irs.ir->SetInsertPoint(mismatchBB); + + catchIdx += 1; + } + + irs.ir->CreateCall(GET_INTRINSIC_DECL(wasm_rethrow, {}), {}, {llvm::OperandBundleDef("funclet", catchpad)}, ""); + irs.ir->CreateUnreachable(); + + scopes.pushCleanup(catchSwitchBlock, catchSwitchBlock); + + // if no landing pad is created, the catch blocks are unused, but + // the verifier complains if there are catchpads without personality + // so we can just set it unconditionally + if (!irs.func()->hasLLVMPersonalityFn()) { + const char *personality = "__gxx_wasm_personality_v0"; + irs.func()->setLLVMPersonalityFn( + getRuntimeFunction(stmt->loc, irs.module, personality)); + } +} + //////////////////////////////////////////////////////////////////////////////// CleanupScope::CleanupScope(llvm::BasicBlock *beginBlock, llvm::BasicBlock *endBlock) { - if (useMSVCEH()) { + if (useMSVCEH() || useWasmEH()) { findSuccessors(blocks, beginBlock, endBlock); return; } @@ -338,7 +497,7 @@ llvm::Instruction *getTerminatorPos(llvm::BasicBlock *bb) { llvm::BasicBlock *CleanupScope::run(IRState &irs, llvm::BasicBlock *sourceBlock, llvm::BasicBlock *continueWith) { - if (useMSVCEH()) + if (useMSVCEH() || useWasmEH()) return runCopying(irs, sourceBlock, continueWith); if (exitTargets.empty() || (exitTargets.size() == 1 && @@ -413,7 +572,7 @@ llvm::BasicBlock *CleanupScope::run(IRState &irs, llvm::BasicBlock *sourceBlock, llvm::cast(endBlock()->getTerminatorOrNull()) #else llvm::cast(endBlock()->getTerminator()) -#endif +#endif ->addCase(selectorVal, continueWith); // ... insert the store into the source block... @@ -517,13 +676,13 @@ void TryCatchFinallyScopes::pushTryCatch(TryCatchStatement *stmt, // catches. tryCatchScopes.push_back(scope); - if (!useMSVCEH()) + if (!useMSVCEH() && !useWasmEH()) landingPadsPerCleanupScope[currentCleanupScope()].push_back(nullptr); } void TryCatchFinallyScopes::popTryCatch() { tryCatchScopes.pop_back(); - if (useMSVCEH()) { + if (useMSVCEH() || useWasmEH()) { assert(isCatchSwitchBlock(cleanupScopes.back().beginBlock())); popCleanups(currentCleanupScope() - 1); } else { @@ -603,7 +762,7 @@ void TryCatchFinallyScopes::runCleanups(CleanupCursor targetScope, void TryCatchFinallyScopes::runCleanups(CleanupCursor sourceScope, CleanupCursor targetScope, llvm::BasicBlock *continueWith) { - if (useMSVCEH()) { + if (useMSVCEH() || useWasmEH()) { runCleanupCopies(sourceScope, targetScope, continueWith); return; } @@ -706,6 +865,11 @@ namespace { } llvm::BasicBlock *TryCatchFinallyScopes::emitLandingPad() { + if (useWasmEH()) { + assert(currentCleanupScope() > 0); + return emitLandingPadWasm(currentCleanupScope() - 1); + } + if (useMSVCEH()) { assert(currentCleanupScope() > 0); return emitLandingPadMSVC(currentCleanupScope() - 1); @@ -728,9 +892,7 @@ llvm::BasicBlock *TryCatchFinallyScopes::emitLandingPad() { llvm::Value *ehSelector = DtoExtractValue(landingPad, 1); const auto ehSelectorType = ehSelector->getType(); - if (!ehSelectorSlot) - ehSelectorSlot = DtoRawAlloca(ehSelectorType, 0, "eh.selector"); - irs.ir->CreateStore(ehSelector, ehSelectorSlot); + irs.ir->CreateStore(ehSelector, getOrCreateEhSelectorSlot(ehSelectorType)); // Add landingpad clauses, emit finallys and 'if' chain to catch the // exception. @@ -798,6 +960,12 @@ llvm::AllocaInst *TryCatchFinallyScopes::getOrCreateEhPtrSlot() { return ehPtrSlot; } +llvm::AllocaInst *TryCatchFinallyScopes::getOrCreateEhSelectorSlot(llvm::Type *ty) { + if (!ehSelectorSlot) + ehSelectorSlot = DtoRawAlloca(ty, 0, "eh.selector"); + return ehSelectorSlot; +} + llvm::BasicBlock *TryCatchFinallyScopes::getOrCreateResumeUnwindBlock() { if (!resumeUnwindBlock) { resumeUnwindBlock = irs.insertBB("eh.resume"); @@ -832,6 +1000,24 @@ TryCatchFinallyScopes::emitLandingPadMSVC(CleanupCursor cleanupScope) { return runCleanupPad(cleanupScope, pad); } +llvm::BasicBlock * +TryCatchFinallyScopes::emitLandingPadWasm(CleanupCursor cleanupScope) { + if (!irs.func()->hasLLVMPersonalityFn()) { + const char *personality = "__gxx_wasm_personality_v0"; + irs.func()->setLLVMPersonalityFn( + getRuntimeFunction(Loc(), irs.module, personality)); + } + + if (cleanupScope == 0) + return runCleanupPad(cleanupScope, nullptr); + + llvm::BasicBlock *&pad = getLandingPadRef(cleanupScope); + if (!pad) + pad = emitLandingPadWasm(cleanupScope - 1); + + return runCleanupPad(cleanupScope, pad); +} + llvm::BasicBlock * TryCatchFinallyScopes::runCleanupPad(CleanupCursor scope, llvm::BasicBlock *unwindTo) { @@ -869,22 +1055,29 @@ TryCatchFinallyScopes::runCleanupPad(CleanupCursor scope, const auto savedInsertPoint = irs.saveInsertPoint(); - auto endFn = getRuntimeFunction(Loc(), irs.module, "_d_leave_cleanup"); - irs.ir->SetInsertPoint(cleanupret); - irs.DBuilder.EmitStopPoint(irs.func()->decl->loc); - irs.ir->CreateCall(endFn, frame, - {llvm::OperandBundleDef("funclet", cleanuppad)}, ""); + if (!useWasmEH()) { + auto endFn = getRuntimeFunction(Loc(), irs.module, "_d_leave_cleanup"); + irs.ir->SetInsertPoint(cleanupret); + irs.DBuilder.EmitStopPoint(irs.func()->decl->loc); + irs.ir->CreateCall(endFn, frame, + {llvm::OperandBundleDef("funclet", cleanuppad)}, ""); + } + llvm::CleanupReturnInst::Create(cleanuppad, unwindTo, cleanupret); auto copybb = cleanupScopes[scope].runCopying(irs, cleanupbb, cleanupret, unwindTo, cleanuppad); - auto beginFn = getRuntimeFunction(Loc(), irs.module, "_d_enter_cleanup"); - irs.ir->SetInsertPoint(cleanupbb); - irs.DBuilder.EmitStopPoint(irs.func()->decl->loc); - auto exec = irs.ir->CreateCall( - beginFn, frame, {llvm::OperandBundleDef("funclet", cleanuppad)}, ""); - createBranch(exec, copybb, cleanupret, cleanupbb); + if (useWasmEH()) { + createBranch(copybb, cleanupbb); + } else { + auto beginFn = getRuntimeFunction(Loc(), irs.module, "_d_enter_cleanup"); + irs.ir->SetInsertPoint(cleanupbb); + irs.DBuilder.EmitStopPoint(irs.func()->decl->loc); + auto exec = irs.ir->CreateCall( + beginFn, frame, {llvm::OperandBundleDef("funclet", cleanuppad)}, ""); + createBranch(exec, copybb, cleanupret, cleanupbb); + } return cleanupbb; } diff --git a/gen/trycatchfinally.h b/gen/trycatchfinally.h index 07aeda7d3e5..f80fb435094 100644 --- a/gen/trycatchfinally.h +++ b/gen/trycatchfinally.h @@ -24,6 +24,7 @@ class BasicBlock; class GlobalVariable; class MDNode; class Value; +class Type; } /// Represents a position on the stack of currently active cleanup scopes. @@ -76,6 +77,7 @@ class TryCatchScope { void emitCatchBodies(IRState &irs, llvm::Value *ehPtrSlot); void emitCatchBodiesMSVC(IRState &irs, llvm::Value *ehPtrSlot); + void emitCatchBodiesWasm(IRState &irs, llvm::Value *ehPtrSlot); }; //////////////////////////////////////////////////////////////////////////////// @@ -243,6 +245,18 @@ class TryCatchFinallyScopes { /// If there's no cached one, a new one will be emitted. llvm::BasicBlock *getLandingPad(); + /// Returns the stack slot that contains the exception object pointer while a + /// landing pad is active, lazily creating it as needed. + /// + /// This value must dominate all uses; first storing it, and then loading it + /// when calling _d_eh_resume_unwind. If we take a select at the end of any + /// cleanups on the way to the latter, the value must also dominate all other + /// predecessors of the cleanup. Thus, we just use a single alloca in the + /// entry BB of the function. + llvm::AllocaInst *getOrCreateEhPtrSlot(); + + /// Similar story to getOrCreateEhPtrSlot, but for the selector value. + llvm::AllocaInst *getOrCreateEhSelectorSlot(llvm::Type *ty); private: IRState &irs; llvm::AllocaInst *ehPtrSlot = nullptr; @@ -287,16 +301,6 @@ class TryCatchFinallyScopes { void runCleanups(CleanupCursor sourceScope, CleanupCursor targetScope, llvm::BasicBlock *continueWith); - /// Returns the stack slot that contains the exception object pointer while a - /// landing pad is active, lazily creating it as needed. - /// - /// This value must dominate all uses; first storing it, and then loading it - /// when calling _d_eh_resume_unwind. If we take a select at the end of any - /// cleanups on the way to the latter, the value must also dominate all other - /// predecessors of the cleanup. Thus, we just use a single alloca in the - /// entry BB of the function. - llvm::AllocaInst *getOrCreateEhPtrSlot(); - /// Returns the basic block with the call to the unwind resume function. /// /// Because of ehPtrSlot, we do not need more than one, so might as well @@ -305,6 +309,11 @@ class TryCatchFinallyScopes { // MSVC llvm::BasicBlock *emitLandingPadMSVC(CleanupCursor cleanupScope); + + // Wasm + llvm::BasicBlock *emitLandingPadWasm(CleanupCursor cleanupScope); + + // MSVC & Wasm void runCleanupCopies(CleanupCursor sourceScope, CleanupCursor targetScope, llvm::BasicBlock *continueWith); llvm::BasicBlock *runCleanupPad(CleanupCursor scope, diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index ba5fe6b759f..f0213cbbc61 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -81,7 +81,7 @@ if("${TARGET_SYSTEM}" STREQUAL "AUTO") endif() endif() -if("${TARGET_SYSTEM}" MATCHES "UNIX") +if("${TARGET_SYSTEM}" MATCHES "UNIX|WASI") ENABLE_LANGUAGE(ASM) endif() @@ -230,6 +230,12 @@ if("${TARGET_SYSTEM}" MATCHES "UNIX") ) endif() +if("${TARGET_SYSTEM}" MATCHES "WASI") + list(APPEND DRUNTIME_ASM + ${RUNTIME_DIR}/src/ldc/eh_asm.S + ) +endif() + if(PHOBOS2_DIR) # Phobos D parts file(GLOB_RECURSE PHOBOS2_D_ETC ${PHOBOS2_DIR}/etc/*.d) diff --git a/runtime/druntime/src/ldc/eh_asm.S b/runtime/druntime/src/ldc/eh_asm.S index e303a77f2b5..a09db581080 100644 --- a/runtime/druntime/src/ldc/eh_asm.S +++ b/runtime/druntime/src/ldc/eh_asm.S @@ -51,3 +51,18 @@ _d_eh_resume_unwind: pop {r1-r3,lr} // restore regs to state at entry b _Unwind_Resume // r0 has returned ucb #endif //__ARM_EABI + +// TODO: remove this when we can link against libunwind +#if __wasm__ +.globl __cpp_exception + +#if defined(__wasm32__) + .tagtype __cpp_exception i32 +#elif defined(__wasm64__) + .tagtype __cpp_exception i64 +#else +#error "Unsupported Wasm architecture" +#endif +__cpp_exception: + +#endif //__wasm__ diff --git a/runtime/druntime/src/ldc/eh_wasm.d b/runtime/druntime/src/ldc/eh_wasm.d deleted file mode 100644 index 59dd74f4ee8..00000000000 --- a/runtime/druntime/src/ldc/eh_wasm.d +++ /dev/null @@ -1,35 +0,0 @@ -/** - * This module implements the runtime-part of LDC exceptions - * on WebAssembly (Wasm EH) - */ - -module ldc.eh_wasm; - -version (WebAssembly): - -extern (C) void _d_throw_exception(Throwable t) { - import core.stdc.stdio : fwrite, stdout, putc; - import ldc.intrinsics : llvm_trap; - - auto msg = t.toString(); - fwrite(msg.ptr, msg.length, 1, stdout); - putc('\n', stdout); - - llvm_trap(); -} - -extern (C) void _Unwind_Resume(void*) { - import core.stdc.stdio : puts; - import ldc.intrinsics : llvm_trap; - - puts("Cannot EH unwind on Wasm (yet)."); - llvm_trap(); -} - -extern (C) void _d_eh_enter_catch(void*) { - import core.stdc.stdio : puts; - import ldc.intrinsics : llvm_trap; - - puts("Cannot EH catch on Wasm (yet)."); - llvm_trap(); -} diff --git a/runtime/druntime/src/ldc/intrinsics.di b/runtime/druntime/src/ldc/intrinsics.di index 74f2eda256c..7aa5e01c522 100644 --- a/runtime/druntime/src/ldc/intrinsics.di +++ b/runtime/druntime/src/ldc/intrinsics.di @@ -734,4 +734,7 @@ pragma(LDC_intrinsic, "llvm.wasm.memory.grow.i32") /// https://webassembly.github.io/spec/core/exec/instructions.html#exec-memory-size pragma(LDC_intrinsic, "llvm.wasm.memory.size.i32") int llvm_wasm_memory_size(int mem); + +pragma(LDC_intrinsic, "llvm.wasm.throw") + void llvm_wasm_throw(uint tag, void* ex); } // version (WebAssembly) diff --git a/runtime/druntime/src/rt/dwarfeh.d b/runtime/druntime/src/rt/dwarfeh.d index 31671c23e9e..456b46a48e8 100644 --- a/runtime/druntime/src/rt/dwarfeh.d +++ b/runtime/druntime/src/rt/dwarfeh.d @@ -11,9 +11,12 @@ module rt.dwarfeh; -version (Posix): +version (Posix) version = DwarfEH; +else version (WebAssembly) version = DwarfEH; -// debug = EH_personality; +version (DwarfEH): + +//debug = EH_personality; import core.internal.backtrace.unwind; import core.stdc.stdio : fprintf, printf, stderr; @@ -31,6 +34,9 @@ version (LDC) } } +version (SjLj_Exceptions) version = SjLjOrWasm_Exceptions; +else version (WebAssembly) version = SjLjOrWasm_Exceptions; + /* These are the register numbers for _Unwind_SetGR(). * Hints for these can be found by looking at the EH_RETURN_DATA_REGNO macro in * GCC. If you have a native gcc you can try the following: @@ -99,6 +105,10 @@ else version (LoongArch64) enum eh_exception_regno = 4; enum eh_selector_regno = 5; } +else version(WebAssembly) +{ + // none; relavent parts of libunwind are inlined +} else { static assert(0, "Unknown EH register numbers for this architecture"); @@ -347,57 +357,62 @@ extern(C) void _d_throw_exception(Throwable o) _d_createTrace(o, null); - version (SjLj_Exceptions) - { - auto r = _Unwind_SjLj_RaiseException(&eh.exception_object); - } - else - { - auto r = _Unwind_RaiseException(&eh.exception_object); - } + version (WebAssembly) { + import ldc.intrinsics : llvm_wasm_throw; + llvm_wasm_throw(0, &eh.exception_object); + } else { + version (SjLj_Exceptions) + { + auto r = _Unwind_SjLj_RaiseException(&eh.exception_object); + } + else + { + auto r = _Unwind_RaiseException(&eh.exception_object); + } - /* Shouldn't have returned, but if it did: - */ - switch (r) - { - case _URC_END_OF_STACK: - /* Unwound the stack without encountering a catch clause. - * In C++, this would mean call uncaught_exception(). - * In D, this can happen only if `rt_trapExceptions` is cleared - * since otherwise everything is enclosed by a top-level - * try/catch. - */ - fprintf(stderr, "%s:%d: uncaught exception reached top of stack\n", __FILE__.ptr, __LINE__); - fprintf(stderr, "This might happen if you're missing a top level catch in your fiber or signal handler\n"); - /** - As _d_print_throwable() itself may throw multiple times when calling core.demangle, - and with the uncaught exception still on the EH stack, this doesn't bode well with core.demangle's error recovery. - */ - version (LDC) - _d_eh_enter_catch(&eh.exception_object); - else - __dmd_begin_catch(&eh.exception_object); - _d_print_throwable(o); - abort(); - assert(0); + /* Shouldn't have returned, but if it did: + */ + switch (r) + { + case _URC_END_OF_STACK: + /* Unwound the stack without encountering a catch clause. + * In C++, this would mean call uncaught_exception(). + * In D, this can happen only if `rt_trapExceptions` is cleared + * since otherwise everything is enclosed by a top-level + * try/catch. + */ + fprintf(stderr, "%s:%d: uncaught exception reached top of stack\n", __FILE__.ptr, __LINE__); + fprintf(stderr, "This might happen if you're missing a top level catch in your fiber or signal handler\n"); + /** + As _d_print_throwable() itself may throw multiple times when calling core.demangle, + and with the uncaught exception still on the EH stack, this doesn't bode well with core.demangle's error recovery. + */ + version (LDC) + _d_eh_enter_catch(&eh.exception_object); + else + __dmd_begin_catch(&eh.exception_object); + _d_print_throwable(o); + abort(); + assert(0); - case _URC_FATAL_PHASE1_ERROR: - /* Unexpected error, likely some sort of corruption. - * In C++, terminate() would be called. - */ - terminate(__LINE__); // should never happen - assert(0); + case _URC_FATAL_PHASE1_ERROR: + /* Unexpected error, likely some sort of corruption. + * In C++, terminate() would be called. + */ + terminate(__LINE__); // should never happen + assert(0); - case _URC_FATAL_PHASE2_ERROR: - /* Unexpected error. Program is in an unknown state. - * In C++, terminate() would be called. - */ - terminate(__LINE__); // should never happen - assert(0); + case _URC_FATAL_PHASE2_ERROR: + /* Unexpected error. Program is in an unknown state. + * In C++, terminate() would be called. + */ + terminate(__LINE__); // should never happen + assert(0); - default: - terminate(__LINE__); // should never happen - assert(0); + default: + terminate(__LINE__); // should never happen + assert(0); + } } } @@ -476,7 +491,7 @@ version (ARM_EABI_UNWINDER) UNWIND_POINTER_REG = 12, UNWIND_STACK_REG = 13 } - + extern (C) _Unwind_Reason_Code _d_eh_personality(_Unwind_State state, _Unwind_Exception* exceptionObject, _Unwind_Context* context) { @@ -526,6 +541,44 @@ version (ARM_EABI_UNWINDER) return _URC_CONTINUE_UNWIND; } } +else version (WebAssembly) +{ + // We inline the needed parts of libunwind here to avoid dependence + // We can't link libunwind because we need to override _Unwind_CallPersonality + // + // TODO: if/when the personality can be changed per function for Wasm in LLVM, use libunwind + // (so we can support C++ exceptions as well) + + extern(C) struct _Unwind_LandingPadContext { + // Input information to personality function + size_t lpad_index; // landing pad index + size_t lsda; // LSDA address + + // Output information computed by personality function + size_t selector; // selector value + } + + // Communication channel between compiler-generated user code and personality + // function + extern(C) _Unwind_LandingPadContext __wasm_lpad_context; + + extern(C) _Unwind_Reason_Code _Unwind_CallPersonality(_Unwind_Exception *exception_object) { + // Reset the selector. + __wasm_lpad_context.selector = 0; + + // Call personality function. Wasm does not have two-phase unwinding, so we + // only do the cleanup phase. + return _d_eh_personality_common( + _UA_SEARCH_PHASE, exception_object.exception_class, exception_object, + cast(_Unwind_Context *)&__wasm_lpad_context); + } + + extern(C) void _Unwind_DeleteException(_Unwind_Exception *exception_object) { + if (exception_object.exception_cleanup != null) { + exception_object.exception_cleanup(_URC_FOREIGN_EXCEPTION_CAUGHT, exception_object); + } + } +} else { extern (C) _Unwind_Reason_Code _d_eh_personality(int ver, _Unwind_Action actions, @@ -568,14 +621,20 @@ extern (C) _Unwind_Reason_Code _d_eh_personality_common(_Unwind_Action actions, } } - language_specific_data = cast(const(ubyte)*)_Unwind_GetLanguageSpecificData(context); + version (WebAssembly) language_specific_data = cast(const(ubyte)*)(cast(_Unwind_LandingPadContext*)context).lsda; + else language_specific_data = cast(const(ubyte)*)_Unwind_GetLanguageSpecificData(context); + debug (EH_personality) writeln("lsda = %p", language_specific_data); - auto Start = _Unwind_GetRegionStart(context); + version (WebAssembly) _Unwind_Ptr Start = 0; + else auto Start = _Unwind_GetRegionStart(context); /* Get instruction pointer (ip) at start of instruction that threw */ - version (CRuntime_Glibc) + version (WebAssembly) { + auto ip = (cast(_Unwind_LandingPadContext*)context).lpad_index + 1; + } + else version (CRuntime_Glibc) { int ip_before_insn; // The instruction pointer must not be decremented when unwinding from a @@ -646,6 +705,12 @@ extern (C) _Unwind_Reason_Code _d_eh_personality_common(_Unwind_Action actions, eh.languageSpecificData = language_specific_data; eh.landingPad = landing_pad; } + + // Wasm only uses a single phase (_UA_SEARCH_PHASE), so save the + // results here. + version (WebAssembly) + (cast(_Unwind_LandingPadContext*)context).selector = handler; + return _URC_HANDLER_FOUND; } break; @@ -719,9 +784,13 @@ extern (C) _Unwind_Reason_Code _d_eh_personality_common(_Unwind_Action actions, } // Set up registers and jump to cleanup or handler - _Unwind_SetGR(context, eh_exception_regno, cast(_Unwind_Ptr)exceptionObject); - _Unwind_SetGR(context, eh_selector_regno, handler); - _Unwind_SetIP(context, landing_pad); + version (WebAssembly) { + (cast(_Unwind_LandingPadContext*)context).selector = handler; + } else { + _Unwind_SetGR(context, eh_exception_regno, cast(_Unwind_Ptr)exceptionObject); + _Unwind_SetGR(context, eh_selector_regno, handler); + _Unwind_SetIP(context, landing_pad); + } return _URC_INSTALL_CONTEXT; } @@ -978,7 +1047,7 @@ LsdaResult scanLSDA(const(ubyte)* lsda, _Unwind_Ptr ip, _Unwind_Exception_Class } } - version (SjLj_Exceptions) + version (SjLjOrWasm_Exceptions) { if (TType == DW_EH_PE_omit) { @@ -1016,7 +1085,7 @@ LsdaResult scanLSDA(const(ubyte)* lsda, _Unwind_Ptr ip, _Unwind_Exception_Class return LsdaResult.corrupt; } } - else // !SjLj_Exceptions + else // !SjLjOrWasm_Exceptions { while (1) { @@ -1102,7 +1171,7 @@ LsdaResult scanLSDA(const(ubyte)* lsda, _Unwind_Ptr ip, _Unwind_Exception_Class } } } - } // !SjLj_Exceptions + } // !SjLjOrWasm_Exceptions if (noAction) {