From 296a6d1f445561501a4e827363027a37f11d6cfb Mon Sep 17 00:00:00 2001 From: ikappaki Date: Wed, 13 May 2026 07:53:43 +0100 Subject: [PATCH 01/25] 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 7f8aec6482b5fe..99df9b533ced49 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 e6626b582bcd8d..5f57a943e36a45 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 dbfaadb5de2b88..19d88bf00c53d1 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 cbecd85df5e43c..8dbd06ff4a7c9e 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 e8d505f218b697..2717ca34d4547c 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 0776934d3209ba..90818944ebecbd 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/25] [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 18db7d24a8262e..5e2227ceec88ca 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 0d7baef3a0d936..d910235fdbeef5 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 00000000000000..b119eb65dc769c --- /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/25] [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 aa91ac053bb509..60046aa4fa547a 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 00000000000000..3ecb1d068c42ed --- /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 e44079aa5cd265..aa35b3ad945229 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 90818944ebecbd..e6d389820e55d4 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/25] [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 60046aa4fa547a..0b6ee438165000 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 00000000000000..c8bd07eeb83f98 --- /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/25] [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 0b6ee438165000..2cf777886cdbb9 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 00000000000000..942303e2243fcb --- /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/25] 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 aa35b3ad945229..630e4bf9e6ef33 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/25] [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 984ef81ae6c99b..4847b5bc09c09e 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 00000000000000..bb6e4f7f5b38ef --- /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/25] [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 5e2227ceec88ca..edefd6128fe076 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/25] [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 00000000000000..12e8dc1d6f45ef --- /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 8c78c0250cfb8d..ef85f1c08a43df 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 edefd6128fe076..edb1aa240ac890 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 00000000000000..ef65fc370f9cb9 --- /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 00000000000000..178e296882a54b --- /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 e6d389820e55d4..00000000000000 --- 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 2b57cc6f81393d..fbf4679f508945 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/25] [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 edb1aa240ac890..296b668da2c487 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 00000000000000..5e8da135709e93 --- /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 fbf4679f508945..089e7c98cb77fb 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/25] [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 12e8dc1d6f45ef..937e28f0c3a8db 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 f17dfe98ba4f96..3ba3bc4acb79b9 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 ef65fc370f9cb9..8c8b9bc5f361e6 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 00000000000000..e5866f21a8e94d --- /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/25] [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 7e9bd0fe3092ef..eb34b1a5149acc 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/25] [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 3ecb1d068c42ed..90f59d1d995f96 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 00000000000000..254dfa595d7d6b --- /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 00000000000000..b4859ec6e1bd89 --- /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 } From 137a5557efd8de5633ae5638e05f71682173a631 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 30 May 2026 18:18:21 +0100 Subject: [PATCH 14/25] [JITLink][COFF] Add GOT/Stubs optimizer pass for COFF x86_64 --- llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp index 2cf777886cdbb9..e2870e3de65ea2 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp @@ -411,6 +411,9 @@ void link_COFF_x86_64(std::unique_ptr G, // Add COFF edge lowering passes. Config.PreFixupPasses.push_back(COFFLinkGraphLowering_x86_64()); + + // Add GOT/Stubs optimizer pass. + Config.PreFixupPasses.push_back(x86_64::optimizeGOTAndStubAccesses); } if (auto Err = Ctx->modifyPassConfig(*G, Config)) From 45b892fcded4c89ce4b9c2a4a3ee459382eb56f7 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 6 Jun 2026 16:24:46 +0100 Subject: [PATCH 15/25] [ORC] Fix crash in SEHFrameRegistrationPlugin on dead-stripped COMDAT .pdata sections --- .../Orc/SEHFrameRegistrationPlugin.h | 1 - .../Orc/SEHFrameRegistrationPlugin.cpp | 4 ++ .../ExecutionEngine/Orc/CMakeLists.txt | 1 + .../Orc/SEHFrameRegistrationPluginTest.cpp | 54 +++++++++++++++++++ 4 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 llvm/unittests/ExecutionEngine/Orc/SEHFrameRegistrationPluginTest.cpp diff --git a/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h b/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h index 937e28f0c3a8db..10e10ebd034d45 100644 --- a/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h +++ b/llvm/include/llvm/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.h @@ -42,7 +42,6 @@ class LLVM_ABI SEHFrameRegistrationPlugin : public LinkGraphLinkingLayer::Plugin void notifyTransferringResources(JITDylib &JD, ResourceKey DstKey, 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/Orc/SEHFrameRegistrationPlugin.cpp b/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp index 8c8b9bc5f361e6..95216c725f8e3d 100644 --- a/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp +++ b/llvm/lib/ExecutionEngine/Orc/SEHFrameRegistrationPlugin.cpp @@ -113,6 +113,10 @@ Error SEHFrameRegistrationPlugin::registerFrameInfo(jitlink::LinkGraph &G) { ExecutorAddr Base(ImageBase->getAddress()); ExecutorAddrRange PDataRange(jitlink::SectionRange(PDataSection).getRange()); + // Skip empty sections (e.g. dead-stripped COMDAT .pdata). + if (PDataRange.size() == 0) + continue; + G.allocActions().push_back( {cantFail(WrapperFunctionCall::Create< SPSArgList>( diff --git a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt index d910235fdbeef5..e529eca0c34be4 100644 --- a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt +++ b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt @@ -53,6 +53,7 @@ add_llvm_unittest(OrcJITTests JITLinkRedirectionManagerTest.cpp LLJITJITLinkSelectionTest.cpp ReOptimizeLayerTest.cpp + SEHFrameRegistrationPluginTest.cpp EXPORT_SYMBOLS ) diff --git a/llvm/unittests/ExecutionEngine/Orc/SEHFrameRegistrationPluginTest.cpp b/llvm/unittests/ExecutionEngine/Orc/SEHFrameRegistrationPluginTest.cpp new file mode 100644 index 00000000000000..83294a40bc8004 --- /dev/null +++ b/llvm/unittests/ExecutionEngine/Orc/SEHFrameRegistrationPluginTest.cpp @@ -0,0 +1,54 @@ +//===-- SEHFrameRegistrationPluginTest.cpp - Unit tests -------------------===// +// +// 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/JITLink.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::jitlink; +using namespace llvm::orc; + +// Test: registerFrameInfo must skip .pdata sections that have no blocks +// (e.g. COMDAT sections whose blocks were dead-stripped during pruning). +// Empty sections produce a zero-sized SectionRange; registering them with +// RtlAddFunctionTable is invalid (zero entries at a null base address). +TEST(SEHFrameRegistrationPluginTest, SkipsEmptyPDataSections) { + LinkGraph G("test.obj", std::make_shared(), + Triple("x86_64-pc-windows-msvc"), SubtargetFeatures(), + getGenericEdgeKindName); + + // Minimal .text with a block (0xC3 = x86 ret, content is arbitrary). + auto &TextSec = G.createSection(".text", MemProt::Read | MemProt::Exec); + char TextContent[] = {'\xc3'}; + auto &TextBlock = G.createContentBlock(TextSec, TextContent, + ExecutorAddr(0x1000), 16, 0); + G.addDefinedSymbol(TextBlock, 0, "main", 1, Linkage::Strong, Scope::Default, + true, false); + + // __ImageBase resolved (as COFFImageBaseResolution_x86_64 would do). + auto &ImageBase = G.addExternalSymbol("__ImageBase", 0, false); + ImageBase.getAddressable().setAddress(ExecutorAddr(0x1000)); + + // A .pdata section with content (should produce an allocAction). + auto &PDataSec = G.createSection(".pdata", MemProt::Read); + char PDataContent[12] = {}; + G.createContentBlock(PDataSec, PDataContent, ExecutorAddr(0x2000), 4, 0); + + // Two empty .pdata$ sections (simulating dead-stripped COMDATs). + G.createSection(".pdata$dead_comdat_1", MemProt::Read); + G.createSection(".pdata$dead_comdat_2", MemProt::Read); + + SEHFrameRegistrationPlugin Plugin; + EXPECT_THAT_ERROR(Plugin.registerFrameInfo(G), Succeeded()); + + // Expect exactly one allocAction for the non-empty .pdata section. + // The two empty sections must not generate allocActions. + EXPECT_EQ(G.allocActions().size(), 1u); +} From 713a692c607c18966aec432f8fa6538f39f765e9 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Mon, 27 Jul 2026 17:47:28 +0100 Subject: [PATCH 16/25] [JITLink][COFF] Migrate COFF x86_64 edge kinds to generic EdgeKind_x86_64 enum --- .../llvm/ExecutionEngine/JITLink/x86_64.h | 31 ++++++++++ .../ExecutionEngine/JITLink/COFF_x86_64.cpp | 61 +++++-------------- llvm/lib/ExecutionEngine/JITLink/x86_64.cpp | 6 ++ 3 files changed, 53 insertions(+), 45 deletions(-) diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/x86_64.h b/llvm/include/llvm/ExecutionEngine/JITLink/x86_64.h index 5ce73fa3cfb835..add3d41238df88 100644 --- a/llvm/include/llvm/ExecutionEngine/JITLink/x86_64.h +++ b/llvm/include/llvm/ExecutionEngine/JITLink/x86_64.h @@ -42,6 +42,16 @@ enum EdgeKind_x86_64 : Edge::Kind { /// Pointer32, + /// A 32-bit image-relative pointer value relocation. + /// + /// The value written is the target's offset from a base address (typically + /// the lowest allocated address in the link graph). + /// + /// Fixup expression: + /// Fixup <- Target + Addend : uint32 + /// + Pointer32NB, + /// A signed 32-bit pointer value relocation /// /// Fixup expression: @@ -427,6 +437,27 @@ enum EdgeKind_x86_64 : Edge::Kind { /// phase will result in an assert/unreachable during the fixup phase. /// RequestTLVPAndTransformToPCRel32TLVPLoadREXRelaxable, + + /// A 32-bit section-relative relocation. + /// + /// The value written is the target's offset from the start of its + /// containing section. + /// + /// Fixup expression: + /// Fixup <- Target + Addend : uint32 + /// + SecRel32, + + /// A 16-bit section index relocation. + /// + /// The value written is the 1-based index of the section containing the + /// target symbol. + /// + /// Fixup expression: + /// Fixup <- Target + Addend : uint16 + /// + SectionIdx16, + // First platform specific relocation. FirstPlatformRelocation }; diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp index e2870e3de65ea2..1fbebdf0b11c18 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp @@ -27,14 +27,6 @@ using namespace llvm::jitlink; namespace { -enum EdgeKind_coff_x86_64 : Edge::Kind { - PCRel32 = x86_64::FirstPlatformRelocation, - Pointer32NB, - Pointer64, - SectionIdx16, - SecRel32, -}; - class COFFJITLinker_x86_64 : public JITLinker { friend class JITLinker; @@ -100,52 +92,52 @@ class COFFLinkGraphBuilder_x86_64 : public COFFLinkGraphBuilder { case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_ADDR32NB: { if (!ImageBase) ImageBase = &addImageBaseSymbol(); - Kind = EdgeKind_coff_x86_64::Pointer32NB; + Kind = x86_64::Pointer32NB; Addend = *reinterpret_cast(FixupPtr); break; } case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_REL32: { - Kind = EdgeKind_coff_x86_64::PCRel32; + Kind = x86_64::PCRel32; Addend = *reinterpret_cast(FixupPtr); break; } case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_REL32_1: { - Kind = EdgeKind_coff_x86_64::PCRel32; + Kind = x86_64::PCRel32; Addend = *reinterpret_cast(FixupPtr); Addend -= 1; break; } case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_REL32_2: { - Kind = EdgeKind_coff_x86_64::PCRel32; + Kind = x86_64::PCRel32; Addend = *reinterpret_cast(FixupPtr); Addend -= 2; break; } case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_REL32_3: { - Kind = EdgeKind_coff_x86_64::PCRel32; + Kind = x86_64::PCRel32; Addend = *reinterpret_cast(FixupPtr); Addend -= 3; break; } case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_REL32_4: { - Kind = EdgeKind_coff_x86_64::PCRel32; + Kind = x86_64::PCRel32; Addend = *reinterpret_cast(FixupPtr); Addend -= 4; break; } case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_REL32_5: { - Kind = EdgeKind_coff_x86_64::PCRel32; + Kind = x86_64::PCRel32; Addend = *reinterpret_cast(FixupPtr); Addend -= 5; break; } case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_ADDR64: { - Kind = EdgeKind_coff_x86_64::Pointer64; + Kind = x86_64::Pointer64; Addend = *reinterpret_cast(FixupPtr); break; } case COFF::RelocationTypeAMD64::IMAGE_REL_AMD64_SECTION: { - Kind = EdgeKind_coff_x86_64::SectionIdx16; + Kind = x86_64::SectionIdx16; Addend = *reinterpret_cast(FixupPtr); uint64_t SectionIdx = 0; if (COFFSymbol.isAbsolute()) @@ -163,7 +155,7 @@ class COFFLinkGraphBuilder_x86_64 : public COFFLinkGraphBuilder { // FIXME: SECREL to external symbol should be handled if (!GraphSymbol->isDefined()) return Error::success(); - Kind = EdgeKind_coff_x86_64::SecRel32; + Kind = x86_64::SecRel32; Addend = *reinterpret_cast(FixupPtr); break; } @@ -201,26 +193,18 @@ class COFFLinkGraphLowering_x86_64 { for (auto *B : G.blocks()) { for (auto &E : B->edges()) { switch (E.getKind()) { - case EdgeKind_coff_x86_64::Pointer32NB: { + case x86_64::Pointer32NB: { auto ImageBase = GetImageBase(G); assert(ImageBase && "__ImageBase symbol must be defined"); E.setAddend(E.getAddend() - ImageBase->getAddress().getValue()); E.setKind(x86_64::Pointer32); break; } - case EdgeKind_coff_x86_64::PCRel32: { - E.setKind(x86_64::PCRel32); - break; - } - case EdgeKind_coff_x86_64::Pointer64: { - E.setKind(x86_64::Pointer64); - break; - } - case EdgeKind_coff_x86_64::SectionIdx16: { + case x86_64::SectionIdx16: { E.setKind(x86_64::Pointer16); break; } - case EdgeKind_coff_x86_64::SecRel32: { + case x86_64::SecRel32: { E.setAddend(E.getAddend() - getSectionStart(E.getTarget().getSection()).getValue()); E.setKind(x86_64::Pointer32); @@ -287,7 +271,7 @@ class COFFPointer32NBStubsManager : public llvm::jitlink::TableManagergetFixupAddress(E) << " (" << B->getAddress() << " + " @@ -336,7 +320,7 @@ Error buildTables_COFF_x86_64(LinkGraph &G) { // 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 && + if (E.getKind() == x86_64::PCRel32 && !E.getTarget().isDefined()) { E.setKind(x86_64::BranchPCRel32); } @@ -354,20 +338,7 @@ namespace jitlink { /// Return the string name of the given COFF x86_64 edge kind. const char *getCOFFX86RelocationKindName(Edge::Kind R) { - switch (R) { - case PCRel32: - return "PCRel32"; - case Pointer32NB: - return "Pointer32NB"; - case Pointer64: - return "Pointer64"; - case SectionIdx16: - return "SectionIdx16"; - case SecRel32: - return "SecRel32"; - default: - return x86_64::getEdgeKindName(R); - } + return x86_64::getEdgeKindName(R); } Expected> createLinkGraphFromCOFFObject_x86_64( diff --git a/llvm/lib/ExecutionEngine/JITLink/x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/x86_64.cpp index 9d312a281315aa..6c7960a5d9fe3e 100644 --- a/llvm/lib/ExecutionEngine/JITLink/x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/x86_64.cpp @@ -24,6 +24,8 @@ const char *getEdgeKindName(Edge::Kind K) { return "Pointer64"; case Pointer32: return "Pointer32"; + case Pointer32NB: + return "Pointer32NB"; case Pointer32Signed: return "Pointer32Signed"; case Pointer16: @@ -74,6 +76,10 @@ const char *getEdgeKindName(Edge::Kind K) { return "PCRel32TLVPLoadREXRelaxable"; case RequestTLVPAndTransformToPCRel32TLVPLoadREXRelaxable: return "RequestTLVPAndTransformToPCRel32TLVPLoadREXRelaxable"; + case SecRel32: + return "SecRel32"; + case SectionIdx16: + return "SectionIdx16"; default: return getGenericEdgeKindName(K); } From 5b0d3320a5373b438d5fd536eef8833abb8cc47b Mon Sep 17 00:00:00 2001 From: mkovacevic99 Date: Fri, 19 Jun 2026 15:33:42 +0200 Subject: [PATCH 17/25] [JITLink][COFF] Synthesize __imp_ IAT entries (#203906) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a default COFF/x86_64 JITLink pass that synthesizes `__imp_` Import Address Table (IAT) entries for dllimport references. This allows COFF objects using dllimport to be JIT-linked without a hand-built import library or a special generator. On COFF, `__declspec(dllimport)` codegen emits indirect accesses through a named `__imp_X` symbol (`callq *__imp_bar(%rip)`; `movq __imp_g(%rip)` for data), with `__imp_X` left undefined. JITLink had no handling for this. The new pass — the COFF counterpart of the ELF/Mach-O GOT builder — defines each undefined external `__imp_X` over an 8-byte slot holding the address of `X`, and leaves `X` as an ordinary external to be resolved normally (import library, dynamic-library search generator, etc.). Both the call and data-access forms then resolve indirectly through the slot. Rather than the `GOTTableManager` pattern (anonymous entry + edge redirection), the pass defines the *named* `__imp_X` symbol over the slot. ELF GOT references are nameless edge kinds, so that builder must create an anonymous entry and redirect edges; COFF references `__imp_X` by name, so defining it is simpler — no edge rewriting, no orphaned-external cleanup, sharing is automatic, and the call/data-access forms are handled identically. x86_64 only (runs in the COFF/x86_64 backend's default pass pipeline). New lit test `COFF_dllimport_iat.s`: assembles an object referencing `__imp_bar` (call) and `__imp_foo` (data load), supplies `foo`/`bar` via `-abs`, links with `-noexec`, and uses `jitlink-check` to verify each `__imp_` slot holds the target's address and that the references resolve through the slot. Partly implements github issue: https://github.com/llvm/llvm-project/issues/190122 In the comment section of the github issue there is this comment https://github.com/llvm/llvm-project/issues/190122#issuecomment-4617328036 This PR implements point 2 Synthesis IAT entries. --- .../ExecutionEngine/JITLink/COFF_x86_64.cpp | 78 +++++++++++++++++++ .../JITLink/x86-64/COFF_dllimport_iat.s | 55 +++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 llvm/test/ExecutionEngine/JITLink/x86-64/COFF_dllimport_iat.s diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp index 1fbebdf0b11c18..043eeddd378930 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp @@ -331,6 +331,79 @@ Error buildTables_COFF_x86_64(LinkGraph &G) { return Error::success(); } +// Synthesize COFF __imp_ Import Address Table (IAT) entries. +// +// For a dllimport reference, codegen emits an indirect access through a named +// __imp_X symbol, e.g. +// +// callq *__imp_bar(%rip) ; or, for data: movq __imp_g(%rip), %rax +// +// where __imp_X is an undefined external. This pass supplies the missing IAT +// entry by defining __imp_X over an 8-byte pointer slot that holds X's address: +// +// __imp_bar: +// .quad bar ; X is resolved as an ordinary external +// +// X is left external, so its address is provided by whatever resolves the +// JITDylib's externals (an import library, a DynamicLibrarySearchGenerator, +// AutoImportGenerator, ...). If X is unresolvable the link fails, exactly as a +// static link against the corresponding import library would. +// +// This is the COFF analog of the ELF/Mach-O GOT builder, but deliberately NOT +// written as a TableManager/visitEdge pass like x86_64::GOTTableManager. ELF's +// GOT references are *nameless* edge kinds, so that builder has to create an +// anonymous entry and redirect every edge to it (and, for our case, would then +// have to delete the now-orphaned __imp_X external so it isn't looked up). +// COFF instead references a *named* __imp_X symbol, so the simpler and more +// natural thing is to define that symbol over the slot: edges to __imp_X then +// resolve to it with no edge rewriting and no orphan cleanup, call and +// data-access references are handled identically, and sharing is automatic +// because there is exactly one __imp_X symbol per import. +// +// Direct (non-dllimport) references such as `callq foo` are intentionally not +// handled here: those are either kept in range by the slab allocator or thunked +// by the opt-in AutoImportGenerator -- both outside this pass. +Error synthesizeIATEntries_COFF_x86_64(LinkGraph &G) { + static constexpr StringRef ImpPrefix = "__imp_"; + + // Collect the external __imp_ symbols up front: we mutate the symbol lists + // below (makeDefined / addExternalSymbol). + SmallVector Imps; + for (auto *Sym : G.external_symbols()) + if (Sym->hasName() && (*Sym->getName()).starts_with(ImpPrefix)) + Imps.push_back(Sym); + if (Imps.empty()) + return Error::success(); + + auto FindByName = [&](const orc::SymbolStringPtr &Name) -> Symbol * { + if (auto *Sym = G.findExternalSymbolByName(Name)) + return Sym; + if (auto *Sym = G.findDefinedSymbolByName(Name)) + return Sym; + return nullptr; + }; + + Section &IATSec = G.createSection("$__IAT", orc::MemProt::Read); + + for (auto *Imp : Imps) { + orc::SymbolStringPtr Base = + G.intern((*Imp->getName()).drop_front(ImpPrefix.size())); + + // Find the real target X, or add it as an external to be resolved normally. + Symbol *Target = FindByName(std::move(Base)); + if (!Target) + Target = &G.addExternalSymbol(std::move(Base), 0, + /*IsWeaklyReferenced=*/false); + + // 8-byte slot holding &X, with __imp_X defined over it. + Symbol &Slot = x86_64::createAnonymousPointer(G, IATSec, Target); + G.makeDefined(*Imp, Slot.getBlock(), 0, G.getPointerSize(), Linkage::Strong, + Scope::Local, /*IsLive=*/true); + } + + return Error::success(); +} + } // namespace namespace llvm { @@ -374,6 +447,11 @@ void link_COFF_x86_64(std::unique_ptr G, } else Config.PrePrunePasses.push_back(markAllSymbolsLive); + // Synthesize __imp_X IAT entries for dllimport references, like the GOT/PLT + // builders for ELF/Mach-O. Runs in PostPrune (before external-symbol + // lookup) so the X targets it introduces are resolved normally. + Config.PostPrunePasses.push_back(synthesizeIATEntries_COFF_x86_64); + // Add an in place GOT/PLT stub build pass for external calls. Config.PostPrunePasses.push_back(buildTables_COFF_x86_64); diff --git a/llvm/test/ExecutionEngine/JITLink/x86-64/COFF_dllimport_iat.s b/llvm/test/ExecutionEngine/JITLink/x86-64/COFF_dllimport_iat.s new file mode 100644 index 00000000000000..754fa956758a5a --- /dev/null +++ b/llvm/test/ExecutionEngine/JITLink/x86-64/COFF_dllimport_iat.s @@ -0,0 +1,55 @@ +# Verify the COFF __imp_ IAT synthesis pass: for a dllimport reference to an +# undefined __imp_X symbol, JITLink should define __imp_X over an 8-byte pointer +# slot that holds the address of X (resolved as an ordinary external). Both the +# call form (callq *__imp_X) and the data-access form (movq __imp_X) resolve +# indirectly through that slot. +# +# X (foo/bar) is supplied as an absolute symbol, so no real library is needed -- +# this exercises the pass itself, not any resolution mechanism. +# +# RUN: llvm-mc -filetype=obj -triple=x86_64-windows-msvc %s -o %t.o +# RUN: llvm-jitlink -noexec \ +# RUN: -slab-allocate 100Kb -slab-address 0xfff00000 -slab-page-size 4096 \ +# RUN: -abs foo=0x7ff700000000 \ +# RUN: -abs bar=0x7ff700001000 \ +# RUN: -check %s %t.o + + .text + + .def main; + .scl 2; + .type 32; + .endef + .globl main + .p2align 4, 0x90 +main: + retq + +# The synthesized __imp_bar slot holds bar's address... +# jitlink-check: *{8}(__imp_bar) = bar +# ... and the dllimport call reads through that slot (RIP-relative displacement +# of the indirect call's memory operand, MCInst operand 3). +# jitlink-check: decode_operand(test_call, 3) = __imp_bar - next_pc(test_call) + .def test_call; + .scl 2; + .type 32; + .endef + .globl test_call + .p2align 4, 0x90 +test_call: + callq *__imp_bar(%rip) + retq + +# Same for a data access: the __imp_foo slot holds foo's address, and the load +# reads through it (displacement is MCInst operand 4 for `movq mem, reg`). +# jitlink-check: *{8}(__imp_foo) = foo +# jitlink-check: decode_operand(test_load, 4) = __imp_foo - next_pc(test_load) + .def test_load; + .scl 2; + .type 32; + .endef + .globl test_load + .p2align 4, 0x90 +test_load: + movq __imp_foo(%rip), %rax + retq From 0022615e09c67f061947274c9f08903679672305 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 8 Aug 2026 08:57:34 +0100 Subject: [PATCH 18/25] [ORC] Remove DLLImportDefinitionGenerator filter, superseded by JITLink IAT synthesis --- llvm/lib/ExecutionEngine/Orc/LLJIT.cpp | 36 -------------------------- 1 file changed, 36 deletions(-) diff --git a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp index 296b668da2c487..138d1c06913d61 100644 --- a/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp +++ b/llvm/lib/ExecutionEngine/Orc/LLJIT.cpp @@ -597,36 +597,6 @@ 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 { @@ -1312,12 +1282,6 @@ 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; From ba9c7117861f4612f5c71998280f80d54f51fd99 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 8 Aug 2026 09:31:17 +0100 Subject: [PATCH 19/25] [JITLink][COFF] Declare buildTables and synthesizeIATEntries in COFF_x86_64.h --- .../include/llvm/ExecutionEngine/JITLink/COFF_x86_64.h | 7 +++++++ llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/llvm/include/llvm/ExecutionEngine/JITLink/COFF_x86_64.h b/llvm/include/llvm/ExecutionEngine/JITLink/COFF_x86_64.h index 2072ae9dfdbe70..d40f2131d58964 100644 --- a/llvm/include/llvm/ExecutionEngine/JITLink/COFF_x86_64.h +++ b/llvm/include/llvm/ExecutionEngine/JITLink/COFF_x86_64.h @@ -26,6 +26,13 @@ namespace jitlink { Expected> createLinkGraphFromCOFFObject_x86_64( MemoryBufferRef ObjectBuffer, std::shared_ptr SSP); +/// 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); + +/// Synthesize IAT entries for undefined __imp_X externals in G. Always succeeds. +Error synthesizeIATEntries_COFF_x86_64(LinkGraph &G); + /// jit-link the given object buffer, which must be a COFF x86-64 object file. void link_COFF_x86_64(std::unique_ptr G, std::unique_ptr Ctx); diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp index 043eeddd378930..494937eb524b70 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp @@ -307,6 +307,11 @@ class COFFPointer32NBStubsManager : public llvm::jitlink::TableManager Date: Fri, 14 Aug 2026 13:00:27 +0100 Subject: [PATCH 20/25] Revert MSYS2 distro patches (out of scope for upstream) --- 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 --- 5 files changed, 3 insertions(+), 11 deletions(-) diff --git a/clang/lib/Driver/ToolChains/MinGW.cpp b/clang/lib/Driver/ToolChains/MinGW.cpp index 99df9b533ced49..7f8aec6482b5fe 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_no_pthread)) + if (Args.hasArg(options::OPT_pthread)) CmdArgs.push_back("-lpthread"); if (Sanitize.needsAsanRt()) { diff --git a/lld/MinGW/Options.td b/lld/MinGW/Options.td index 5f57a943e36a45..e6626b582bcd8d 100644 --- a/lld/MinGW/Options.td +++ b/lld/MinGW/Options.td @@ -249,8 +249,6 @@ 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 19d88bf00c53d1..dbfaadb5de2b88 100644 --- a/llvm/cmake/modules/Findzstd.cmake +++ b/llvm/cmake/modules/Findzstd.cmake @@ -16,8 +16,6 @@ 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 @@ -29,7 +27,6 @@ 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$") @@ -41,7 +38,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 8dbd06ff4a7c9e..cbecd85df5e43c 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 ) + elseif( MINGW AND NOT MSYS ) # 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 2717ca34d4547c..e8d505f218b697 100644 --- a/llvm/lib/Support/CMakeLists.txt +++ b/llvm/lib/Support/CMakeLists.txt @@ -42,9 +42,6 @@ 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() From 8b79c2b1b3d3fc8e21a79e1e4f312834512df721 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 22 Aug 2026 09:42:16 +0100 Subject: [PATCH 21/25] [JITLink][COFF] Move __ImageBase resolution to PostAllocationPass --- llvm/lib/ExecutionEngine/JITLink/COFF.cpp | 4 ++-- llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h | 2 +- llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF.cpp index eb34b1a5149acc..c9892dd9d84f59 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF.cpp @@ -137,10 +137,10 @@ Symbol *GetImageBaseSymbol::operator()(LinkGraph &G) { // __ImageBase is only ever external (generic platform) or absolute (COFFPlatform). auto IBN = G.intern(ImageBaseName); - ImageBase = G.findExternalSymbolByName(IBN); + ImageBase = G.findAbsoluteSymbolByName(IBN); if (*ImageBase) return *ImageBase; - ImageBase = G.findAbsoluteSymbolByName(IBN); + ImageBase = G.findExternalSymbolByName(IBN); if (*ImageBase) return *ImageBase; diff --git a/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h b/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h index a8ad781da15581..f7e6c671eb9b3b 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h +++ b/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.h @@ -78,7 +78,7 @@ class COFFLinkGraphBuilder { } Symbol &addImageBaseSymbol(StringRef Name = "__ImageBase") { - auto &ImageBase = G->addExternalSymbol(G->intern(Name), 0, true); + auto &ImageBase = G->addAbsoluteSymbol(G->intern(Name), orc::ExecutorAddr(0), 0, Linkage::Strong, Scope::Local, true); ImageBase.setLive(true); return ImageBase; } diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp index 494937eb524b70..25857c39a46b94 100644 --- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp +++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp @@ -455,8 +455,8 @@ void link_COFF_x86_64(std::unique_ptr G, // 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()); + // Resolve __ImageBase to the lowest allocated section address. + Config.PostAllocationPasses.push_back(COFFImageBaseResolution_x86_64()); // Add COFF edge lowering passes. Config.PreFixupPasses.push_back(COFFLinkGraphLowering_x86_64()); From 4f138de7c5d58429950486d5b533242ecd2b3d00 Mon Sep 17 00:00:00 2001 From: ikappaki Date: Sat, 22 Aug 2026 09:52:14 +0100 Subject: [PATCH 22/25] [ORC] Add release note for JITLink default on COFF x86_64 --- llvm/docs/ReleaseNotes.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md index b68495c6f8584b..76c7c16ba8e6fb 100644 --- a/llvm/docs/ReleaseNotes.md +++ b/llvm/docs/ReleaseNotes.md @@ -42,7 +42,8 @@ point (e.g. maybe you would like to give an example of the functionality, or simply have a lot to talk about), see the comment below for adding a new subsection. --> -* ... +* LLJIT now uses JITLink by default for x86_64 COFF targets (Windows). + This enables SEH exception handling in JIT'd code on Windows.