diff options
| -rw-r--r-- | AGENTS/ARCH.md | 77 | ||||
| -rw-r--r-- | CMakeLists.txt | 4 | ||||
| -rw-r--r-- | tools/driver/EmbeddedRunner.cc | 335 | ||||
| -rw-r--r-- | tools/driver/MinGWCOFF.h | 138 | ||||
| -rw-r--r-- | tools/driver/MinGWJIT.cc | 447 | ||||
| -rw-r--r-- | tools/driver/MinGWJIT.h | 25 |
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 |
