From 296a6d1f445561501a4e827363027a37f11d6cfb Mon Sep 17 00:00:00 2001 From: ikappaki Date: Wed, 13 May 2026 07:53:43 +0100 Subject: [PATCH 01/13] apply msys2 patches, correct plugin windows.h header --- clang/lib/Driver/ToolChains/MinGW.cpp | 2 +- lld/MinGW/Options.td | 2 ++ llvm/cmake/modules/Findzstd.cmake | 5 ++++- llvm/cmake/modules/GetHostTriple.cmake | 2 +- llvm/lib/Support/CMakeLists.txt | 3 +++ llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h | 2 +- 6 files changed, 12 insertions(+), 4 deletions(-) diff --git a/clang/lib/Driver/ToolChains/MinGW.cpp b/clang/lib/Driver/ToolChains/MinGW.cpp index 7f8aec6482b5f..99df9b533ced4 100644 --- a/clang/lib/Driver/ToolChains/MinGW.cpp +++ b/clang/lib/Driver/ToolChains/MinGW.cpp @@ -329,7 +329,7 @@ void tools::MinGW::Linker::ConstructJob(Compilation &C, const JobAction &JA, if (Args.hasArg(options::OPT_pg)) CmdArgs.push_back("-lgmon"); - if (Args.hasArg(options::OPT_pthread)) + if (!Args.hasArg(options::OPT_no_pthread)) CmdArgs.push_back("-lpthread"); if (Sanitize.needsAsanRt()) { diff --git a/lld/MinGW/Options.td b/lld/MinGW/Options.td index e6626b582bcd8..5f57a943e36a4 100644 --- a/lld/MinGW/Options.td +++ b/lld/MinGW/Options.td @@ -249,6 +249,8 @@ def alias_undefined_u: JoinedOrSeparate<["-"], "u">, Alias; // Ignored options def: Joined<["-"], "O">; def: F<"as-needed">; +def: F<"default-image-base-high">; +def: F<"default-image-base-low">; def: F<"disable-auto-image-base">; def: F<"enable-auto-image-base">; def: F<"end-group">; diff --git a/llvm/cmake/modules/Findzstd.cmake b/llvm/cmake/modules/Findzstd.cmake index dbfaadb5de2b8..19d88bf00c53d 100644 --- a/llvm/cmake/modules/Findzstd.cmake +++ b/llvm/cmake/modules/Findzstd.cmake @@ -16,6 +16,8 @@ else() set(zstd_STATIC_LIBRARY_SUFFIX "\\${CMAKE_STATIC_LIBRARY_SUFFIX}$") endif() +find_package(zstd CONFIG QUIET) +if(NOT zstd_FOUND) find_path(zstd_INCLUDE_DIR NAMES zstd.h) find_library(zstd_LIBRARY NAMES zstd zstd_static) find_library(zstd_STATIC_LIBRARY NAMES @@ -27,6 +29,7 @@ find_package_handle_standard_args( zstd DEFAULT_MSG zstd_LIBRARY zstd_INCLUDE_DIR ) +endif() if(zstd_FOUND) if(zstd_LIBRARY MATCHES "${zstd_STATIC_LIBRARY_SUFFIX}$" AND NOT zstd_LIBRARY MATCHES "\\.dll\\.a$") @@ -38,7 +41,7 @@ if(zstd_FOUND) # IMPORTED_LOCATION is the path to the DLL and IMPORTED_IMPLIB is the "library". get_filename_component(zstd_DIRNAME "${zstd_LIBRARY}" DIRECTORY) if(NOT "${CMAKE_INSTALL_LIBDIR}" STREQUAL "" AND NOT "${CMAKE_INSTALL_BINDIR}" STREQUAL "") - string(REGEX REPLACE "${CMAKE_INSTALL_LIBDIR}$" "${CMAKE_INSTALL_BINDIR}" zstd_DIRNAME "${zstd_DIRNAME}") + string(REGEX REPLACE "\\${CMAKE_INSTALL_LIBDIR}$" "${CMAKE_INSTALL_BINDIR}" zstd_DIRNAME "${zstd_DIRNAME}") endif() get_filename_component(zstd_BASENAME "${zstd_LIBRARY}" NAME) string(REGEX REPLACE "\\${CMAKE_LINK_LIBRARY_SUFFIX}$" "${CMAKE_SHARED_LIBRARY_SUFFIX}" zstd_BASENAME "${zstd_BASENAME}") diff --git a/llvm/cmake/modules/GetHostTriple.cmake b/llvm/cmake/modules/GetHostTriple.cmake index cbecd85df5e43..8dbd06ff4a7c9 100644 --- a/llvm/cmake/modules/GetHostTriple.cmake +++ b/llvm/cmake/modules/GetHostTriple.cmake @@ -16,7 +16,7 @@ function( get_host_triple var ) else() set( value "i686-pc-windows-msvc" ) endif() - elseif( MINGW AND NOT MSYS ) + elseif( MINGW ) # CMake doesn't provide COMPILER_ARCHITECTURE_ID for Clang/GCC, # but it does for MSVC. if( CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "ARM.*" ) diff --git a/llvm/lib/Support/CMakeLists.txt b/llvm/lib/Support/CMakeLists.txt index e8d505f218b69..2717ca34d4547 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -42,6 +42,9 @@ if( WIN32 ) # advapi32 required for CryptAcquireContextW in lib/Support/Windows/Path.inc. # ntdll required for RtlGetLastNtStatus in lib/Support/ErrorHandling.cpp. set(system_libs ${system_libs} psapi shell32 ole32 uuid advapi32 ws2_32 ntdll) + if( MINGW ) + set(system_libs ${system_libs} pthread) + endif() if( HAVE_WINDOWS_ICU ) list(APPEND system_libs icu) endif() diff --git a/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h b/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h index 0776934d3209b..90818944ebecb 100644 --- a/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h +++ b/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h @@ -24,7 +24,7 @@ #include #ifdef _WIN32 -#include +#include #else // For testing purposes only. using DWORD = uint32_t; From 2019be527937a2e614dee22ab59cab773417dc46 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Wed, 13 May 2026 21:24:33 +0100 Subject: [PATCH 02/13] [ORC] Enable JITLink by default for x86_64 COFF targets --- llvm/lib/ExecutionEngine/Orc/LLJIT.cpp | 2 +- .../ExecutionEngine/Orc/CMakeLists.txt | 1 + .../Orc/LLJITJITLinkSelectionTest.cpp | 103 ++++++++++++++++++ 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 llvm/unittests/ExecutionEngine/Orc/LLJITJITLinkSelectionTest.cpp diff --git a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp index 18db7d24a8262..5e2227ceec88c 100644 --- a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +++ b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp @@ -813,7 +813,7 @@ Error LLJITBuilderState::prepareForConstruction() { UseJITLink = TT.isOSBinFormatELF(); break; case Triple::x86_64: - UseJITLink = !TT.isOSBinFormatCOFF(); + UseJITLink = true; break; case Triple::ppc64: UseJITLink = TT.isPPC64ELFv2ABI(); diff --git a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt index 0d7baef3a0d93..d910235fdbeef 100644 --- a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt @@ -51,6 +51,7 @@ add_llvm_unittest(OrcJITTests WaitingOnGraphTest.cpp WrapperFunctionUtilsTest.cpp JITLinkRedirectionManagerTest.cpp + LLJITJITLinkSelectionTest.cpp ReOptimizeLayerTest.cpp EXPORT_SYMBOLS diff --git a/llvm/unittests/ExecutionEngine/Orc/LLJITJITLinkSelectionTest.cpp b/llvm/unittests/ExecutionEngine/Orc/LLJITJITLinkSelectionTest.cpp new file mode 100644 index 0000000000000..b119eb65dc769 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/Orc/LLJITJITLinkSelectionTest.cpp @@ -0,0 +1,103 @@ +//===-- LLJITJITLinkSelectionTest.cpp - Test JITLink default selection ----===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Verifies that LLJIT selects ObjectLinkingLayer (JITLink) by default for +// the host target, and that plugins can be installed on it. +// +//===----------------------------------------------------------------------===// + +#include "OrcTestCommon.h" +#include "llvm/ExecutionEngine/Orc/LLJIT.h" +#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::orc; + +namespace { + +// A minimal plugin that just records whether modifyPassConfig was called. +class PluginCallTracker : public ObjectLinkingLayer::Plugin { +public: + PluginCallTracker(bool &WasCalled) : WasCalled(WasCalled) {} + + void modifyPassConfig(MaterializationResponsibility &MR, + jitlink::LinkGraph &G, + jitlink::PassConfiguration &Config) override { + WasCalled = true; + } + + Error notifyFailed(MaterializationResponsibility &MR) override { + return Error::success(); + } + + Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { + return Error::success(); + } + + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, + ResourceKey SrcKey) override {} + +private: + bool &WasCalled; +}; + +// Test that LLJIT selects JITLink (ObjectLinkingLayer) by default on +// the host platform. +TEST(LLJITJITLinkSelectionTest, DefaultLinkerIsJITLink) { + // Initialize native target so LLJIT can create a JIT for the host. + OrcNativeTarget::initialize(); + + auto J = LLJITBuilder().create(); + if (!J) { + // If we can't create an LLJIT (e.g., no target registered), skip. + consumeError(J.takeError()); + GTEST_SKIP(); + } + + // The key assertion: the default linker for this host must be JITLink + // (ObjectLinkingLayer), not RTDyld. + auto *OLL = dyn_cast(&(*J)->getObjLinkingLayer()); + ASSERT_NE(OLL, nullptr) + << "LLJIT did not select ObjectLinkingLayer (JITLink) by default. " + "Check the host architecture case in prepareForConstruction."; + + // Verify that a plugin can be installed and actually runs. + bool PluginWasCalled = false; + OLL->addPlugin(std::make_unique(PluginWasCalled)); + + // Create a trivial module with a function that returns 42. + auto Ctx = std::make_unique(); + auto M = std::make_unique("test", *Ctx); + M->setTargetTriple((*J)->getTargetTriple()); + + auto *FT = FunctionType::get(Type::getInt32Ty(*Ctx), false); + auto *F = Function::Create(FT, GlobalValue::ExternalLinkage, "test_fn", *M); + auto *BB = BasicBlock::Create(*Ctx, "entry", F); + IRBuilder<> Builder(BB); + Builder.CreateRet(Builder.getInt32(42)); + + // Add the module and look up the function. + ASSERT_THAT_ERROR((*J)->addIRModule(ThreadSafeModule(std::move(M), + std::move(Ctx))), + Succeeded()); + + auto Sym = (*J)->lookup("test_fn"); + ASSERT_THAT_EXPECTED(Sym, Succeeded()); + + // Verify the plugin was invoked during linking. + EXPECT_TRUE(PluginWasCalled) + << "Plugin was not called — ObjectLinkingLayer plugin system not working"; + + // Call the function and verify it returns 42. + auto *FnPtr = Sym->toPtr(); + EXPECT_EQ(FnPtr(), 42); +} + +} // end anonymous namespace From 8fcef754b66ca22bf868f3241db3f55a05c399ac Mon Sep 17 00:00:00 2001 From: ikappaki Date: Fri, 15 May 2026 07:38:10 +0100 Subject: [PATCH 03/13] [JITLink][COFF] Resolve __ImageBase before ADDR32NB lowering --- .../ExecutionEngine/JITLink/COFF_x86_64.cpp | 27 +++++++++++++++++++ .../Orc/coff-imagebase-resolution.ll | 26 ++++++++++++++++++ llvm/test/lit.cfg.py | 6 ++++- llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h | 18 +++++++------ 4 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 llvm/test/ExecutionEngine/Orc/coff-imagebase-resolution.ll diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp index aa91ac053bb50..60046aa4fa547 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp @@ -247,6 +247,30 @@ class COFFLinkGraphLowering_x86_64 { GetImageBaseSymbol GetImageBase; DenseMap
SectionStartCache; }; + +// A LinkGraph pass that resolves __ImageBase for COFF x86_64 graphs +class COFFImageBaseResolution_x86_64 { +public: + // Resolves __ImageBase to the lowest allocated section address in G + Error operator()(LinkGraph &G) { + GetImageBaseSymbol GetImageBase; + + auto ImageBase = GetImageBase(G); + if (ImageBase) { + orc::ExecutorAddr Base(~uint64_t(0)); + for (auto &Sec : G.sections()) { + if (Sec.empty()) + continue; + SectionRange SR(Sec); + Base = std::min(Base, SR.getStart()); + } + assert(ImageBase && "__ImageBase symbol must be defined"); + ImageBase->getAddressable().setAddress(Base); + } + return Error::success(); + } +}; + } // namespace namespace llvm { @@ -303,6 +327,9 @@ void link_COFF_x86_64(std::unique_ptr G, } else Config.PrePrunePasses.push_back(markAllSymbolsLive); + // Add ImageBase resolution pass, needed by Lowering and other downstream passes. + Config.PreFixupPasses.push_back(COFFImageBaseResolution_x86_64()); + // Add COFF edge lowering passes. Config.PreFixupPasses.push_back(COFFLinkGraphLowering_x86_64()); } diff --git a/llvm/test/ExecutionEngine/Orc/coff-imagebase-resolution.ll b/llvm/test/ExecutionEngine/Orc/coff-imagebase-resolution.ll new file mode 100644 index 0000000000000..3ecb1d068c42e --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/coff-imagebase-resolution.ll @@ -0,0 +1,26 @@ +; Test that __ImageBase resolves to the correct base address so that +; image-relative (ADDR32NB) relocations in .pdata/.xdata produce valid +; 32-bit offsets. Without the fix, __ImageBase is zero and the offsets +; overflow, causing a link failure. +; +; The test compiles a non-leaf function (one that calls another) with the +; uwtable attribute. Non-leaf functions require unwind info, so the compiler +; emits .pdata/.xdata with ADDR32NB relocations that reference __ImageBase. +; +; We use llvm-jitlink -noexec to test linking in isolation — verifying that +; __ImageBase resolves correctly and all ADDR32NB edges fit in 32 bits. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: llc -mtriple=x86_64-w64-windows-gnu -filetype=obj -o %t.obj %s +; RUN: llvm-jitlink -noexec -entry entry %t.obj + +define i32 @helper() #0 { + ret i32 42 +} + +define i32 @entry() #0 { + %val = call i32 @helper() + ret i32 0 +} + +attributes #0 = { nounwind uwtable } diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py index e44079aa5cd26..aa35b3ad94522 100644 --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -727,8 +727,12 @@ def host_unwind_supports_jit(): if platform.system() in ["Linux", "FreeBSD", "NetBSD"]: return True - # Windows does not support frame info without the ORC runtime. + # Windows x86_64 with MinGW (windows-gnu) supports SEH frame registration + # via SEHFrameRegistrationPlugin in ORC JITLink. MSVC targets are not yet + # supported without the ORC runtime. if platform.system() == "Windows": + if "windows-gnu" in config.host_triple and "x86_64" in config.host_triple: + return True return False # On Darwin/x86-64 clang produces both eh-frames and compact-unwind, and diff --git a/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h b/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h index 90818944ebecb..e6d389820e55d 100644 --- a/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h +++ b/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h @@ -92,15 +92,17 @@ class WindowsEasyEHPlugin : public ObjectLinkingLayer::Plugin { Base = ImageBase->getAddress(); PDataRange = jitlink::SectionRange(*PDataSection).getRange(); } else { + return make_error(".pdata section present but __ImageBase symbol not found in graph", + inconvertibleErrorCode()); // No __ImageBase. Use the lowest address in this graph as a substitute. - for (auto &Sec : G.sections()) { - if (Sec.empty()) - continue; - jitlink::SectionRange SR(Sec); - Base = std::min(Base, SR.getStart()); - if (&Sec == PDataSection) - PDataRange = SR.getRange(); - } + // for (auto &Sec : G.sections()) { + // if (Sec.empty()) + // continue; + // jitlink::SectionRange SR(Sec); + // Base = std::min(Base, SR.getStart()); + // if (&Sec == PDataSection) + // PDataRange = SR.getRange(); + // } } G.allocActions().push_back( From 5bb976340d29e84f234800c3245b80bd3df11ef7 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 16 May 2026 12:11:21 +0100 Subject: [PATCH 04/13] [JITLink][COFF] Add GOT/PLT stub support for ext calls in COFFx86_64 --- .../ExecutionEngine/JITLink/COFF_x86_64.cpp | 25 +++++++++++++++++++ .../ExecutionEngine/Orc/coff-plt-stubs.ll | 23 +++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 llvm/test/ExecutionEngine/Orc/coff-plt-stubs.ll diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp index 60046aa4fa547..0b6ee43816500 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp @@ -271,6 +271,28 @@ class COFFImageBaseResolution_x86_64 { } }; +// Create GOT entries and PLT stubs in G for calls to external +// symbols. Returns Error::success() unconditionally. +Error buildTables_COFF_x86_64(LinkGraph &G) { + LLVM_DEBUG(dbgs() << "Visiting edges in graph:\n"); + + x86_64::GOTTableManager GOT(G); + x86_64::PLTTableManager PLT(G, GOT); + // Mark calls to externals as BranchPCRel32 so PLTTableManager will create + // stubs for them. Without this, it ignores COFF's PCRel32 edge kind. + for (auto *B : G.blocks()) { + for (auto &E : B->edges()) { + if (E.getKind() == EdgeKind_coff_x86_64::PCRel32 && + !E.getTarget().isDefined()) { + E.setKind(x86_64::BranchPCRel32); + } + } + } + + visitExistingEdges(G, PLT); + return Error::success(); +} + } // namespace namespace llvm { @@ -327,6 +349,9 @@ void link_COFF_x86_64(std::unique_ptr G, } else Config.PrePrunePasses.push_back(markAllSymbolsLive); + // Add an in place GOT/PLT stub build pass for external calls. + Config.PostPrunePasses.push_back(buildTables_COFF_x86_64); + // Add ImageBase resolution pass, needed by Lowering and other downstream passes. Config.PreFixupPasses.push_back(COFFImageBaseResolution_x86_64()); diff --git a/llvm/test/ExecutionEngine/Orc/coff-plt-stubs.ll b/llvm/test/ExecutionEngine/Orc/coff-plt-stubs.ll new file mode 100644 index 0000000000000..c8bd07eeb83f9 --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/coff-plt-stubs.ll @@ -0,0 +1,23 @@ +; Test that COFF x86_64 JITLink creates PLT stubs for calls to external symbols. +; +; When JIT'd code calls an external DLL function (e.g., puts), the call uses a +; 32-bit PC-relative offset which cannot reach targets beyond ±2GB. JITLink +; generates a PLT stub (jmp [rip+0] + GOT entry) near the call site to bridge +; the gap via a 64-bit indirect jump. +; +; This test calls puts through a PLT stub and verifies it executes correctly. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: llc -mtriple=x86_64-w64-windows-gnu -filetype=obj -o %t.obj %s +; RUN: llvm-jitlink -entry entry %t.obj + +@.str = private unnamed_addr constant [12 x i8] c"plt works!\0A\00" + +declare i32 @puts(ptr) + +define i32 @entry() #0 { + %call = call i32 @puts(ptr @.str) + ret i32 0 +} + +attributes #0 = { nounwind uwtable } From f9f8a22050deae6a8592890072f2a32bf0d5ad78 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sun, 17 May 2026 13:29:20 +0100 Subject: [PATCH 05/13] [JITLink][COFF] Add ADDR32NB stubs for image rel refs to externals --- .../ExecutionEngine/JITLink/COFF_x86_64.cpp | 60 ++++++++++++++++++- .../Orc/coff-addr32nb-stubs.ll | 48 +++++++++++++++ 2 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 llvm/test/ExecutionEngine/Orc/coff-addr32nb-stubs.ll diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp index 0b6ee43816500..2cf777886cdbb 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp @@ -271,13 +271,67 @@ class COFFImageBaseResolution_x86_64 { } }; -// Create GOT entries and PLT stubs in G for calls to external -// symbols. Returns Error::success() unconditionally. +// Creates executable stubs for Pointer32NB edges targeting external +// symbols whose image relative offset from __ImageBase would exceed +// 32 bits. +class COFFPointer32NBStubsManager : public llvm::jitlink::TableManager { +public: + // Returns section name for ADDR32NB stubs in the link graph. + static StringRef getSectionName() { return "$__STUBS_ADDR32NB"; } + + // Constructs a stub manager using GOT for indirect jump targets. + COFFPointer32NBStubsManager(LinkGraph &, x86_64::GOTTableManager &GOT) : GOT(GOT) { + } + + // Checks edge E on block B in graph G. If E is a Pointer32NB + // targeting an external, redirects it to a nearby stub. Returns + // true if handled. + bool visitEdge(LinkGraph &G, Block *B, Edge &E) { + if (E.getKind() == EdgeKind_coff_x86_64::Pointer32NB && !E.getTarget().isDefined()) { + DEBUG_WITH_TYPE("jitlink", { + dbgs() << " Fixing " << G.getEdgeKindName(E.getKind()) << " edge at " + << B->getFixupAddress(E) << " (" << B->getAddress() << " + " + << formatv("{0:x}", E.getOffset()) << ")\n"; + }); + + E.setTarget(getEntryForTarget(G, E.getTarget())); + return true; + } + return false; + } + + // Creates an executable stub in G that jumps to Target via a GOT + // entry. Returns an anonymous symbol pointing to the stub. + Symbol &createEntry(LinkGraph &G, Symbol &Target) { + return x86_64::createAnonymousPointerJumpStub(G, getStubsSection(G), + GOT.getEntryForTarget(G, Target)); + } + +public: + // Returns the executable stub section in G, creating it on first + // call. + Section &getStubsSection(LinkGraph &G) { + if (!StubsSection) + StubsSection = &G.createSection(getSectionName(), + orc::MemProt::Read | orc::MemProt::Exec); + return *StubsSection; + } + + // Shared GOT for indirect jump targets. + x86_64::GOTTableManager &GOT; + // Lazily created stub section. + Section *StubsSection = nullptr; +}; + +// Create stubs in G for external references: PLT stubs for calls (PCRel32) +// and ADDR32NB stubs for image-relative pointers (Pointer32NB). Always succeeds. Error buildTables_COFF_x86_64(LinkGraph &G) { LLVM_DEBUG(dbgs() << "Visiting edges in graph:\n"); x86_64::GOTTableManager GOT(G); x86_64::PLTTableManager PLT(G, GOT); + COFFPointer32NBStubsManager COFFPtr32NB(G, GOT); + // Mark calls to externals as BranchPCRel32 so PLTTableManager will create // stubs for them. Without this, it ignores COFF's PCRel32 edge kind. for (auto *B : G.blocks()) { @@ -289,7 +343,7 @@ Error buildTables_COFF_x86_64(LinkGraph &G) { } } - visitExistingEdges(G, PLT); + visitExistingEdges(G, PLT, COFFPtr32NB); return Error::success(); } diff --git a/llvm/test/ExecutionEngine/Orc/coff-addr32nb-stubs.ll b/llvm/test/ExecutionEngine/Orc/coff-addr32nb-stubs.ll new file mode 100644 index 0000000000000..942303e2243fc --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/coff-addr32nb-stubs.ll @@ -0,0 +1,48 @@ +; Test that COFF x86_64 JITLink creates stubs for Pointer32NB edges targeting +; external symbols whose image-relative offset exceeds 32 bits. +; +; The .xdata section references the personality function (__gxx_personality_seh0) +; via an IMAGE_REL_AMD64_ADDR32NB relocation. When the personality function is +; in a DLL far from JIT'd code, its offset from __ImageBase doesn't fit in 32 +; bits. JITLink creates an executable stub near the JIT'd code and redirects +; the .xdata reference to the stub, whose image-relative offset does fit. +; +; This test throws and catches a C++ exception, exercising the full path: +; .xdata personality reference → stub → indirect jump → real personality function. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: llc -mtriple=x86_64-w64-windows-gnu -filetype=obj -o %t.obj %s +; RUN: llvm-jitlink -entry entry -preload libc++.dll -preload libunwind.dll %t.obj + +@_ZTIi = external constant ptr + +declare void @__cxa_throw(ptr, ptr, ptr) +declare ptr @__cxa_allocate_exception(i64) +declare i32 @__gxx_personality_seh0(...) +declare i32 @__cxa_begin_catch(ptr) +declare void @__cxa_end_catch() + +define i32 @thrower() #0 personality ptr @__gxx_personality_seh0 { + %ex = call ptr @__cxa_allocate_exception(i64 4) + store i32 42, ptr %ex + call void @__cxa_throw(ptr %ex, ptr @_ZTIi, ptr null) + unreachable +} + +define i32 @entry() #0 personality ptr @__gxx_personality_seh0 { + %val = invoke i32 @thrower() + to label %normal unwind label %catch + +normal: + ret i32 1 + +catch: + %lp = landingpad { ptr, i32 } + catch ptr @_ZTIi + %exn = extractvalue { ptr, i32 } %lp, 0 + %sel = call i32 @__cxa_begin_catch(ptr %exn) + call void @__cxa_end_catch() + ret i32 0 +} + +attributes #0 = { nounwind uwtable } From aa807e6c3d752edf8a57b4905c16cb6222d4baa1 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sun, 17 May 2026 14:49:49 +0100 Subject: [PATCH 06/13] fixup! [JITLink][COFF] Resolve __ImageBase before ADDR32NB lowering --- llvm/test/lit.cfg.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/llvm/test/lit.cfg.py b/llvm/test/lit.cfg.py index aa35b3ad94522..630e4bf9e6ef3 100644 --- a/llvm/test/lit.cfg.py +++ b/llvm/test/lit.cfg.py @@ -727,9 +727,9 @@ def host_unwind_supports_jit(): if platform.system() in ["Linux", "FreeBSD", "NetBSD"]: return True - # Windows x86_64 with MinGW (windows-gnu) supports SEH frame registration - # via SEHFrameRegistrationPlugin in ORC JITLink. MSVC targets are not yet - # supported without the ORC runtime. + # Windows x86_64 uses SEH (.pdata/.xdata) with RtlAddFunctionTable + # for dynamic registration. Only tested with windows-gnu (MinGW) + # so far. if platform.system() == "Windows": if "windows-gnu" in config.host_triple and "x86_64" in config.host_triple: return True From 59c2bca9d5611cb59e5f87d848e1373c4cbe4091 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sun, 17 May 2026 20:45:17 +0100 Subject: [PATCH 07/13] [JITLink][COFF] Fix missing symbol reg for COMDAT sect def symbols --- .../JITLink/COFFLinkGraphBuilder.cpp | 27 ++++++++++++-- .../Orc/coff-comdat-relocation.ll | 37 +++++++++++++++++++ 2 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 llvm/test/ExecutionEngine/Orc/coff-comdat-relocation.ll diff --git a/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp b/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp index 984ef81ae6c99..4847b5bc09c09 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp @@ -534,7 +534,16 @@ Expected COFFLinkGraphBuilder::createDefinedSymbol( return make_error( "COMDAT export request already exists before symbol " + formatv("{0:d}", SymIndex)); - return createCOMDATExportRequest(SymIndex, Symbol, Definition); + + if (auto Err = createCOMDATExportRequest(SymIndex, Symbol, Definition).takeError()) + return Err; + + // Return a local symbol so relocations can resolve against this index. + // COMDAT sections without an export (e.g. .xdata$*) keep this symbol, + // while those with one will get it replaced in exportCOMDATSymbol. + return &G->addDefinedSymbol( + *B, Symbol.getValue(), SymbolName, 0, Linkage::Strong, Scope::Local, + Symbol.getComplexType() == COFF::IMAGE_SYM_DTYPE_FUNCTION, false); } return make_error("Unsupported storage class " + formatv("{0:d}", Symbol.getStorageClass()) + @@ -550,9 +559,13 @@ Expected COFFLinkGraphBuilder::createDefinedSymbol( // Second symbol is COMDAT symbol which usually defines the external name and // data type. // -// Since two symbols always come in a specific order, we initiate pending COMDAT +// Since two symbols usually come in a specific order, we initiate pending COMDAT // export request when we encounter the first symbol and actually exports it -// when we process the second symbol. +// when we process the second symbol. However, some COMDAT sections (e.g. +// .xdata$*, .pdata$*) only have the section definition symbol with no +// export following. The caller SHOULD register a local symbol for the first +// symbol's index unconditionally so that relocations targeting it can +// resolve regardless of whether an export symbol follows. // // Process the first symbol of COMDAT sequence. Expected COFFLinkGraphBuilder::createCOMDATExportRequest( @@ -604,6 +617,9 @@ Expected COFFLinkGraphBuilder::createCOMDATExportRequest( } // Process the second symbol of COMDAT sequence. +// +// Replaces the local placeholder registered for the first symbol's +// index. Expected COFFLinkGraphBuilder::exportCOMDATSymbol(COFFSymbolIndex SymIndex, orc::SymbolStringPtr SymbolName, @@ -623,6 +639,11 @@ COFFLinkGraphBuilder::exportCOMDATSymbol(COFFSymbolIndex SymIndex, << "\" in section " << Symbol.getSectionNumber() << "\n"; dbgs() << " " << *GSym << "\n"; }); + + // Clear the first symbol's provisional placeholder so + // setGraphSymbol can replace it. + GraphSymbols[PendingComdatExport->SymbolIndex] = nullptr; + setGraphSymbol(Symbol.getSectionNumber(), PendingComdatExport->SymbolIndex, *GSym); DefinedSymbols[SymbolName] = GSym; diff --git a/llvm/test/ExecutionEngine/Orc/coff-comdat-relocation.ll b/llvm/test/ExecutionEngine/Orc/coff-comdat-relocation.ll new file mode 100644 index 0000000000000..bb6e4f7f5b38e --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/coff-comdat-relocation.ll @@ -0,0 +1,37 @@ +; Test that COMDAT section symbols are registered in the graph symbol table +; so that relocations in .pdata$ can reference them by index. +; +; COMDAT metadata sections (e.g. .xdata$*) may only have a section-definition +; symbol with no export following. Relocations from .pdata$ targeting +; these indices must still resolve. +; +; The test uses a COMDAT function with uwtable to force .pdata$ +; emission, then links with llvm-jitlink -noexec to verify all relocations +; resolve without error. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: llc -mtriple=x86_64-w64-windows-gnu -filetype=obj -o %t.obj %s +; RUN: llvm-jitlink -noexec -entry comdat_caller %t.obj + +; A simple helper that the COMDAT function calls (making it non-leaf). +define i32 @helper() #0 { + ret i32 7 +} + +; A COMDAT function — simulates a template instantiation or inline function. +; It calls helper, making it non-leaf so the compiler emits .pdata$comdat_fn +; and .xdata$comdat_fn with relocations referencing the COMDAT section symbol. +$comdat_fn = comdat any + +define weak i32 @comdat_fn() #0 comdat { + %val = call i32 @helper() + ret i32 %val +} + +; Entry point that calls the COMDAT function. +define i32 @comdat_caller() #0 { + %val = call i32 @comdat_fn() + ret i32 %val +} + +attributes #0 = { nounwind uwtable } From f226c46abf3792855158b0d09104eefb172913b5 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Mon, 18 May 2026 07:53:01 +0100 Subject: [PATCH 08/13] [ORC] Set OverrideObjectFlags for JITLink on COFF Tested by (with assertions enabled): test/ExecutionEngine/Orc/trivial-return-zero.ll --- llvm/lib/ExecutionEngine/Orc/LLJIT.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp index 5e2227ceec88c..edefd6128fe07 100644 --- a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +++ b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp @@ -828,10 +828,17 @@ Error LLJITBuilderState::prepareForConstruction() { if (!JTMB->getCodeModel()) JTMB->setCodeModel(CodeModel::Small); JTMB->setRelocationModel(Reloc::PIC_); - CreateObjectLinkingLayer = [](ExecutionSession &ES, + bool IsOSBinFormatCOFF = TT.isOSBinFormatCOFF(); + CreateObjectLinkingLayer = [IsOSBinFormatCOFF](ExecutionSession &ES, jitlink::JITLinkMemoryManager &MemMgr) -> Expected> { - return std::make_unique(ES, MemMgr); + auto ObjectLayer = std::make_unique(ES, MemMgr); + if (IsOSBinFormatCOFF) { + // COFF doesn't track symbol visibility, use IR flags as + // authoritative, matching RTDyld COFF behavior. + ObjectLayer->setOverrideObjectFlagsWithResponsibilityFlags(true); + } + return std::move(ObjectLayer); }; } } From 8327fd89a8a33c2b252c87fdba348f4afbaf0ba3 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Mon, 18 May 2026 22:00:39 +0100 Subject: [PATCH 09/13] [ORC] Add SEHFrameRegistrationPlugin for Windows COFF x86_64 --- .../Orc/SEHFrameRegistrationPlugin.h | 44 +++++ llvm/lib/ExecutionEngine/Orc/CMakeLists.txt | 1 + llvm/lib/ExecutionEngine/Orc/LLJIT.cpp | 8 + .../Orc/SEHFrameRegistrationPlugin.cpp | 123 +++++++++++++ .../Orc/coff-seh-registration.ll | 44 +++++ llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h | 167 ------------------ llvm/tools/llvm-jitlink/llvm-jitlink.cpp | 7 +- 7 files changed, 223 insertions(+), 171 deletions(-) create mode 100644 llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h create mode 100644 llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp create mode 100644 llvm/test/ExecutionEngine/Orc/coff-seh-registration.ll delete mode 100644 llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h diff --git a/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h new file mode 100644 index 0000000000000..12e8dc1d6f45e --- /dev/null +++ b/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h @@ -0,0 +1,44 @@ +//===- WindowsEasyEHPlugin.h - Register COFF EH info in-process -*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Register and deregister .pdata sections in-process using RtlAddFunctionTable +// and RtlDeleteFunctionTable. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_EXECUTIONENGINE_ORC_SEHFRAMEREGISTRATIONPLUGIN_H +#define LLVM_EXECUTIONENGINE_ORC_SEHFRAMEREGISTRATIONPLUGIN_H + +#include "llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h" + +namespace llvm::orc { + +class LLVM_ABI SEHFrameRegistrationPlugin : public LinkGraphLinkingLayer::Plugin { +public: + void modifyPassConfig(MaterializationResponsibility &MR, + jitlink::LinkGraph &LG, + jitlink::PassConfiguration &PassConfig) override; + + Error notifyFailed(MaterializationResponsibility &MR) override { + return Error::success(); + } + + Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { + return Error::success(); + } + + void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, + ResourceKey SrcKey) override {} + +private: + Error registerFrameInfo(jitlink::LinkGraph &G); +}; + +} // namespace llvm::orc + +#endif // LLVM_EXECUTIONENGINE_ORC_SEHFRAMEREGISTRATIONPLUGIN_H diff --git a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt index 8c78c0250cfb8..ef85f1c08a43d 100644 --- a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt @@ -52,6 +52,7 @@ add_llvm_component_library(LLVMOrcJIT OrcV2CBindings.cpp RTDyldObjectLinkingLayer.cpp SectCreate.cpp + SEHFrameRegistrationPlugin.cpp SelfExecutorProcessControl.cpp SimpleRemoteEPC.cpp SimpleRemoteMemoryMapper.cpp diff --git a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp index edefd6128fe07..edb1aa240ac89 100644 --- a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +++ b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp @@ -19,6 +19,7 @@ #include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" #include "llvm/ExecutionEngine/Orc/ObjectTransformLayer.h" #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" +#include "llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h" #include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h" #include "llvm/ExecutionEngine/Orc/TargetProcess/RegisterEHFrames.h" #include "llvm/ExecutionEngine/Orc/UnwindInfoRegistrationPlugin.h" @@ -1278,6 +1279,13 @@ Expected setUpGenericLLVMIRPlatform(LLJIT &J) { } } + // Register .pdata with the Windows unwinder for SEH support. + if (J.getTargetTriple().isOSBinFormatCOFF()) { + OLL->addPlugin(std::make_shared()); + LLVM_DEBUG(dbgs() << "Enabled seh-frame support.\n"); + UseEHFrames = false; + } + // Otherwise fall back to standard unwind registration. if (UseEHFrames) { auto &ES = J.getExecutionSession(); diff --git a/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp new file mode 100644 index 0000000000000..ef65fc370f9cb --- /dev/null +++ b/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp @@ -0,0 +1,123 @@ +//===----- SEHFrameRegistrationPlugin.cpp - Windows SEH registration ------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h" + +#include "llvm/ExecutionEngine/JITLink/COFF.h" +#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h" + +#include "llvm/Support/Debug.h" +#include "llvm/Support/raw_ostream.h" + +#include + +#ifdef _WIN32 +#include +#endif + +#define DEBUG_TYPE "orc" + +using namespace llvm::jitlink; + +namespace llvm::orc { + +shared::CWrapperFunctionBuffer registerPData(const char *ArgData, + size_t ArgSize) { + using namespace shared; + return WrapperFunction:: + handle(ArgData, ArgSize, + [](ExecutorAddr Base, ExecutorAddrRange PDataRange) -> Error { +#ifdef _WIN32 + constexpr size_t RecordSize = sizeof(RUNTIME_FUNCTION); + if (PDataRange.size() % RecordSize) + return make_error( + ".pdata section does not contain an integer number of " + "RUNTIME_FUNCTION records", + inconvertibleErrorCode()); + size_t Count = PDataRange.size() / RecordSize; + if (Count > std::numeric_limits::max()) + return make_error( + ".pdata section contains too many records", + inconvertibleErrorCode()); + if (RtlAddFunctionTable( + PDataRange.Start.toPtr(), Count, + Base.getValue())) + return Error::success(); + else + return make_error( + "RtlAddFunctionTable returned error", + inconvertibleErrorCode()); +#else + return make_error( + "SEH registration not supported on this platform", + inconvertibleErrorCode()); +#endif + }) + .release(); +} + +shared::CWrapperFunctionBuffer deregisterPData(const char *ArgData, + size_t ArgSize) { + using namespace shared; + return WrapperFunction::handle( + ArgData, ArgSize, + [](ExecutorAddr PDataStart) -> Error { +#ifdef _WIN32 + if (RtlDeleteFunctionTable( + PDataStart.toPtr())) + return Error::success(); + else + return make_error( + "RtlDeleteFunctionTable returned error", + inconvertibleErrorCode()); +#else + return make_error( + "SEH deregistration not supported on this platform", + inconvertibleErrorCode()); +#endif + }) + .release(); +} + +void SEHFrameRegistrationPlugin::modifyPassConfig( + MaterializationResponsibility &MR, jitlink::LinkGraph &LG, + jitlink::PassConfiguration &PassConfig) { + PassConfig.PostFixupPasses.push_back( + [this](jitlink::LinkGraph &G) { return registerFrameInfo(G); }); +} + +Error SEHFrameRegistrationPlugin::registerFrameInfo(jitlink::LinkGraph &G) { + using namespace shared; + + auto *PDataSection = G.findSectionByName(".pdata"); + if (!PDataSection) + return Error::success(); + + ExecutorAddr Base(~uint64_t(0)); + ExecutorAddrRange PDataRange; + if (auto *ImageBase = jitlink::GetImageBaseSymbol()(G)) { + // If there's an __ImageBase symbol then use it to get the base address. + Base = ImageBase->getAddress(); + PDataRange = jitlink::SectionRange(*PDataSection).getRange(); + } else { + return make_error( + ".pdata section present but __ImageBase symbol not found in graph", + inconvertibleErrorCode()); + } + + G.allocActions().push_back( + {cantFail(WrapperFunctionCall::Create< + SPSArgList>( + ExecutorAddr::fromPtr(®isterPData), Base, PDataRange)), + cantFail(WrapperFunctionCall::Create>( + ExecutorAddr::fromPtr(&deregisterPData), PDataRange.Start))}); + + return Error::success(); +} + +} // namespace llvm::orc diff --git a/llvm/test/ExecutionEngine/Orc/coff-seh-registration.ll b/llvm/test/ExecutionEngine/Orc/coff-seh-registration.ll new file mode 100644 index 0000000000000..178e296882a54 --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/coff-seh-registration.ll @@ -0,0 +1,44 @@ +; Test that C++ exceptions work in JIT'd code via lli (LLJIT). +; This verifies that LLJIT's platform setup registers .pdata with the +; Windows unwinder, enabling SEH-based stack unwinding through JIT'd frames. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: lli -jit-kind=orc -dlopen libc++.dll -dlopen libunwind.dll %s + +@_ZTIi = external constant ptr + +declare void @__cxa_throw(ptr, ptr, ptr) +declare ptr @__cxa_allocate_exception(i64) +declare i32 @__gxx_personality_seh0(...) +declare ptr @__cxa_begin_catch(ptr) +declare void @__cxa_end_catch() + +; A non-leaf function that throws. The unwinder must find its .pdata entry +; to unwind past this frame. +define void @do_throw() #0 personality ptr @__gxx_personality_seh0 { + %ex = call ptr @__cxa_allocate_exception(i64 4) + store i32 99, ptr %ex + call void @__cxa_throw(ptr %ex, ptr @_ZTIi, ptr null) + unreachable +} + +; Entry point: calls do_throw inside a try/catch. If the unwinder can find +; .pdata for both frames, the exception is caught and we return 0. +; If .pdata is not registered, the process crashes (non-zero exit). +define i32 @main() #0 personality ptr @__gxx_personality_seh0 { + invoke void @do_throw() + to label %unreachable unwind label %catch + +unreachable: + ret i32 1 + +catch: + %lp = landingpad { ptr, i32 } + catch ptr @_ZTIi + %exn = extractvalue { ptr, i32 } %lp, 0 + %val = call ptr @__cxa_begin_catch(ptr %exn) + call void @__cxa_end_catch() + ret i32 0 +} + +attributes #0 = { uwtable } diff --git a/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h b/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h deleted file mode 100644 index e6d389820e55d..0000000000000 --- a/llvm/tools/llvm-jitlink/WindowsEasyEHPlugin.h +++ /dev/null @@ -1,167 +0,0 @@ -//===- WindowsEasyEHPlugin.h - Register COFF EH info in-process -*- C++ -*-===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// -// -// Register and deregister .pdata sections in-process using RtlAddFunctionTable -// and RtlDeleteFunctionTable. -// -//===----------------------------------------------------------------------===// - -#ifndef WINDOWSEASYEHPLUGIN_H -#define WINDOWSEASYEHPLUGIN_H - -#include "llvm/ExecutionEngine/JITLink/COFF.h" -#include "llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h" -#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h" - -#include "llvm/Support/Debug.h" -#include "llvm/Support/raw_ostream.h" - -#include - -#ifdef _WIN32 -#include -#else -// For testing purposes only. -using DWORD = uint32_t; -using DWORD64 = uint64_t; -struct RUNTIME_FUNCTION { - DWORD BeginAddress; - DWORD EndAddress; - union { - DWORD UnwindInfoAddress; - DWORD UnwindData; - } DUMMYUNIONNAME; -}; -using PRUNTIME_FUNCTION = RUNTIME_FUNCTION *; - -static inline bool RtlAddFunctionTable(PRUNTIME_FUNCTION FunctionTable, - DWORD Count, DWORD64 BaseAddress) { - llvm::dbgs() << "RtlAddFunctionTable(" << static_cast(FunctionTable) - << ", " << Count << ", " << llvm::formatv("{0:x})", BaseAddress) - << ");\n"; - return true; -} - -static inline bool RtlDeleteFunctionTable(PRUNTIME_FUNCTION FunctionTable) { - llvm::dbgs() << "RtlDeleteFunctionTable(" - << static_cast(FunctionTable) << ");\n"; - return true; -} - -#endif - -namespace llvm::orc { - -class WindowsEasyEHPlugin : public ObjectLinkingLayer::Plugin { -public: - void modifyPassConfig(MaterializationResponsibility &MR, - jitlink::LinkGraph &LG, - jitlink::PassConfiguration &PassConfig) override { - PassConfig.PostFixupPasses.push_back( - [this](jitlink::LinkGraph &G) { return registerFrameInfo(G); }); - } - - Error notifyFailed(MaterializationResponsibility &MR) override { - return Error::success(); - } - - Error notifyRemovingResources(JITDylib &JD, ResourceKey K) override { - return Error::success(); - } - - void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, - ResourceKey SrcKey) override {} - -private: - Error registerFrameInfo(jitlink::LinkGraph &G) { - using namespace shared; - - auto *PDataSection = G.findSectionByName(".pdata"); - if (!PDataSection) - return Error::success(); - - ExecutorAddr Base(~uint64_t(0)); - ExecutorAddrRange PDataRange; - if (auto *ImageBase = jitlink::GetImageBaseSymbol()(G)) { - // If there's an __ImageBase symbol then use it to get the base address. - Base = ImageBase->getAddress(); - PDataRange = jitlink::SectionRange(*PDataSection).getRange(); - } else { - return make_error(".pdata section present but __ImageBase symbol not found in graph", - inconvertibleErrorCode()); - // No __ImageBase. Use the lowest address in this graph as a substitute. - // for (auto &Sec : G.sections()) { - // if (Sec.empty()) - // continue; - // jitlink::SectionRange SR(Sec); - // Base = std::min(Base, SR.getStart()); - // if (&Sec == PDataSection) - // PDataRange = SR.getRange(); - // } - } - - G.allocActions().push_back( - {cantFail(WrapperFunctionCall::Create< - SPSArgList>( - ExecutorAddr::fromPtr(®isterPData), Base, PDataRange)), - cantFail(WrapperFunctionCall::Create>( - ExecutorAddr::fromPtr(&deregisterPData), PDataRange.Start))}); - - return Error::success(); - } - - static shared::CWrapperFunctionBuffer registerPData(const char *ArgData, - size_t ArgSize) { - using namespace shared; - return WrapperFunction:: - handle(ArgData, ArgSize, - [](ExecutorAddr Base, ExecutorAddrRange PDataRange) -> Error { - constexpr size_t RecordSize = sizeof(RUNTIME_FUNCTION); - if (PDataRange.size() % RecordSize) - return make_error( - ".pdata section does not contain an integer number of " - "RUNTIME_FUNCTION records", - inconvertibleErrorCode()); - size_t Count = PDataRange.size() / RecordSize; - if (Count > std::numeric_limits::max()) - return make_error( - ".pdata section contains too many records", - inconvertibleErrorCode()); - if (RtlAddFunctionTable( - PDataRange.Start.toPtr(), Count, - Base.getValue())) - return Error::success(); - else - return make_error( - "RtlAddFunctionTable returned error", - inconvertibleErrorCode()); - }) - .release(); - } - - static shared::CWrapperFunctionBuffer deregisterPData(const char *ArgData, - size_t ArgSize) { - using namespace shared; - return WrapperFunction::handle( - ArgData, ArgSize, - [](ExecutorAddr PDataStart) -> Error { - if (RtlDeleteFunctionTable( - PDataStart.toPtr())) - return Error::success(); - else - return make_error( - "RtlDeleteFunctionTable returned error", - inconvertibleErrorCode()); - }) - .release(); - } -}; - -} // namespace llvm::orc - -#endif // WINDOWSEASYEHPLUGIN_H diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp index 2b57cc6f81393..fbf4679f50894 100644 --- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp +++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp @@ -37,6 +37,7 @@ #include "llvm/ExecutionEngine/Orc/MapperJITLinkMemoryManager.h" #include "llvm/ExecutionEngine/Orc/ObjectFileInterface.h" #include "llvm/ExecutionEngine/Orc/SectCreate.h" +#include "llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h" #include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h" #include "llvm/ExecutionEngine/Orc/Shared/OrcRTBridge.h" #include "llvm/ExecutionEngine/Orc/SimpleRemoteMemoryMapper.h" @@ -79,8 +80,6 @@ #include #endif // LLVM_ON_UNIX -#include "WindowsEasyEHPlugin.h" - #define DEBUG_TYPE "llvm_jitlink" using namespace llvm; @@ -1271,8 +1270,6 @@ Session::Session(std::unique_ptr EPC, Error &Err) } } - ObjLayer->addPlugin(std::make_unique()); - if (DebuggerSupport && TT.isOSBinFormatMachO()) { if (!ProcessSymsJD) { Err = make_error("MachO debugging requires process symbols", @@ -1325,6 +1322,8 @@ Session::Session(std::unique_ptr EPC, Error &Err) return; } } else if (TT.isOSBinFormatCOFF()) { + if (!NoExec) + ObjLayer->addPlugin(std::make_unique()); auto LoadDynLibrary = [&, this](JITDylib &JD, StringRef DLLName) -> Error { if (!DLLName.ends_with_insensitive(".dll")) From 905aef3d25178566b8637cb6239e7466a894e017 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Tue, 19 May 2026 20:00:04 +0100 Subject: [PATCH 10/13] [ORC] Add filterd DLLImportDefinitionGenerator for COFF gen platform --- llvm/lib/ExecutionEngine/Orc/LLJIT.cpp | 37 +++++++++++++ .../Orc/coff-dllimport-filter.ll | 52 +++++++++++++++++++ llvm/tools/llvm-jitlink/llvm-jitlink.cpp | 5 +- 3 files changed, 92 insertions(+), 2 deletions(-) create mode 100644 llvm/test/ExecutionEngine/Orc/coff-dllimport-filter.ll diff --git a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp index edb1aa240ac89..296b668da2c48 100644 --- a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +++ b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp @@ -14,6 +14,7 @@ #include "llvm/ExecutionEngine/Orc/EHFrameRegistrationPlugin.h" #include "llvm/ExecutionEngine/Orc/ELFNixPlatform.h" #include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h" +#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h" #include "llvm/ExecutionEngine/Orc/ExecutorProcessControl.h" #include "llvm/ExecutionEngine/Orc/MachOPlatform.h" #include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h" @@ -596,6 +597,36 @@ class InactivePlatformSupport : public LLJIT::PlatformSupport { } }; +/// Forwards only `__imp_` prefixed symbols to an inner generator. +/// Non matching symbols are ignored. +class ImpSymbolFilterGenerator : public DefinitionGenerator { + std::unique_ptr Inner; + +public: + /// Wraps Inner so it only sees `__imp_` prefixed lookups. + ImpSymbolFilterGenerator(std::unique_ptr Inner) + : Inner(std::move(Inner)) {} + + /// Filters Symbols to `__imp_` prefixed entries and forwards the filtered + /// set to the inner generator via LS, K, JD, and JDLookupFlags. + /// Returns the inner generator's result, or success if no `__imp_` symbols + /// are present in Symbols. + Error tryToGenerate(LookupState &LS, LookupKind K, JITDylib &JD, + JITDylibLookupFlags JDLookupFlags, + const SymbolLookupSet &Symbols) override { + SymbolLookupSet Filtered; + for (auto symbol : Symbols) { + if ((*symbol.first).starts_with("__imp_")) + Filtered.add(symbol.first, symbol.second); + } + + if (!Filtered.empty()) + return Inner->tryToGenerate(LS, K, JD, JDLookupFlags, Filtered); + else + return Error::success(); + } +}; + } // end anonymous namespace namespace llvm { @@ -1281,6 +1312,12 @@ Expected setUpGenericLLVMIRPlatform(LLJIT &J) { // Register .pdata with the Windows unwinder for SEH support. if (J.getTargetTriple().isOSBinFormatCOFF()) { + // Resolve __imp_ symbols (IAT entries) from process DLLs. Filtered + // to avoid intercepting e.g. __gxx_personality_seh0 which needs + // JITLink's ADDR32NB stub instead. + auto DLLImportGen = DLLImportDefinitionGenerator::Create(J.getExecutionSession(), *OLL); + PlatformJD.addGenerator(std::make_unique(std::move(DLLImportGen))); + OLL->addPlugin(std::make_shared()); LLVM_DEBUG(dbgs() << "Enabled seh-frame support.\n"); UseEHFrames = false; diff --git a/llvm/test/ExecutionEngine/Orc/coff-dllimport-filter.ll b/llvm/test/ExecutionEngine/Orc/coff-dllimport-filter.ll new file mode 100644 index 0000000000000..5e8da135709e9 --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/coff-dllimport-filter.ll @@ -0,0 +1,52 @@ +; Test that COFF x86_64 JIT correctly handles both __imp_ symbol resolution +; and C++ exception handling in the same module. +; +; The test uses __imp_GetCurrentProcessId (an IAT-style DLL import) alongside +; a C++ throw/catch that requires the personality function +; (__gxx_personality_seh0) to be callable via an image-relative reference in +; .xdata. Both mechanisms must coexist without interfering. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: lli -jit-kind=orc -dlopen libc++.dll -dlopen libunwind.dll %s + +@_ZTIi = external constant ptr +@__imp_GetCurrentProcessId = external global ptr + +declare void @__cxa_throw(ptr, ptr, ptr) +declare ptr @__cxa_allocate_exception(i64) +declare i32 @__gxx_personality_seh0(...) +declare ptr @__cxa_begin_catch(ptr) +declare void @__cxa_end_catch() + +; Non-leaf function that throws. The unwinder must find its .pdata entry +; to unwind past this frame. +define void @do_throw() #0 personality ptr @__gxx_personality_seh0 { + %ex = call ptr @__cxa_allocate_exception(i64 4) + store i32 77, ptr %ex + call void @__cxa_throw(ptr %ex, ptr @_ZTIi, ptr null) + unreachable +} + +; Entry point: loads a DLL function pointer through an __imp_ symbol AND +; catches an exception. Both paths must work simultaneously. +define i32 @main() #0 personality ptr @__gxx_personality_seh0 { + %fp = load ptr, ptr @__imp_GetCurrentProcessId + %pid = call i32 %fp() + %cmp = icmp eq i32 %pid, 0 + + invoke void @do_throw() + to label %unreachable unwind label %catch + +unreachable: + ret i32 1 + +catch: + %lp = landingpad { ptr, i32 } + catch ptr @_ZTIi + %exn = extractvalue { ptr, i32 } %lp, 0 + %val = call ptr @__cxa_begin_catch(ptr %exn) + call void @__cxa_end_catch() + ret i32 0 +} + +attributes #0 = { uwtable } diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp index fbf4679f50894..089e7c98cb77f 100644 --- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp +++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp @@ -1322,8 +1322,6 @@ Session::Session(std::unique_ptr EPC, Error &Err) return; } } else if (TT.isOSBinFormatCOFF()) { - if (!NoExec) - ObjLayer->addPlugin(std::make_unique()); auto LoadDynLibrary = [&, this](JITDylib &JD, StringRef DLLName) -> Error { if (!DLLName.ends_with_insensitive(".dll")) @@ -1374,6 +1372,9 @@ Session::Session(std::unique_ptr EPC, Error &Err) logAllUnhandledErrors(std::move(TargetSymErr), errs(), "Debugger support not available: "); } + } else if (TT.isOSBinFormatCOFF()) { + if (!NoExec) + ObjLayer->addPlugin(std::make_unique()); } if (auto MainJDOrErr = ES.createJITDylib("main")) From 4198c1650b544d11dc3c476a9b83e87e93f7df93 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Wed, 20 May 2026 07:51:29 +0100 Subject: [PATCH 11/13] [JITLink][ORC] Extend SEHFrameKeepAlivePass and registration for COMDAT .pdata$* sections --- .../Orc/SEHFrameRegistrationPlugin.h | 11 +++- .../ExecutionEngine/JITLink/SEHFrameSupport.h | 51 ++++++++++--------- .../Orc/SEHFrameRegistrationPlugin.cpp | 43 ++++++++-------- .../Orc/coff-comdat-pdata-keepalive.ll | 49 ++++++++++++++++++ 4 files changed, 110 insertions(+), 44 deletions(-) create mode 100644 llvm/test/ExecutionEngine/Orc/coff-comdat-pdata-keepalive.ll diff --git a/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h index 12e8dc1d6f45e..937e28f0c3a8d 100644 --- a/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h +++ b/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h @@ -1,4 +1,4 @@ -//===- WindowsEasyEHPlugin.h - Register COFF EH info in-process -*- C++ -*-===// +//===- SEHFrameRegistrationPlugin.h - Register COFF EH info in-process -*- C++ -*-===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -18,8 +18,15 @@ namespace llvm::orc { +/// Registers .pdata sections with the Windows unwinder via RtlAddFunctionTable. +/// +/// This plugin enables SEH-based stack unwinding for JIT'd code on Windows +/// by registering unwind metadata at finalization and deregistering it on +/// deallocation. class LLVM_ABI SEHFrameRegistrationPlugin : public LinkGraphLinkingLayer::Plugin { public: + /// Adds a pass to PassConfig that registers .pdata sections in LG with the + /// OS unwinder. void modifyPassConfig(MaterializationResponsibility &MR, jitlink::LinkGraph &LG, jitlink::PassConfiguration &PassConfig) override; @@ -36,6 +43,8 @@ class LLVM_ABI SEHFrameRegistrationPlugin : public LinkGraphLinkingLayer::Plugin ResourceKey SrcKey) override {} private: + /// Registers .pdata sections in G with the OS unwinder. + /// Returns an error if registration setup fails. Error registerFrameInfo(jitlink::LinkGraph &G); }; diff --git a/llvm/lib/ExecutionEngine/JITLink/SEHFrameSupport.h b/llvm/lib/ExecutionEngine/JITLink/SEHFrameSupport.h index f17dfe98ba4f9..3ba3bc4acb79b 100644 --- a/llvm/lib/ExecutionEngine/JITLink/SEHFrameSupport.h +++ b/llvm/lib/ExecutionEngine/JITLink/SEHFrameSupport.h @@ -21,39 +21,44 @@ namespace llvm { namespace jitlink { -/// This pass adds keep-alive edge from SEH frame sections -/// to the parent function content block. +/// This pass adds keep-alive edges from SEH frame sections +/// to parent function content blocks. class SEHFrameKeepAlivePass { public: - SEHFrameKeepAlivePass(StringRef SEHFrameSectionName) - : SEHFrameSectionName(SEHFrameSectionName) {} + /// SEHFrameSectionNamePrefix to match against graph sections + SEHFrameKeepAlivePass(StringRef SEHFrameSectionNamePrefix) + : SEHFrameSectionNamePrefix(SEHFrameSectionNamePrefix) {} + /// Adds keep alive edges in G for all sections matching + /// SEHFrameSectionNamePrefix Error operator()(LinkGraph &G) { - auto *S = G.findSectionByName(SEHFrameSectionName); - if (!S) - return Error::success(); - - // Simply consider every block pointed by seh frame block as parants. - // This adds some unnecessary keep-alive edges to unwind info blocks, - // (xdata) but these blocks are usually dead by default, so they wouldn't - // count for the fate of seh frame block. - for (auto *B : S->blocks()) { - auto &DummySymbol = G.addAnonymousSymbol(*B, 0, 0, false, false); - SetVector Children; - for (auto &E : B->edges()) { - auto &Sym = E.getTarget(); - if (!Sym.isDefined()) - continue; - Children.insert(&Sym.getBlock()); + for (auto &S : G.sections()) { + if (!S.getName().starts_with(SEHFrameSectionNamePrefix)) + continue; + + // Simply consider every block pointed by seh frame block as parents. + // This adds some unnecessary keep-alive edges to unwind info blocks, + // (xdata) but these blocks are usually dead by default, so they wouldn't + // count for the fate of seh frame block. + for (auto *B : S.blocks()) { + auto &DummySymbol = G.addAnonymousSymbol(*B, 0, 0, false, false); + SetVector Children; + for (auto &E : B->edges()) { + auto &Sym = E.getTarget(); + if (!Sym.isDefined()) + continue; + Children.insert(&Sym.getBlock()); + } + for (auto *Child : Children) + Child->addEdge(Edge(Edge::KeepAlive, 0, DummySymbol, 0)); } - for (auto *Child : Children) - Child->addEdge(Edge(Edge::KeepAlive, 0, DummySymbol, 0)); } return Error::success(); } private: - StringRef SEHFrameSectionName; + /// Sections starting with this prefix are kept alive + StringRef SEHFrameSectionNamePrefix; }; } // end namespace jitlink diff --git a/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp index ef65fc370f9cb..8c8b9bc5f361e 100644 --- a/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp +++ b/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp @@ -26,6 +26,9 @@ using namespace llvm::jitlink; namespace llvm::orc { +// Calls RtlAddFunctionTable to register .pdata entries with the OS unwinder. +// ArgData/ArgSize contain the serialized base address and .pdata range. +// Returns a serialized error result indicating success or failure. shared::CWrapperFunctionBuffer registerPData(const char *ArgData, size_t ArgSize) { using namespace shared; @@ -61,6 +64,9 @@ shared::CWrapperFunctionBuffer registerPData(const char *ArgData, .release(); } +// Calls RtlDeleteFunctionTable to unregister .pdata entries from the OS unwinder. +// ArgData/ArgSize contain the serialized .pdata start address. +// Returns a serialized error result indicating success or failure. shared::CWrapperFunctionBuffer deregisterPData(const char *ArgData, size_t ArgSize) { using namespace shared; @@ -94,29 +100,26 @@ void SEHFrameRegistrationPlugin::modifyPassConfig( Error SEHFrameRegistrationPlugin::registerFrameInfo(jitlink::LinkGraph &G) { using namespace shared; - auto *PDataSection = G.findSectionByName(".pdata"); - if (!PDataSection) + auto *ImageBase = jitlink::GetImageBaseSymbol()(G); + if (!ImageBase) return Error::success(); - ExecutorAddr Base(~uint64_t(0)); - ExecutorAddrRange PDataRange; - if (auto *ImageBase = jitlink::GetImageBaseSymbol()(G)) { - // If there's an __ImageBase symbol then use it to get the base address. - Base = ImageBase->getAddress(); - PDataRange = jitlink::SectionRange(*PDataSection).getRange(); - } else { - return make_error( - ".pdata section present but __ImageBase symbol not found in graph", - inconvertibleErrorCode()); + // Register each .pdata prefixed section (includes COMDAT + // .pdata$). + for (auto &PDataSection : G.sections()) { + if (!PDataSection.getName().starts_with(".pdata")) + continue; + + ExecutorAddr Base(ImageBase->getAddress()); + ExecutorAddrRange PDataRange(jitlink::SectionRange(PDataSection).getRange()); + + G.allocActions().push_back( + {cantFail(WrapperFunctionCall::Create< + SPSArgList>( + ExecutorAddr::fromPtr(®isterPData), Base, PDataRange)), + cantFail(WrapperFunctionCall::Create>( + ExecutorAddr::fromPtr(&deregisterPData), PDataRange.Start))}); } - - G.allocActions().push_back( - {cantFail(WrapperFunctionCall::Create< - SPSArgList>( - ExecutorAddr::fromPtr(®isterPData), Base, PDataRange)), - cantFail(WrapperFunctionCall::Create>( - ExecutorAddr::fromPtr(&deregisterPData), PDataRange.Start))}); - return Error::success(); } diff --git a/llvm/test/ExecutionEngine/Orc/coff-comdat-pdata-keepalive.ll b/llvm/test/ExecutionEngine/Orc/coff-comdat-pdata-keepalive.ll new file mode 100644 index 0000000000000..e5866f21a8e94 --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/coff-comdat-pdata-keepalive.ll @@ -0,0 +1,49 @@ +; Test that COMDAT .pdata$ sections are kept alive during +; dead-stripping, ensuring the Windows unwinder can find unwind info +; for COMDAT functions (e.g., template instantiations). +; +; A COMDAT function produces .pdata$ and .xdata$ sections. +; SEHFrameKeepAlivePass must process all sections starting with ".pdata" +; (not just the exact ".pdata" section) so the pruner preserves them. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: lli -jit-kind=orc -dlopen libc++.dll -dlopen libunwind.dll %s + +@_ZTIi = external constant ptr + +declare void @__cxa_throw(ptr, ptr, ptr) +declare ptr @__cxa_allocate_exception(i64) +declare i32 @__gxx_personality_seh0(...) +declare ptr @__cxa_begin_catch(ptr) +declare void @__cxa_end_catch() + +; COMDAT function that throws — its .pdata$comdat_thrower must survive +; dead-stripping for the unwinder to traverse this frame. +$comdat_thrower = comdat any + +define weak void @comdat_thrower() #0 comdat personality ptr @__gxx_personality_seh0 { + %ex = call ptr @__cxa_allocate_exception(i64 4) + store i32 55, ptr %ex + call void @__cxa_throw(ptr %ex, ptr @_ZTIi, ptr null) + unreachable +} + +; Calls comdat_thrower inside a try/catch. Verifies the unwinder can +; unwind through the COMDAT frame using its preserved .pdata entry. +define i32 @main() #0 personality ptr @__gxx_personality_seh0 { + invoke void @comdat_thrower() + to label %unreachable unwind label %catch + +unreachable: + ret i32 1 + +catch: + %lp = landingpad { ptr, i32 } + catch ptr @_ZTIi + %exn = extractvalue { ptr, i32 } %lp, 0 + %val = call ptr @__cxa_begin_catch(ptr %exn) + call void @__cxa_end_catch() + ret i32 0 +} + +attributes #0 = { uwtable } From 1b574b035c6220645f071b856964d0018ef6b781 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Thu, 21 May 2026 07:14:42 +0100 Subject: [PATCH 12/13] [JITLink][COFF] Remove dead findDefinedSymbolByName call from GetImageBaseSymbol --- llvm/lib/ExecutionEngine/JITLink/COFF.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF.cpp index 7e9bd0fe3092e..eb34b1a5149ac 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF.cpp @@ -135,14 +135,12 @@ Symbol *GetImageBaseSymbol::operator()(LinkGraph &G) { if (ImageBase) return *ImageBase; + // __ImageBase is only ever external (generic platform) or absolute (COFFPlatform). auto IBN = G.intern(ImageBaseName); ImageBase = G.findExternalSymbolByName(IBN); if (*ImageBase) return *ImageBase; ImageBase = G.findAbsoluteSymbolByName(IBN); - if (*ImageBase) - return *ImageBase; - ImageBase = G.findDefinedSymbolByName(IBN); if (*ImageBase) return *ImageBase; From 13c2f438225ec9ba0bdad6673b8db0db4f0be321 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Thu, 21 May 2026 07:30:41 +0100 Subject: [PATCH 13/13] [ORC] Add end-to-end SEH integration tests for COFF x86_64 --- .../Orc/coff-imagebase-resolution.ll | 3 +- .../Orc/throw-catch-mingw-seh-no-fp.ll | 68 +++++++++++++++++++ .../Orc/throw-catch-mingw-seh.ll | 60 ++++++++++++++++ 3 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 llvm/test/ExecutionEngine/Orc/throw-catch-mingw-seh-no-fp.ll create mode 100644 llvm/test/ExecutionEngine/Orc/throw-catch-mingw-seh.ll diff --git a/llvm/test/ExecutionEngine/Orc/coff-imagebase-resolution.ll b/llvm/test/ExecutionEngine/Orc/coff-imagebase-resolution.ll index 3ecb1d068c42e..90f59d1d995f9 100644 --- a/llvm/test/ExecutionEngine/Orc/coff-imagebase-resolution.ll +++ b/llvm/test/ExecutionEngine/Orc/coff-imagebase-resolution.ll @@ -1,7 +1,6 @@ ; Test that __ImageBase resolves to the correct base address so that ; image-relative (ADDR32NB) relocations in .pdata/.xdata produce valid -; 32-bit offsets. Without the fix, __ImageBase is zero and the offsets -; overflow, causing a link failure. +; 32-bit offsets. ; ; The test compiles a non-leaf function (one that calls another) with the ; uwtable attribute. Non-leaf functions require unwind info, so the compiler diff --git a/llvm/test/ExecutionEngine/Orc/throw-catch-mingw-seh-no-fp.ll b/llvm/test/ExecutionEngine/Orc/throw-catch-mingw-seh-no-fp.ll new file mode 100644 index 0000000000000..254dfa595d7d6 --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/throw-catch-mingw-seh-no-fp.ll @@ -0,0 +1,68 @@ +; Integration test: C++ throw/catch through a no-frame-pointer function +; on MinGW x86_64. +; +; This tests that the Windows unwinder correctly processes UNWIND_INFO with +; ALLOC_SMALL unwind codes (used when there's no frame pointer and the +; function only needs a small stack allocation for spills/locals). +; +; Without a frame pointer, the unwinder relies entirely on .xdata unwind +; codes to restore RSP. If .pdata/.xdata are wrong or missing, the +; unwinder computes a garbage return address and crashes. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: lli -jit-kind=orc -dlopen libc++.dll -dlopen libunwind.dll %s + +@_ZTIi = external constant ptr + +declare void @__cxa_throw(ptr, ptr, ptr) +declare ptr @__cxa_allocate_exception(i64) +declare i32 @__gxx_personality_seh0(...) +declare ptr @__cxa_begin_catch(ptr) +declare void @__cxa_end_catch() + +; A non-leaf function WITHOUT a frame pointer that throws. +; The compiler will emit ALLOC_SMALL unwind codes in .xdata to describe +; the stack adjustment. The unwinder must use these codes to restore RSP. +define void @no_fp_thrower() #0 personality ptr @__gxx_personality_seh0 { + ; Use some local state to force a stack frame without frame pointer + %buf = alloca [16 x i8], align 8 + store i8 1, ptr %buf + %ex = call ptr @__cxa_allocate_exception(i64 4) + store i32 77, ptr %ex + call void @__cxa_throw(ptr %ex, ptr @_ZTIi, ptr null) + unreachable +} + +; Middle frame: also no frame pointer. Tests unwinding through multiple +; no-FP frames in sequence. +define void @no_fp_caller() #0 personality ptr @__gxx_personality_seh0 { + %local = alloca i64, align 8 + store i64 123, ptr %local + call void @no_fp_thrower() + ret void +} + +; Entry point: catches the exception after unwinding through two +; no-frame-pointer frames. +define i32 @main() #0 personality ptr @__gxx_personality_seh0 { + invoke void @no_fp_caller() + to label %unreachable unwind label %catch + +unreachable: + ret i32 1 + +catch: + %lp = landingpad { ptr, i32 } + catch ptr @_ZTIi + %exn = extractvalue { ptr, i32 } %lp, 0 + %caught = call ptr @__cxa_begin_catch(ptr %exn) + %val = load i32, ptr %caught + call void @__cxa_end_catch() + ; Return 0 on success (exception value is 77) + %is77 = icmp eq i32 %val, 77 + %ret = select i1 %is77, i32 0, i32 2 + ret i32 %ret +} + +; uwtable but NO frame pointer — forces ALLOC_SMALL unwind codes +attributes #0 = { uwtable "frame-pointer"="none" } diff --git a/llvm/test/ExecutionEngine/Orc/throw-catch-mingw-seh.ll b/llvm/test/ExecutionEngine/Orc/throw-catch-mingw-seh.ll new file mode 100644 index 0000000000000..b4859ec6e1bd8 --- /dev/null +++ b/llvm/test/ExecutionEngine/Orc/throw-catch-mingw-seh.ll @@ -0,0 +1,60 @@ +; Integration test: C++ throw/catch through JIT'd frames using Windows SEH +; on MinGW x86_64. +; +; This exercises the full COFF SEH pipeline: JITLink linker selection, +; __ImageBase resolution, PLT/ADDR32NB stubs, SEH registration plugin, +; DLLImport filtering, and .pdata keep-alive for COMDAT sections. +; +; The test throws an integer exception through two JIT'd frames (main → +; call_thrower → do_throw) and catches it in main. If any part of the +; pipeline is broken, the unwinder crashes or the link fails. +; +; REQUIRES: system-windows && host-unwind-supports-jit +; RUN: lli -jit-kind=orc -dlopen libc++.dll -dlopen libunwind.dll %s + +@_ZTIi = external constant ptr + +declare void @__cxa_throw(ptr, ptr, ptr) +declare ptr @__cxa_allocate_exception(i64) +declare i32 @__gxx_personality_seh0(...) +declare ptr @__cxa_begin_catch(ptr) +declare void @__cxa_end_catch() + +; Innermost frame: allocates and throws an integer exception. +define void @do_throw() #0 personality ptr @__gxx_personality_seh0 { + %ex = call ptr @__cxa_allocate_exception(i64 4) + store i32 42, ptr %ex + call void @__cxa_throw(ptr %ex, ptr @_ZTIi, ptr null) + unreachable +} + +; Middle frame: calls do_throw. The unwinder must traverse this frame +; using its .pdata entry to reach main's catch handler. +define void @call_thrower() #0 personality ptr @__gxx_personality_seh0 { + call void @do_throw() + ret void +} + +; Entry point: invokes call_thrower inside a try/catch. Verifies the +; exception propagates through two JIT'd frames and is caught correctly. +define i32 @main() #0 personality ptr @__gxx_personality_seh0 { + invoke void @call_thrower() + to label %unreachable unwind label %catch + +unreachable: + ret i32 1 + +catch: + %lp = landingpad { ptr, i32 } + catch ptr @_ZTIi + %exn = extractvalue { ptr, i32 } %lp, 0 + %caught = call ptr @__cxa_begin_catch(ptr %exn) + %val = load i32, ptr %caught + call void @__cxa_end_catch() + ; Return 0 on success (exception caught with correct value) + %is42 = icmp eq i32 %val, 42 + %ret = select i1 %is42, i32 0, i32 2 + ret i32 %ret +} + +attributes #0 = { uwtable }