aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AGENTS/ARCH.md77
-rw-r--r--CMakeLists.txt4
-rw-r--r--tools/driver/EmbeddedRunner.cc335
-rw-r--r--tools/driver/MinGWCOFF.h138
-rw-r--r--tools/driver/MinGWJIT.cc447
-rw-r--r--tools/driver/MinGWJIT.h25
6 files changed, 510 insertions, 516 deletions
diff --git a/AGENTS/ARCH.md b/AGENTS/ARCH.md
index 73e28bc..b146996 100644
--- a/AGENTS/ARCH.md
+++ b/AGENTS/ARCH.md
@@ -127,52 +127,37 @@ Driver behavior (`tools/driver/main.cc`):
source compilation. Without `-r`, no external tools are checked; clang/clang++
are never invoked or checked by this mode. Process-launch/crash failures return 1.
`-r` alone uses temporary IR removed on return, including execution failure.
-- In EMBEDDED mode, `-r` uses LLJIT with JITLink in-process (`tools/driver/EmbeddedRunner.cc`).
- The JIT owns a copy of CodeGen's module made with `cloneExternalModuleToContext`
- into an independent `ThreadSafeContext`; CodeGen retains its module and context.
- The running copy uses the JIT target triple and DataLayout. The JIT target uses
- Large code model with PIC. Static C++ exception
- ABI symbols are defined with ORC `absoluteSymbols` using the JIT mangling
- (MinGW keeps its `snprintf`/`strtoll` aliases and a no-op `__main`; MinGW
- x86_64 additionally binds the toolchain-provided `___chkstk_ms`); ordinary
- C library symbols resolve through the ORC process-symbol mechanism. Linux uses
- the generic LLVM IR platform with EH-frame registration. macOS uses LLJIT's
- unwind registration, selecting compact unwind or EH-frame per host capability.
- Windows x86_64 keeps the GNU C++ runtime and `__gxx_personality_seh0` with a
- small in-process COFF adapter: `MinGWCOFF.h` normalizes symbol-less MinGW
- `.pdata$NAME`/`.xdata$NAME` COMDATs to associative COMDATs of `.text$NAME`
- in private object-buffer copies before graph construction, including lazy
- runtime archive members. It also makes MinGW's weak `__cxa_pure_virtual`
- reference with an absolute-zero fallback an ordinary external, resolved to
- the host ABI function. The fallback is made local, and runtime archive
- interfaces are computed from the normalized object. The runner binds the host's
- initialized stack guard, stack-check failure handler and `write` alias (`_write`) on MinGW.
- It also binds the host's `_lock_file`/`_unlock_file` compatibility functions,
- keeping MinGW stdio locks compatible with the host's `FILE` objects.
- Microsoft msvcrt.dll lacks these exports, so process-wide lookup can select
- another CRT's incompatible functions. Wine's msvcrt.dll exports them and can
- mask this defect; Wine success does not replace real Windows verification.
- Per-graph `__ImageBase` is shared by relocations
- and SEH registration, with default backend liveness for `.pdata` and
- associative code edges retaining the suffixed unwind sections. JITLink uses
- pointer-jump stubs for external direct CALL/JMP PCRel32 references in user
- objects and `.xdata`/`.xdata$NAME` personality references (shared per target;
- data references are preserved), and
- `RtlAddFunctionTable`/`RtlDeleteFunctionTable` via JITLink allocation actions
- for one sorted, allocation-owned table combining `.pdata` and `.pdata$NAME`
- per graph to avoid overlapping unwinder lookup ranges, covering teardown and
- link-failure cleanup, with table validation in the registration callback;
- registration failure blocks entry.
- The JIT builds its `ObjectLinkingLayer` through one file-local factory (COFF
- flags stay format-conditional, SEH plugin installed before LLJIT ownership)
- so Windows COFF does not fall back
- to RuntimeDyld. Initialization runs through the platform support before lookup
- of `main`; deinitialization runs while code is still mapped so unwind
- deregistration precedes memory release. Execution failures return 1 without
- `compilation terminated.`. The host target comes from
- `JITTargetMachineBuilder::detectHost()`.
+- In EMBEDDED mode, `-r` uses LLJIT with JITLink in-process
+ (`tools/driver/EmbeddedRunner.cc`). It clones CodeGen's module into an independent
+ `ThreadSafeContext`, sets the JIT target triple/DataLayout, and uses the Large
+ code model with PIC. CodeGen retains its original module and context.
+ Static exception ABI symbols resolve to the host runtime through ORC
+ `absoluteSymbols`; ordinary C symbols use process lookup. Linux uses generic
+ EH-frame registration; macOS uses LLVM's compact-unwind or EH-frame support.
+ Windows x86_64 compatibility lives in `tools/driver/MinGWJIT.cc`, exposed through
+ two internal entry points for object-layer creation and archive symbol interfaces.
+ Symbol-less MinGW `.pdata$NAME`/`.xdata$NAME` COMDATs become associative COMDATs
+ of `.text$NAME` in private object copies. Weak absolute-zero `__cxa_pure_virtual`
+ references resolve to the host ABI, with their fallback kept local. Archive
+ interfaces and object emission use the same normalization.
+ LLVM's `.pdata` liveness and associative unwind sections are retained. A graph-local
+ `__ImageBase` is shared by relocations and SEH registration; nearby pointer-jump
+ stubs handle external direct CALL/JMP and unwind personality references, preserving
+ data references. Each graph combines `.pdata` and `.pdata$NAME` into one sorted,
+ allocation-owned table, validated before `RtlAddFunctionTable`. Allocation actions
+ call `RtlDeleteFunctionTable` on cleanup before memory release.
+ MinGW runtime bindings remain in EmbeddedRunner: formatting aliases, no-op `__main`,
+ host stack guard/failure handler, `_write`, `__acrt_iob_func`, stdio locks and the
+ x86_64 `___chkstk_ms` probe. Prefer `msvcrt.dll` before process-wide C lookup so
+ JIT code uses compatible `FILE` objects. MinGW supplies `_lock_file`/`_unlock_file`
+ because Microsoft's msvcrt.dll does not export them; Wine can mask this difference.
+ See [Microsoft's x64 unwind requirements](https://learn.microsoft.com/en-us/cpp/build/exception-handling-x64),
+ [LLVM 23's COFF graph builder](https://github.com/llvm/llvm-project/blob/release/23.x/llvm/lib/ExecutionEngine/JITLink/COFFLinkGraphBuilder.cpp)
+ and [MinGW's stdio lock implementation](https://github.com/mingw-w64/mingw-w64/blob/master/mingw-w64-crt/stdio/mingw_lock.c).
+ Initialize the JIT before looking up `main` and deinitialize while its code is
+ still mapped. Execution failures return 1 without `compilation terminated.`.
No external runner or PATH lookup is used; `-r` alone runs in memory.
- Linux embedded execution is verified; Windows/macOS await CI verification.
+ Platform-specific changes require native CI verification.
- In NONE mode, `-r` returns 1 before compiling or writing output; native and IR
compilation remain available. Only EMBEDDED uses the in-process execution engine.
- Both execution modes return the program's exit status and inherit standard
@@ -345,7 +330,7 @@ do not modify the real release for local verification.
| `lib/CodeGen/*Handler.cc`, `FunctionGen.cc`, `RuntimeCheck.cc`, `TypeSystem.cc` | Helpers owned by CodeGen (including exception emission) |
| `CMakeLists.txt` | 3 static libs (CPSLexer, CPSParser, CPSCodeGen) + `cpsc`. **New .cc files must be added to the source lists here** |
| `tools/driver/NativeCompiler.cc`, `cmake/NativeCompiler.cmake` | Host object generation, embedded Clang/LLD linking and compressed link resources |
-| `tools/driver/EmbeddedRunner.cc`, `cmake/EmbeddedLLVM.cmake` | EMBEDDED-only LLJIT/JITLink runner; static support-library selection for all modes |
+| `tools/driver/EmbeddedRunner.cc`, `tools/driver/MinGWJIT.cc`, `cmake/EmbeddedLLVM.cmake` | EMBEDDED-only LLJIT/JITLink runner and MinGW COFF/SEH support; static support-library selection for all modes |
| `cmake/LocalDependencies.cmake`, `cmake/*Packages.cmake` | Local package extraction and platform relocation |
| `cmake/DependencyPaths.cmake`, `cmake/FindDependency.cmake` | Shared local paths and local-first/system-fallback lookup |
| `cmake/BuildTools.cmake`, `cmake/LLVMDependencies.cmake` | Compiler/build tool selection and LLVM dependency discovery |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 86fef58..5d9f84f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -97,7 +97,9 @@ include(cmake/EmbeddedLLVM.cmake)
add_executable(cpsc tools/driver/main.cc)
target_compile_definitions(cpsc PRIVATE CPSC_RUN_${CPSC_RUN_MODE})
if(CPSC_RUN_MODE STREQUAL "EMBEDDED")
- target_sources(cpsc PRIVATE tools/driver/EmbeddedRunner.cc)
+ target_sources(cpsc PRIVATE
+ tools/driver/EmbeddedRunner.cc
+ tools/driver/MinGWJIT.cc)
endif()
if(MINGW)
target_link_options(cpsc PRIVATE -static)
diff --git a/tools/driver/EmbeddedRunner.cc b/tools/driver/EmbeddedRunner.cc
index 49375e1..587d664 100644
--- a/tools/driver/EmbeddedRunner.cc
+++ b/tools/driver/EmbeddedRunner.cc
@@ -1,11 +1,11 @@
#include "EmbeddedRunner.h"
+#include "MinGWJIT.h"
#include "NativeCompiler.h"
#include "llvm/Object/ObjectFile.h"
#include "cps/CodeGen.h"
#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
#include "llvm/ExecutionEngine/Orc/LLJIT.h"
-#include "llvm/ExecutionEngine/Orc/ObjectFileInterface.h"
#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
#include "llvm/ExecutionEngine/Orc/ThreadSafeModule.h"
#include "llvm/Support/Error.h"
@@ -17,323 +17,12 @@
#include <cxxabi.h>
#include <typeinfo>
#include <unwind.h>
-#if defined(_WIN32) && defined(__x86_64__)
-#ifndef NOMINMAX
-#define NOMINMAX
-#endif
-#include <algorithm>
-#include <cstring>
-#include <limits>
-#include "llvm/ExecutionEngine/JITLink/JITLink.h"
-#include "llvm/ExecutionEngine/JITLink/x86_64.h"
-#include "llvm/ExecutionEngine/Orc/LinkGraphLinkingLayer.h"
-#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h"
-#include "llvm/Support/Endian.h"
-#include "MinGWCOFF.h"
-// Windows IMAGE_* macros conflict with LLVM COFF enums and inline helpers.
-// Parse all LLVM COFF declarations and helpers before introducing those macros.
-#include <windows.h>
-#endif
#ifdef __MINGW32__
#include <io.h>
#endif
namespace {
-#if defined(_WIN32) && defined(__x86_64__)
-// In-process COFF/SEH support for the MinGW x86_64 JIT.
-//
-// The full upstream COFFPlatform starts the VC runtime loading machinery,
-// which does not apply to the current MinGW build. This plugin provides only
-// the linking and registration pieces needed for in-process execution:
-// a per-graph __ImageBase shared by relocations and SEH registration,
-// JITLink stubs for external direct branches and GNU personalities in .xdata,
-// and RtlAddFunctionTable/RtlDeleteFunctionTable via JITLink allocation
-// actions. Exception propagation still uses the existing C++ ABI, LLVM
-// exception instructions and platform unwinder.
-
-static constexpr llvm::StringLiteral COFFSEHTableSection = "$__cpsc_seh";
-
-static llvm::Error addCOFFJumpStubs(llvm::jitlink::LinkGraph &G) {
- using namespace llvm;
- using namespace llvm::jitlink;
- using namespace llvm::jitlink::x86_64;
- GOTTableManager GOT(G);
- PLTTableManager PLT(G, GOT);
- // Creating a stub may add sections/blocks. Visit only the original blocks.
- std::vector<Block *> Blocks(G.blocks().begin(), G.blocks().end());
- for (auto *B : Blocks) {
- bool Executable = (B->getSection().getMemProt() & orc::MemProt::Exec) !=
- orc::MemProt::None;
- for (auto &E : B->edges()) {
- Symbol &Target = E.getTarget();
- if (!Target.isExternal())
- continue;
- bool Personality = false;
- if (isCOFFUnwindSection(B->getSection().getName(), ".xdata") &&
- Target.hasName()) {
- StringRef Name = *Target.getName();
- Personality = Name == "__gxx_personality_seh0" ||
- Name == "__gxx_personality_sj0" ||
- Name == "__gxx_personality_v0";
- }
- // COFF's private PCRel32 kind is lowered only in PreFixup.
- // Use its name rather than confusing it with x86_64::PCRel32.
- // Only E8/E9 direct branches with a plain symbol target need a
- // jump stub; RIP-relative data and indirect calls must stay intact.
- bool DirectBranch = false;
- if (Executable && !B->isZeroFill() && E.getAddend() == 0 &&
- StringRef(G.getEdgeKindName(E.getKind())) == "PCRel32") {
- auto Content = B->getContent();
- size_t Offset = E.getOffset();
- if (Offset > 0 && Offset <= Content.size() &&
- Content.size() - Offset >= 4) {
- unsigned char Opcode = Content[Offset - 1];
- DirectBranch = Opcode == 0xe8 || Opcode == 0xe9;
- }
- }
- if (Personality || DirectBranch)
- E.setTarget(PLT.getEntryForTarget(G, Target));
- }
- }
- return Error::success();
-}
-
-static llvm::Error registerSEHTable(llvm::orc::ExecutorAddrRange TableRange,
- llvm::orc::ExecutorAddr ImageBase) {
- using llvm::make_error;
- using llvm::StringError;
- using llvm::inconvertibleErrorCode;
- size_t Size = TableRange.size();
- if (Size == 0 || ImageBase.isNull())
- return make_error<StringError>("invalid embedded SEH function table",
- inconvertibleErrorCode());
- if (Size % sizeof(RUNTIME_FUNCTION))
- return make_error<StringError>("invalid embedded SEH function table",
- inconvertibleErrorCode());
- uint64_t Count64 = Size / sizeof(RUNTIME_FUNCTION);
- if (Count64 == 0 || Count64 > static_cast<uint64_t>(std::numeric_limits<DWORD>::max()))
- return make_error<StringError>("invalid embedded SEH function table",
- inconvertibleErrorCode());
- auto *Table = TableRange.Start.toPtr<const RUNTIME_FUNCTION *>();
- DWORD PrevBegin = 0;
- for (uint64_t I = 0; I < Count64; ++I) {
- DWORD Begin = llvm::support::endian::read32le(
- reinterpret_cast<const char *>(Table + I) + 0);
- DWORD End = llvm::support::endian::read32le(
- reinterpret_cast<const char *>(Table + I) + 4);
- if (!(Begin < End))
- return make_error<StringError>("invalid embedded SEH function table",
- inconvertibleErrorCode());
- if (I > 0 && !(PrevBegin < Begin))
- return make_error<StringError>("invalid embedded SEH function table",
- inconvertibleErrorCode());
- PrevBegin = Begin;
- }
- auto *MutableTable = const_cast<PRUNTIME_FUNCTION>(Table);
- DWORD Count = static_cast<DWORD>(Count64);
- DWORD64 Base = static_cast<DWORD64>(ImageBase.getValue());
- if (!RtlAddFunctionTable(MutableTable, Count, Base))
- return make_error<StringError>("cannot register embedded SEH function table",
- inconvertibleErrorCode());
- return llvm::Error::success();
-}
-
-static llvm::Error deregisterSEHTable(llvm::orc::ExecutorAddrRange TableRange,
- llvm::orc::ExecutorAddr ImageBase) {
- (void)ImageBase;
- auto *Table = TableRange.Start.toPtr<PRUNTIME_FUNCTION>();
- if (TableRange.size() == 0 || !Table)
- return llvm::Error::success();
- RtlDeleteFunctionTable(Table);
- return llvm::Error::success();
-}
-
-extern "C" llvm::orc::shared::CWrapperFunctionBuffer
-cps_register_seh_table_alloc_action(const char *ArgData, size_t ArgSize) {
- using namespace llvm::orc::shared;
- return WrapperFunction<SPSError(SPSExecutorAddrRange, SPSExecutorAddr)>::handle(
- ArgData, ArgSize, registerSEHTable)
- .release();
-}
-
-extern "C" llvm::orc::shared::CWrapperFunctionBuffer
-cps_deregister_seh_table_alloc_action(const char *ArgData, size_t ArgSize) {
- using namespace llvm::orc::shared;
- return WrapperFunction<SPSError(SPSExecutorAddrRange, SPSExecutorAddr)>::handle(
- ArgData, ArgSize, deregisterSEHTable)
- .release();
-}
-
-class MinGWSEHRegistrationPlugin final
- : public llvm::orc::LinkGraphLinkingLayer::Plugin {
-public:
- void modifyPassConfig(llvm::orc::MaterializationResponsibility &MR,
- llvm::jitlink::LinkGraph &G,
- llvm::jitlink::PassConfiguration &Config) override {
- using namespace llvm;
- using namespace llvm::orc;
- using namespace llvm::jitlink;
- const Triple &TT = G.getTargetTriple();
- if (!TT.isOSBinFormatCOFF() || TT.getArch() != Triple::x86_64)
- return;
-
- // SEH table liveness relies on the COFF backend's default
- // SEHFrameKeepAlivePass(".pdata"), which keeps .pdata alive from its
- // referenced function/unwind blocks. Normalized .pdata$/.xdata$
- // sections are kept alive by their associative COMDAT code edges.
- // COFF unwind tables address the personality by 32-bit RVA, so it
- // must live near JIT code. Retarget .xdata personality references
- // to this graph's anonymous pointer jump stubs; the stub pointer
- // itself uses a 64-bit relocation to reach the far host runtime.
- // External direct calls/jumps from user objects also need nearby
- // stubs: their PCRel32 displacement cannot reach a distant CRT DLL.
- // The same target reuses the same stub, including personality uses.
- Config.PostPrunePasses.push_back(addCOFFJumpStubs);
-
- // Tables sharing an image base have overlapping lookup ranges in the
- // Windows unwinder. Reserve one table for all retained .pdata sections
- // in this graph, then fill and sort its entries after relocations.
- Config.PostPrunePasses.push_back([](LinkGraph &G) -> Error {
- size_t Size = 0;
- for (auto &Sec : G.sections()) {
- if (!isCOFFUnwindSection(Sec.getName(), ".pdata"))
- continue;
- for (auto *B : Sec.blocks()) {
- if (B->isZeroFill() || B->getSize() % sizeof(RUNTIME_FUNCTION))
- return make_error<JITLinkError>(
- "invalid embedded SEH function table");
- Size += B->getSize();
- }
- }
- if (Size) {
- auto &Sec = G.createSection(COFFSEHTableSection, MemProt::Read);
- G.createMutableContentBlock(Sec, Size, ExecutorAddr(),
- alignof(RUNTIME_FUNCTION), 0);
- }
- return Error::success();
- });
-
- // COFF unwind tables use 32-bit image-relative addresses, so the
- // image base must sit at or below the lowest JIT address. Alias the
- // external __ImageBase to the lowest defined block once addresses
- // are final; a trailing zero block would push RVAs negative.
- Config.PostAllocationPasses.push_back([](LinkGraph &G) -> Error {
- // A graph-local alias never collides at materialization time,
- // no matter how many graphs define the same base name.
- if (G.findDefinedSymbolByName(G.intern("__ImageBase")))
- return Error::success();
- auto *ImageBaseExt =
- G.findExternalSymbolByName(G.intern("__ImageBase"));
- if (!ImageBaseExt)
- return Error::success();
- jitlink::Block *Low = nullptr;
- for (auto &Sec : G.sections()) {
- for (auto *B : Sec.blocks()) {
- if (!Low || B->getAddress() < Low->getAddress())
- Low = B;
- }
- }
- if (!Low)
- return make_error<JITLinkError>(
- "missing embedded __ImageBase content");
- G.makeDefined(*ImageBaseExt, *Low, 0, 8, Linkage::Strong,
- Scope::Local, true);
- return Error::success();
- });
-
- // Copy fixed-up entries into one sorted, allocation-owned table.
- // Register it once per graph, with deregistration on link failure or
- // teardown before the table and its code are released.
- Config.PostFixupPasses.push_back(
- [](LinkGraph &G) -> Error {
- using namespace llvm::orc::shared;
- auto *TableSec = G.findSectionByName(COFFSEHTableSection);
- if (!TableSec)
- return Error::success();
- auto *Table = *TableSec->blocks().begin();
- std::vector<RUNTIME_FUNCTION> Entries;
- Entries.reserve(Table->getSize() / sizeof(RUNTIME_FUNCTION));
- for (auto &Sec : G.sections()) {
- if (!isCOFFUnwindSection(Sec.getName(), ".pdata"))
- continue;
- for (auto *B : Sec.blocks()) {
- size_t Start = Entries.size();
- Entries.resize(Start + B->getSize() / sizeof(RUNTIME_FUNCTION));
- if (B->getSize())
- std::memcpy(Entries.data() + Start, B->getContent().data(),
- B->getSize());
- }
- }
- std::sort(Entries.begin(), Entries.end(),
- [](const RUNTIME_FUNCTION &L, const RUNTIME_FUNCTION &R) {
- return L.BeginAddress < R.BeginAddress;
- });
- std::memcpy(Table->getAlreadyMutableContent().data(), Entries.data(),
- Table->getSize());
- ExecutorAddrRange TableRange(Table->getAddress(), Table->getSize());
- auto *ImageBaseSym =
- G.findDefinedSymbolByName(G.intern("__ImageBase"));
- if (!ImageBaseSym)
- return make_error<JITLinkError>(
- "missing embedded __ImageBase for SEH registration");
- ExecutorAddr ImageBase = ImageBaseSym->getAddress();
- if (ImageBase.isNull())
- return make_error<JITLinkError>("invalid embedded SEH image base");
- auto RegisterAction =
- WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddrRange,
- SPSExecutorAddr>>(
- ExecutorAddr::fromPtr(&cps_register_seh_table_alloc_action),
- TableRange, ImageBase);
- if (!RegisterAction)
- return RegisterAction.takeError();
- auto DeregisterAction =
- WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddrRange,
- SPSExecutorAddr>>(
- ExecutorAddr::fromPtr(&cps_deregister_seh_table_alloc_action),
- TableRange, ImageBase);
- if (!DeregisterAction)
- return DeregisterAction.takeError();
- G.allocActions().push_back(
- {std::move(*RegisterAction), std::move(*DeregisterAction)});
- return Error::success();
- });
- (void)MR;
- }
-
- llvm::Error notifyFailed(llvm::orc::MaterializationResponsibility &) override {
- return llvm::Error::success();
- }
-
- llvm::Error notifyRemovingResources(llvm::orc::JITDylib &,
- llvm::orc::ResourceKey) override {
- return llvm::Error::success();
- }
-
- void notifyTransferringResources(llvm::orc::JITDylib &,
- llvm::orc::ResourceKey,
- llvm::orc::ResourceKey) override {}
-};
-
-// Both user objects and lazily extracted runtime archive members pass through
-// this layer, before JITLink builds their graphs or runs plugin passes.
-class MinGWObjectLinkingLayer final : public llvm::orc::ObjectLinkingLayer {
-public:
- using ObjectLinkingLayer::ObjectLinkingLayer;
-
- void emit(std::unique_ptr<llvm::orc::MaterializationResponsibility> R,
- std::unique_ptr<llvm::MemoryBuffer> O) override {
- auto Normalized = normalizeMinGWObject(std::move(O));
- if (!Normalized) {
- getExecutionSession().reportError(Normalized.takeError());
- R->failMaterialization();
- return;
- }
- ObjectLinkingLayer::emit(std::move(R), std::move(*Normalized));
- }
-};
-#endif
-
// Embedded JITLink object layer. Created before LLJIT takes ownership so
// runEmbedded keeps only the execution flow. COFF flags stay conditional on
// the object format; other formats use LLVM defaults.
@@ -341,21 +30,15 @@ static std::unique_ptr<llvm::orc::ObjectLinkingLayer>
createEmbeddedObjectLayer(llvm::orc::ExecutionSession &ES,
llvm::jitlink::JITLinkMemoryManager &MM,
const llvm::Triple &TT) {
- std::unique_ptr<llvm::orc::ObjectLinkingLayer> Layer;
#if defined(_WIN32) && defined(__x86_64__)
if (TT.isOSBinFormatCOFF() && TT.getArch() == llvm::Triple::x86_64)
- Layer = std::make_unique<MinGWObjectLinkingLayer>(ES, MM);
- else
+ return createMinGWObjectLinkingLayer(ES, MM);
#endif
- Layer = std::make_unique<llvm::orc::ObjectLinkingLayer>(ES, MM);
+ auto Layer = std::make_unique<llvm::orc::ObjectLinkingLayer>(ES, MM);
if (TT.isOSBinFormatCOFF()) {
Layer->setOverrideObjectFlagsWithResponsibilityFlags(true);
Layer->setAutoClaimResponsibilityForObjectSymbols(true);
}
-#if defined(_WIN32) && defined(__x86_64__)
- if (TT.isOSBinFormatCOFF() && TT.getArch() == llvm::Triple::x86_64)
- Layer->addPlugin(std::make_shared<MinGWSEHRegistrationPlugin>());
-#endif
return Layer;
}
@@ -459,8 +142,6 @@ static llvm::Error defineRuntimeSymbols(llvm::orc::LLJIT &J) {
// msvcrt DLL only exports __iob_func, so bind the host's shim directly.
Symbols[Intern("__acrt_iob_func")] =
ExecutorSymbolDef::fromPtr(&__acrt_iob_func);
-#endif
-#ifdef _WIN32
// The JIT-generated main references __main on MinGW; provide a no-op so
// the host CRT is not initialized a second time.
Symbols[Intern("__main")] =
@@ -481,15 +162,7 @@ static llvm::Error loadCxxRuntime(llvm::orc::LLJIT &J) {
for (auto &Archive : Archives) {
llvm::orc::StaticLibraryDefinitionGenerator::GetObjectFileInterface GetInterface;
#if defined(_WIN32) && defined(__x86_64__)
- // Match the normalized object that MinGWObjectLinkingLayer::emit links.
- GetInterface = [](llvm::orc::ExecutionSession &ES, llvm::MemoryBufferRef Ref)
- -> llvm::Expected<llvm::orc::MaterializationUnit::Interface> {
- auto Normalized = normalizeMinGWObject(
- llvm::MemoryBuffer::getMemBuffer(Ref, false));
- if (!Normalized)
- return Normalized.takeError();
- return llvm::orc::getObjectFileInterface(ES, (*Normalized)->getMemBufferRef());
- };
+ GetInterface = getMinGWObjectFileInterface;
#endif
auto Generator = llvm::orc::StaticLibraryDefinitionGenerator::Create(
J.getObjLinkingLayer(), std::move(Archive),
diff --git a/tools/driver/MinGWCOFF.h b/tools/driver/MinGWCOFF.h
deleted file mode 100644
index 555dcc8..0000000
--- a/tools/driver/MinGWCOFF.h
+++ /dev/null
@@ -1,138 +0,0 @@
-#ifndef CPSC_DRIVER_MINGWCOFF_H
-#define CPSC_DRIVER_MINGWCOFF_H
-
-#include "llvm/ADT/DenseSet.h"
-#include "llvm/ADT/SmallVector.h"
-#include "llvm/ADT/StringMap.h"
-#include "llvm/Object/COFF.h"
-#include "llvm/Support/MemoryBuffer.h"
-#include <cstddef>
-#include <cstring>
-
-namespace {
-
-inline bool isCOFFUnwindSection(llvm::StringRef Name, llvm::StringRef Base) {
- return Name == Base || (Name.starts_with(Base) &&
- Name.drop_front(Base.size()).starts_with("$"));
-}
-
-// MinGW emits .xdata$foo/.pdata$foo as SELECT_ANY without an external
-// COMDAT symbol. JITLink waits for that symbol and never creates the section
-// symbol used by relocations. Express their implicit association with .text$foo
-// as ordinary associative COMDATs in a private copy of the object instead.
-inline llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
-normalizeMinGWObject(std::unique_ptr<llvm::MemoryBuffer> Buffer) {
- using namespace llvm;
- auto Object = object::ObjectFile::createObjectFile(Buffer->getMemBufferRef());
- if (!Object)
- return Object.takeError();
- auto *COFF = dyn_cast<object::COFFObjectFile>(Object->get());
- if (!COFF || COFF->getMachine() != COFF::IMAGE_FILE_MACHINE_AMD64)
- return std::move(Buffer);
-
- StringMap<int32_t> CodeSections;
- DenseSet<int32_t> ExternalSections;
- for (const auto &Section : COFF->sections()) {
- auto Name = Section.getName();
- if (!Name)
- return Name.takeError();
- if (Name->starts_with(".text$") &&
- (COFF->getCOFFSection(Section)->Characteristics &
- COFF::IMAGE_SCN_LNK_COMDAT))
- CodeSections[*Name] = COFF->getSectionID(Section);
- }
- for (const auto &Symbol : COFF->symbols()) {
- auto Sym = COFF->getCOFFSymbol(Symbol);
- if (Sym.isExternal() && Sym.getSectionNumber() > 0)
- ExternalSections.insert(Sym.getSectionNumber());
- }
-
- struct Association { size_t Offset; int32_t Parent; };
- SmallVector<Association, 16> Associations;
- SmallVector<size_t, 2> PureVirtualReferences;
- SmallVector<size_t, 2> PureVirtualFallbacks;
- for (const auto &Symbol : COFF->symbols()) {
- auto Sym = COFF->getCOFFSymbol(Symbol);
- if (auto *Weak = Sym.getWeakExternal()) {
- auto Name = Symbol.getName();
- if (!Name)
- return Name.takeError();
- if (*Name == "__cxa_pure_virtual" &&
- Weak->Characteristics == COFF::IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY) {
- auto Fallback = COFF->getSymbol(Weak->TagIndex);
- if (!Fallback)
- return Fallback.takeError();
- if (Fallback->isAbsolute() && Fallback->getValue() == 0) {
- size_t Offset = reinterpret_cast<const char *>(Sym.getRawPtr()) -
- Buffer->getBufferStart();
- Offset += Sym.isBigObj()
- ? offsetof(object::coff_symbol32, StorageClass)
- : offsetof(object::coff_symbol16, StorageClass);
- PureVirtualReferences.push_back(Offset);
- size_t FallbackOffset =
- reinterpret_cast<const char *>(Fallback->getRawPtr()) -
- Buffer->getBufferStart();
- FallbackOffset += Fallback->isBigObj()
- ? offsetof(object::coff_symbol32, StorageClass)
- : offsetof(object::coff_symbol16, StorageClass);
- PureVirtualFallbacks.push_back(FallbackOffset);
- }
- }
- }
- auto *Def = Sym.getSectionDefinition();
- if (!Def || Def->Selection != COFF::IMAGE_COMDAT_SELECT_ANY ||
- Sym.getSectionNumber() <= 0 ||
- ExternalSections.contains(Sym.getSectionNumber()))
- continue;
- auto Section = COFF->getSection(Sym.getSectionNumber());
- if (!Section)
- return Section.takeError();
- if (!((*Section)->Characteristics & COFF::IMAGE_SCN_LNK_COMDAT))
- continue;
- auto Name = COFF->getSectionName(*Section);
- if (!Name)
- return Name.takeError();
- if (!Name->starts_with(".xdata$") && !Name->starts_with(".pdata$"))
- continue;
- std::string CodeName = (".text$" + Name->drop_front(7)).str();
- auto Parent = CodeSections.find(CodeName);
- if (Parent == CodeSections.end())
- return make_error<StringError>(
- "missing MinGW unwind code section '" + CodeName + "' in '" +
- Buffer->getBufferIdentifier().str() + "'",
- inconvertibleErrorCode());
- Associations.push_back({static_cast<size_t>(
- reinterpret_cast<const char *>(Def) -
- Buffer->getBufferStart()),
- Parent->second});
- }
- if (Associations.empty() && PureVirtualReferences.empty())
- return std::move(Buffer);
-
- auto Copy = WritableMemoryBuffer::getNewUninitMemBuffer(
- Buffer->getBufferSize(), Buffer->getBufferIdentifier());
- std::memcpy(Copy->getBufferStart(), Buffer->getBufferStart(),
- Buffer->getBufferSize());
- for (const auto &A : Associations) {
- auto *Def = reinterpret_cast<object::coff_aux_section_definition *>(
- Copy->getBufferStart() + A.Offset);
- Def->Selection = COFF::IMAGE_COMDAT_SELECT_ASSOCIATIVE;
- Def->NumberLowPart = A.Parent & 0xffff;
- Def->NumberHighPart = static_cast<uint32_t>(A.Parent) >> 16;
- }
- // JITLink cannot alias a weak external to an absolute-zero fallback.
- // The runner always supplies the host's __cxa_pure_virtual, so this known
- // optional ABI reference can be an ordinary external. Keep its auxiliary
- // entry count intact so the remaining symbol indices do not move.
- for (size_t Offset : PureVirtualReferences)
- Copy->getBufferStart()[Offset] = COFF::IMAGE_SYM_CLASS_EXTERNAL;
- // Only the now-unused weak auxiliary record references the fallback.
- // Keep it local so archive interfaces do not advertise a global definition.
- for (size_t Offset : PureVirtualFallbacks)
- Copy->getBufferStart()[Offset] = COFF::IMAGE_SYM_CLASS_STATIC;
- return std::unique_ptr<MemoryBuffer>(std::move(Copy));
-}
-
-} // namespace
-
-#endif
diff --git a/tools/driver/MinGWJIT.cc b/tools/driver/MinGWJIT.cc
new file mode 100644
index 0000000..7886b80
--- /dev/null
+++ b/tools/driver/MinGWJIT.cc
@@ -0,0 +1,447 @@
+#include "MinGWJIT.h"
+
+#if defined(_WIN32) && defined(__x86_64__)
+#ifndef NOMINMAX
+#define NOMINMAX
+#endif
+#include "llvm/ADT/DenseSet.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringMap.h"
+#include "llvm/ExecutionEngine/JITLink/JITLink.h"
+#include "llvm/ExecutionEngine/JITLink/x86_64.h"
+#include "llvm/ExecutionEngine/Orc/ObjectFileInterface.h"
+#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
+#include "llvm/ExecutionEngine/Orc/Shared/WrapperFunctionUtils.h"
+#include "llvm/Object/COFF.h"
+#include "llvm/Support/Endian.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include <algorithm>
+#include <cstddef>
+#include <cstring>
+#include <limits>
+#include <vector>
+
+namespace {
+
+bool isCOFFUnwindSection(llvm::StringRef Name, llvm::StringRef Base) {
+ return Name == Base || (Name.starts_with(Base) &&
+ Name.drop_front(Base.size()).starts_with("$"));
+}
+
+// MinGW emits .xdata$foo/.pdata$foo as SELECT_ANY without an external
+// COMDAT symbol. JITLink waits for that symbol and never creates the section
+// symbol used by relocations. Express their implicit association with .text$foo
+// as ordinary associative COMDATs in a private copy of the object instead.
+llvm::Expected<std::unique_ptr<llvm::MemoryBuffer>>
+normalizeMinGWObject(std::unique_ptr<llvm::MemoryBuffer> Buffer) {
+ using namespace llvm;
+ auto Object = object::ObjectFile::createObjectFile(Buffer->getMemBufferRef());
+ if (!Object)
+ return Object.takeError();
+ auto *COFF = dyn_cast<object::COFFObjectFile>(Object->get());
+ if (!COFF || COFF->getMachine() != COFF::IMAGE_FILE_MACHINE_AMD64)
+ return std::move(Buffer);
+
+ StringMap<int32_t> CodeSections;
+ DenseSet<int32_t> ExternalSections;
+ for (const auto &Section : COFF->sections()) {
+ auto Name = Section.getName();
+ if (!Name)
+ return Name.takeError();
+ if (Name->starts_with(".text$") &&
+ (COFF->getCOFFSection(Section)->Characteristics &
+ COFF::IMAGE_SCN_LNK_COMDAT))
+ CodeSections[*Name] = COFF->getSectionID(Section);
+ }
+ for (const auto &Symbol : COFF->symbols()) {
+ auto Sym = COFF->getCOFFSymbol(Symbol);
+ if (Sym.isExternal() && Sym.getSectionNumber() > 0)
+ ExternalSections.insert(Sym.getSectionNumber());
+ }
+
+ struct Association { size_t Offset; int32_t Parent; };
+ SmallVector<Association, 16> Associations;
+ SmallVector<size_t, 2> PureVirtualReferences;
+ SmallVector<size_t, 2> PureVirtualFallbacks;
+ for (const auto &Symbol : COFF->symbols()) {
+ auto Sym = COFF->getCOFFSymbol(Symbol);
+ if (auto *Weak = Sym.getWeakExternal()) {
+ auto Name = Symbol.getName();
+ if (!Name)
+ return Name.takeError();
+ if (*Name == "__cxa_pure_virtual" &&
+ Weak->Characteristics == COFF::IMAGE_WEAK_EXTERN_SEARCH_NOLIBRARY) {
+ auto Fallback = COFF->getSymbol(Weak->TagIndex);
+ if (!Fallback)
+ return Fallback.takeError();
+ if (Fallback->isAbsolute() && Fallback->getValue() == 0) {
+ size_t Offset = reinterpret_cast<const char *>(Sym.getRawPtr()) -
+ Buffer->getBufferStart();
+ Offset += Sym.isBigObj()
+ ? offsetof(object::coff_symbol32, StorageClass)
+ : offsetof(object::coff_symbol16, StorageClass);
+ PureVirtualReferences.push_back(Offset);
+ size_t FallbackOffset =
+ reinterpret_cast<const char *>(Fallback->getRawPtr()) -
+ Buffer->getBufferStart();
+ FallbackOffset += Fallback->isBigObj()
+ ? offsetof(object::coff_symbol32, StorageClass)
+ : offsetof(object::coff_symbol16, StorageClass);
+ PureVirtualFallbacks.push_back(FallbackOffset);
+ }
+ }
+ }
+ auto *Def = Sym.getSectionDefinition();
+ if (!Def || Def->Selection != COFF::IMAGE_COMDAT_SELECT_ANY ||
+ Sym.getSectionNumber() <= 0 ||
+ ExternalSections.contains(Sym.getSectionNumber()))
+ continue;
+ auto Section = COFF->getSection(Sym.getSectionNumber());
+ if (!Section)
+ return Section.takeError();
+ if (!((*Section)->Characteristics & COFF::IMAGE_SCN_LNK_COMDAT))
+ continue;
+ auto Name = COFF->getSectionName(*Section);
+ if (!Name)
+ return Name.takeError();
+ if (!Name->starts_with(".xdata$") && !Name->starts_with(".pdata$"))
+ continue;
+ std::string CodeName = (".text$" + Name->drop_front(7)).str();
+ auto Parent = CodeSections.find(CodeName);
+ if (Parent == CodeSections.end())
+ return make_error<StringError>(
+ "missing MinGW unwind code section '" + CodeName + "' in '" +
+ Buffer->getBufferIdentifier().str() + "'",
+ inconvertibleErrorCode());
+ Associations.push_back({static_cast<size_t>(
+ reinterpret_cast<const char *>(Def) -
+ Buffer->getBufferStart()),
+ Parent->second});
+ }
+ if (Associations.empty() && PureVirtualReferences.empty())
+ return std::move(Buffer);
+
+ auto Copy = WritableMemoryBuffer::getNewUninitMemBuffer(
+ Buffer->getBufferSize(), Buffer->getBufferIdentifier());
+ std::memcpy(Copy->getBufferStart(), Buffer->getBufferStart(),
+ Buffer->getBufferSize());
+ for (const auto &A : Associations) {
+ auto *Def = reinterpret_cast<object::coff_aux_section_definition *>(
+ Copy->getBufferStart() + A.Offset);
+ Def->Selection = COFF::IMAGE_COMDAT_SELECT_ASSOCIATIVE;
+ Def->NumberLowPart = A.Parent & 0xffff;
+ Def->NumberHighPart = static_cast<uint32_t>(A.Parent) >> 16;
+ }
+ // JITLink cannot alias a weak external to an absolute-zero fallback.
+ // The runner always supplies the host's __cxa_pure_virtual, so this known
+ // optional ABI reference can be an ordinary external. Keep its auxiliary
+ // entry count intact so the remaining symbol indices do not move.
+ for (size_t Offset : PureVirtualReferences)
+ Copy->getBufferStart()[Offset] = COFF::IMAGE_SYM_CLASS_EXTERNAL;
+ // Only the now-unused weak auxiliary record references the fallback.
+ // Keep it local so archive interfaces do not advertise a global definition.
+ for (size_t Offset : PureVirtualFallbacks)
+ Copy->getBufferStart()[Offset] = COFF::IMAGE_SYM_CLASS_STATIC;
+ return std::unique_ptr<MemoryBuffer>(std::move(Copy));
+}
+
+} // namespace
+
+// Parse LLVM COFF helpers and normalization before Windows IMAGE_* macros.
+#include <windows.h>
+
+namespace {
+
+// Keep MinGW's GNU C++ ABI while registering COFF unwind tables with Windows.
+// Full ORC COFFPlatform setup loads the VC runtime, which this build does not use.
+static constexpr llvm::StringLiteral COFFSEHTableSection = "$__cpsc_seh";
+
+static llvm::Error addCOFFJumpStubs(llvm::jitlink::LinkGraph &G) {
+ using namespace llvm;
+ using namespace llvm::jitlink;
+ using namespace llvm::jitlink::x86_64;
+ GOTTableManager GOT(G);
+ PLTTableManager PLT(G, GOT);
+ // Creating a stub may add sections/blocks. Visit only the original blocks.
+ std::vector<Block *> Blocks(G.blocks().begin(), G.blocks().end());
+ for (auto *B : Blocks) {
+ bool Executable = (B->getSection().getMemProt() & orc::MemProt::Exec) !=
+ orc::MemProt::None;
+ for (auto &E : B->edges()) {
+ Symbol &Target = E.getTarget();
+ if (!Target.isExternal())
+ continue;
+ bool Personality = false;
+ if (isCOFFUnwindSection(B->getSection().getName(), ".xdata") &&
+ Target.hasName()) {
+ StringRef Name = *Target.getName();
+ Personality = Name == "__gxx_personality_seh0" ||
+ Name == "__gxx_personality_sj0" ||
+ Name == "__gxx_personality_v0";
+ }
+ // COFF's private PCRel32 kind is lowered only in PreFixup.
+ // Use its name rather than confusing it with x86_64::PCRel32.
+ // Only E8/E9 direct branches with a plain symbol target need a
+ // jump stub; RIP-relative data and indirect calls must stay intact.
+ bool DirectBranch = false;
+ if (Executable && !B->isZeroFill() && E.getAddend() == 0 &&
+ StringRef(G.getEdgeKindName(E.getKind())) == "PCRel32") {
+ auto Content = B->getContent();
+ size_t Offset = E.getOffset();
+ if (Offset > 0 && Offset <= Content.size() &&
+ Content.size() - Offset >= 4) {
+ unsigned char Opcode = Content[Offset - 1];
+ DirectBranch = Opcode == 0xe8 || Opcode == 0xe9;
+ }
+ }
+ if (Personality || DirectBranch)
+ E.setTarget(PLT.getEntryForTarget(G, Target));
+ }
+ }
+ return Error::success();
+}
+
+static llvm::Error registerSEHTable(llvm::orc::ExecutorAddrRange TableRange,
+ llvm::orc::ExecutorAddr ImageBase) {
+ using llvm::make_error;
+ using llvm::StringError;
+ using llvm::inconvertibleErrorCode;
+ size_t Size = TableRange.size();
+ if (Size == 0 || ImageBase.isNull() || Size % sizeof(RUNTIME_FUNCTION))
+ return make_error<StringError>("invalid embedded SEH function table",
+ inconvertibleErrorCode());
+ uint64_t Count64 = Size / sizeof(RUNTIME_FUNCTION);
+ if (Count64 > static_cast<uint64_t>(std::numeric_limits<DWORD>::max()))
+ return make_error<StringError>("invalid embedded SEH function table",
+ inconvertibleErrorCode());
+ auto *Table = TableRange.Start.toPtr<const RUNTIME_FUNCTION *>();
+ DWORD PrevBegin = 0;
+ for (uint64_t I = 0; I < Count64; ++I) {
+ DWORD Begin = llvm::support::endian::read32le(
+ reinterpret_cast<const char *>(Table + I) + 0);
+ DWORD End = llvm::support::endian::read32le(
+ reinterpret_cast<const char *>(Table + I) + 4);
+ if (!(Begin < End))
+ return make_error<StringError>("invalid embedded SEH function table",
+ inconvertibleErrorCode());
+ if (I > 0 && !(PrevBegin < Begin))
+ return make_error<StringError>("invalid embedded SEH function table",
+ inconvertibleErrorCode());
+ PrevBegin = Begin;
+ }
+ auto *MutableTable = const_cast<PRUNTIME_FUNCTION>(Table);
+ DWORD Count = static_cast<DWORD>(Count64);
+ DWORD64 Base = static_cast<DWORD64>(ImageBase.getValue());
+ if (!RtlAddFunctionTable(MutableTable, Count, Base))
+ return make_error<StringError>("cannot register embedded SEH function table",
+ inconvertibleErrorCode());
+ return llvm::Error::success();
+}
+
+static llvm::Error deregisterSEHTable(llvm::orc::ExecutorAddrRange TableRange,
+ llvm::orc::ExecutorAddr) {
+ auto *Table = TableRange.Start.toPtr<PRUNTIME_FUNCTION>();
+ if (TableRange.size() == 0 || !Table)
+ return llvm::Error::success();
+ RtlDeleteFunctionTable(Table);
+ return llvm::Error::success();
+}
+
+extern "C" llvm::orc::shared::CWrapperFunctionBuffer
+cps_register_seh_table_alloc_action(const char *ArgData, size_t ArgSize) {
+ using namespace llvm::orc::shared;
+ return WrapperFunction<SPSError(SPSExecutorAddrRange, SPSExecutorAddr)>::handle(
+ ArgData, ArgSize, registerSEHTable)
+ .release();
+}
+
+extern "C" llvm::orc::shared::CWrapperFunctionBuffer
+cps_deregister_seh_table_alloc_action(const char *ArgData, size_t ArgSize) {
+ using namespace llvm::orc::shared;
+ return WrapperFunction<SPSError(SPSExecutorAddrRange, SPSExecutorAddr)>::handle(
+ ArgData, ArgSize, deregisterSEHTable)
+ .release();
+}
+
+class MinGWSEHRegistrationPlugin final
+ : public llvm::orc::LinkGraphLinkingLayer::Plugin {
+public:
+ void modifyPassConfig(llvm::orc::MaterializationResponsibility &,
+ llvm::jitlink::LinkGraph &G,
+ llvm::jitlink::PassConfiguration &Config) override {
+ using namespace llvm;
+ using namespace llvm::orc;
+ using namespace llvm::jitlink;
+ const Triple &TT = G.getTargetTriple();
+ if (!TT.isOSBinFormatCOFF() || TT.getArch() != Triple::x86_64)
+ return;
+
+ // LLVM's SEHFrameKeepAlivePass retains .pdata; associative COMDATs
+ // retain the normalized suffixed sections. Nearby jump stubs let
+ // 32-bit personality RVAs and direct branches reach the host runtime.
+ Config.PostPrunePasses.push_back(addCOFFJumpStubs);
+
+ // Tables sharing an image base have overlapping lookup ranges in the
+ // Windows unwinder. Reserve one table for all retained .pdata sections
+ // in this graph, then fill and sort its entries after relocations.
+ Config.PostPrunePasses.push_back([](LinkGraph &G) -> Error {
+ size_t Size = 0;
+ for (auto &Sec : G.sections()) {
+ if (!isCOFFUnwindSection(Sec.getName(), ".pdata"))
+ continue;
+ for (auto *B : Sec.blocks()) {
+ if (B->isZeroFill() || B->getSize() % sizeof(RUNTIME_FUNCTION))
+ return make_error<JITLinkError>(
+ "invalid embedded SEH function table");
+ Size += B->getSize();
+ }
+ }
+ if (Size) {
+ auto &Sec = G.createSection(COFFSEHTableSection, MemProt::Read);
+ G.createMutableContentBlock(Sec, Size, ExecutorAddr(),
+ alignof(RUNTIME_FUNCTION), 0);
+ }
+ return Error::success();
+ });
+
+ // COFF unwind tables use 32-bit image-relative addresses, so the
+ // image base must sit at or below the lowest JIT address. Alias the
+ // external __ImageBase to the lowest defined block once addresses
+ // are final; a trailing zero block would push RVAs negative.
+ Config.PostAllocationPasses.push_back([](LinkGraph &G) -> Error {
+ // A graph-local alias never collides at materialization time,
+ // no matter how many graphs define the same base name.
+ if (G.findDefinedSymbolByName(G.intern("__ImageBase")))
+ return Error::success();
+ auto *ImageBaseExt =
+ G.findExternalSymbolByName(G.intern("__ImageBase"));
+ if (!ImageBaseExt)
+ return Error::success();
+ jitlink::Block *Low = nullptr;
+ for (auto &Sec : G.sections()) {
+ for (auto *B : Sec.blocks()) {
+ if (!Low || B->getAddress() < Low->getAddress())
+ Low = B;
+ }
+ }
+ if (!Low)
+ return make_error<JITLinkError>(
+ "missing embedded __ImageBase content");
+ G.makeDefined(*ImageBaseExt, *Low, 0, 8, Linkage::Strong,
+ Scope::Local, true);
+ return Error::success();
+ });
+
+ // Copy fixed-up entries into one sorted, allocation-owned table.
+ // Register it once per graph, with deregistration on link failure or
+ // teardown before the table and its code are released.
+ Config.PostFixupPasses.push_back(
+ [](LinkGraph &G) -> Error {
+ using namespace llvm::orc::shared;
+ auto *TableSec = G.findSectionByName(COFFSEHTableSection);
+ if (!TableSec)
+ return Error::success();
+ auto *Table = *TableSec->blocks().begin();
+ std::vector<RUNTIME_FUNCTION> Entries;
+ Entries.reserve(Table->getSize() / sizeof(RUNTIME_FUNCTION));
+ for (auto &Sec : G.sections()) {
+ if (!isCOFFUnwindSection(Sec.getName(), ".pdata"))
+ continue;
+ for (auto *B : Sec.blocks()) {
+ size_t Start = Entries.size();
+ Entries.resize(Start + B->getSize() / sizeof(RUNTIME_FUNCTION));
+ if (B->getSize())
+ std::memcpy(Entries.data() + Start, B->getContent().data(),
+ B->getSize());
+ }
+ }
+ std::sort(Entries.begin(), Entries.end(),
+ [](const RUNTIME_FUNCTION &L, const RUNTIME_FUNCTION &R) {
+ return L.BeginAddress < R.BeginAddress;
+ });
+ std::memcpy(Table->getAlreadyMutableContent().data(), Entries.data(),
+ Table->getSize());
+ ExecutorAddrRange TableRange(Table->getAddress(), Table->getSize());
+ auto *ImageBaseSym =
+ G.findDefinedSymbolByName(G.intern("__ImageBase"));
+ if (!ImageBaseSym)
+ return make_error<JITLinkError>(
+ "missing embedded __ImageBase for SEH registration");
+ ExecutorAddr ImageBase = ImageBaseSym->getAddress();
+ if (ImageBase.isNull())
+ return make_error<JITLinkError>("invalid embedded SEH image base");
+ auto RegisterAction =
+ WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddrRange,
+ SPSExecutorAddr>>(
+ ExecutorAddr::fromPtr(&cps_register_seh_table_alloc_action),
+ TableRange, ImageBase);
+ if (!RegisterAction)
+ return RegisterAction.takeError();
+ auto DeregisterAction =
+ WrapperFunctionCall::Create<SPSArgList<SPSExecutorAddrRange,
+ SPSExecutorAddr>>(
+ ExecutorAddr::fromPtr(&cps_deregister_seh_table_alloc_action),
+ TableRange, ImageBase);
+ if (!DeregisterAction)
+ return DeregisterAction.takeError();
+ G.allocActions().push_back(
+ {std::move(*RegisterAction), std::move(*DeregisterAction)});
+ return Error::success();
+ });
+ }
+
+ llvm::Error notifyFailed(llvm::orc::MaterializationResponsibility &) override {
+ return llvm::Error::success();
+ }
+
+ llvm::Error notifyRemovingResources(llvm::orc::JITDylib &,
+ llvm::orc::ResourceKey) override {
+ return llvm::Error::success();
+ }
+
+ void notifyTransferringResources(llvm::orc::JITDylib &,
+ llvm::orc::ResourceKey,
+ llvm::orc::ResourceKey) override {}
+};
+
+// Both user objects and lazily extracted runtime archive members pass through
+// this layer, before JITLink builds their graphs or runs plugin passes.
+class MinGWObjectLinkingLayer final : public llvm::orc::ObjectLinkingLayer {
+public:
+ using ObjectLinkingLayer::ObjectLinkingLayer;
+
+ void emit(std::unique_ptr<llvm::orc::MaterializationResponsibility> R,
+ std::unique_ptr<llvm::MemoryBuffer> O) override {
+ auto Normalized = normalizeMinGWObject(std::move(O));
+ if (!Normalized) {
+ getExecutionSession().reportError(Normalized.takeError());
+ R->failMaterialization();
+ return;
+ }
+ ObjectLinkingLayer::emit(std::move(R), std::move(*Normalized));
+ }
+};
+
+} // namespace
+
+std::unique_ptr<llvm::orc::ObjectLinkingLayer>
+createMinGWObjectLinkingLayer(llvm::orc::ExecutionSession &ES,
+ llvm::jitlink::JITLinkMemoryManager &MM) {
+ auto Layer = std::make_unique<MinGWObjectLinkingLayer>(ES, MM);
+ Layer->setOverrideObjectFlagsWithResponsibilityFlags(true);
+ Layer->setAutoClaimResponsibilityForObjectSymbols(true);
+ Layer->addPlugin(std::make_shared<MinGWSEHRegistrationPlugin>());
+ return Layer;
+}
+
+llvm::Expected<llvm::orc::MaterializationUnit::Interface>
+getMinGWObjectFileInterface(llvm::orc::ExecutionSession &ES,
+ llvm::MemoryBufferRef Ref) {
+ // Archive interfaces must describe the same normalized object emit links.
+ auto Normalized = normalizeMinGWObject(
+ llvm::MemoryBuffer::getMemBuffer(Ref, false));
+ if (!Normalized)
+ return Normalized.takeError();
+ return llvm::orc::getObjectFileInterface(ES, (*Normalized)->getMemBufferRef());
+}
+#endif
diff --git a/tools/driver/MinGWJIT.h b/tools/driver/MinGWJIT.h
new file mode 100644
index 0000000..8064f0f
--- /dev/null
+++ b/tools/driver/MinGWJIT.h
@@ -0,0 +1,25 @@
+#ifndef CPSC_DRIVER_MINGWJIT_H
+#define CPSC_DRIVER_MINGWJIT_H
+
+#if defined(_WIN32) && defined(__x86_64__)
+#include "llvm/ExecutionEngine/Orc/Core.h"
+#include "llvm/Support/MemoryBufferRef.h"
+#include <memory>
+
+namespace llvm::jitlink {
+class JITLinkMemoryManager;
+}
+namespace llvm::orc {
+class ObjectLinkingLayer;
+}
+
+std::unique_ptr<llvm::orc::ObjectLinkingLayer>
+createMinGWObjectLinkingLayer(llvm::orc::ExecutionSession &ES,
+ llvm::jitlink::JITLinkMemoryManager &MM);
+
+llvm::Expected<llvm::orc::MaterializationUnit::Interface>
+getMinGWObjectFileInterface(llvm::orc::ExecutionSession &ES,
+ llvm::MemoryBufferRef Ref);
+#endif
+
+#endif