[llvm] [ORC] Add AutoImportGenerator for COFF dllimport auto-import (PR #203914)
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 2 00:32:03 PDT 2026
https://github.com/lhames updated https://github.com/llvm/llvm-project/pull/203914
>From c646cbd510c07708a3ce75b7542487e99d302bcb Mon Sep 17 00:00:00 2001
From: Milica Kovacevic <mkovacevic at baylibre.com>
Date: Mon, 15 Jun 2026 16:15:41 +0200
Subject: [PATCH] [ORC] Add COFFAutoImportGenerator for COFF dllimport
auto-import
Lazily synthesizes COFF __imp_ slots and jump-thunks for symbols
exported by a given DLL, so COFF objects link without hand-built import
libraries. The DLL's export table is authoritative (unexported symbols
fail as in a static link). Stubs are owned by one ResourceTracker for
cleanup. Supports whichever architectures JITLink has a pointer /
pointer-jump-stub creator registered for; Load() fails eagerly for any
other architecture.
Wires auto-import into llvm-jitlink as a new -auto-l library-load
modifier, so it participates in -L search
paths, extension resolution, and command-line ordering like every other
library kind. Adds ORCv2.md docs. #190122
---
llvm/docs/ORCv2.md | 39 ++++
.../Orc/COFFAutoImportGenerator.h | 112 +++++++++++
.../ExecutionEngine/JITLink/COFF_x86_64.cpp | 6 +-
llvm/lib/ExecutionEngine/Orc/CMakeLists.txt | 1 +
.../Orc/COFFAutoImportGenerator.cpp | 132 +++++++++++++
llvm/tools/llvm-jitlink/llvm-jitlink.cpp | 63 ++++++-
llvm/tools/llvm-jitlink/llvm-jitlink.h | 4 +
.../ExecutionEngine/Orc/CMakeLists.txt | 1 +
.../Orc/COFFAutoImportGeneratorTest.cpp | 178 ++++++++++++++++++
.../llvm/lib/ExecutionEngine/Orc/BUILD.gn | 1 +
.../unittests/ExecutionEngine/Orc/BUILD.gn | 1 +
11 files changed, 532 insertions(+), 6 deletions(-)
create mode 100644 llvm/include/llvm/ExecutionEngine/Orc/COFFAutoImportGenerator.h
create mode 100644 llvm/lib/ExecutionEngine/Orc/COFFAutoImportGenerator.cpp
create mode 100644 llvm/unittests/ExecutionEngine/Orc/COFFAutoImportGeneratorTest.cpp
diff --git a/llvm/docs/ORCv2.md b/llvm/docs/ORCv2.md
index 5aeb9d67f55d4..39ddbc721d97e 100644
--- a/llvm/docs/ORCv2.md
+++ b/llvm/docs/ORCv2.md
@@ -849,6 +849,45 @@ JD.addGenerator(cantFail(DynamicLibrarySearchGenerator::GetForCurrentProcess(
CompileLayer.add(JD, loadModule(...));
```
+On Windows/COFF targets, calls to dllimport functions are emitted as indirect
+calls through an ``__imp_`` *import address table* (IAT) slot, and even direct
+calls to library functions are expected to bind to a thunk supplied by an import
+library. ORC provides the ``COFFAutoImportGenerator`` utility to synthesize these on
+demand from a dynamic library, so that COFF objects can be JIT-linked without
+building import libraries. The generator is bound to a single DLL: that DLL's
+export table is the authority on what may be synthesized, so a reference to a
+symbol the DLL does not export remains unresolved and the link fails, exactly as
+a static link against the corresponding import library would.
+
+ .. code-block:: c++
+
+ auto &JD = ES.createJITDylib("main");
+
+ if (auto AIGOrErr =
+ COFFAutoImportGenerator::Load(ES, ObjLinkingLayer, DylibMgr,
+ "/path/to/lib.dll"))
+ JD.addGenerator(std::move(*AIGOrErr));
+ else
+ return AIGOrErr.takeError();
+
+ // COFF objects added to JD can now call functions exported by lib.dll, both
+ // directly and via the dllimport (__imp_) convention.
+ ObjLinkingLayer.add(JD, loadObject(...));
+
+For each exported function ``X`` that is referenced, the generator synthesizes an
+``__imp_X`` IAT slot holding ``X``'s address in the library plus an ``X`` thunk
+that jumps through that slot. It is "easy mode": it assumes every import is a
+function and makes no attempt to distinguish code from data, so data imports are
+unsupported and clients that need them must supply an import library or use
+``__declspec(dllimport)``. Note also that, because ``X`` resolves to a synthesized
+thunk, ``&X`` yields the thunk's address rather than the implementation in the
+library. ``COFFAutoImportGenerator`` supports whichever architectures JITLink has
+a pointer / pointer-jump-stub creator registered for; ``Load`` fails for any
+other architecture. It resolves imports through the executor's ``DylibManager``,
+so it works for both in-process and out-of-process execution. For the more
+general case where the underlying symbol is resolved through the JITDylib's
+link order rather than a specific library, see ``DLLImportDefinitionGenerator``.
+
References to process or library symbols could also be hardcoded into your IR
or object files using the symbols' raw addresses, however symbolic resolution
using the JIT symbol tables should be preferred: it keeps the IR and objects
diff --git a/llvm/include/llvm/ExecutionEngine/Orc/COFFAutoImportGenerator.h b/llvm/include/llvm/ExecutionEngine/Orc/COFFAutoImportGenerator.h
new file mode 100644
index 0000000000000..18fb2680c8238
--- /dev/null
+++ b/llvm/include/llvm/ExecutionEngine/Orc/COFFAutoImportGenerator.h
@@ -0,0 +1,112 @@
+//===- COFFAutoImportGenerator.h - COFF dllimport auto-import -*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// Declares COFFAutoImportGenerator, which synthesizes COFF dllimport __imp_
+// symbols and jump-thunks for the symbols exported by a single dynamic
+// library.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_EXECUTIONENGINE_ORC_COFFAUTOIMPORTGENERATOR_H
+#define LLVM_EXECUTIONENGINE_ORC_COFFAUTOIMPORTGENERATOR_H
+
+#include "llvm/ADT/StringRef.h"
+#include "llvm/ExecutionEngine/JITLink/JITLink.h"
+#include "llvm/ExecutionEngine/Orc/Core.h"
+#include "llvm/ExecutionEngine/Orc/DylibManager.h"
+#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
+#include "llvm/Support/Compiler.h"
+
+namespace llvm::orc {
+
+/// A utility class that synthesizes COFF dllimport __imp_ symbols and PLT
+/// stubs for the symbols exported by a single dynamic library ("easy mode"
+/// auto-import).
+///
+/// Unlike DLLImportDefinitionGenerator, which resolves the underlying symbol
+/// through the JITDylib's link order, this generator is bound to one dynamic
+/// library: that library's export table is the authority on what may be
+/// synthesized. Any requested symbol the library does not export is left
+/// unresolved, so the link fails exactly as a static link against the
+/// corresponding import library would.
+///
+/// Synthesis is lazy (driven by JITLink external-symbol lookups) and assumes
+/// every import is a function: for each resolved import X it creates an __imp_X
+/// pointer slot holding X's address in the library and an X thunk that jumps
+/// through that slot. Data imports are not distinguished from code and will
+/// misbehave; clients with data imports must supply an import library or use
+/// __declspec(dllimport). Note also that &X resolves to the synthesized thunk,
+/// not to X's address inside the library.
+///
+/// All synthesized stubs share a single ResourceTracker; see
+/// getImportStubsResourceTracker() to reclaim them.
+///
+/// Supports whichever architectures JITLink has a pointer / pointer-jump-stub
+/// creator registered for (see jitlink::getAnonymousPointerCreator and
+/// jitlink::getPointerJumpStubCreator); Load() fails for any other
+/// architecture.
+class LLVM_ABI COFFAutoImportGenerator : public DefinitionGenerator {
+public:
+ /// Loads the dynamic library at the given path in the executor (via the given
+ /// DylibManager) and, on success, returns a COFFAutoImportGenerator that
+ /// synthesizes imports for the symbols it exports. On failure returns the
+ /// reason the library failed to load. Resolving imports through DylibManager
+ /// means this works for both in-process and out-of-process execution.
+ static Expected<std::unique_ptr<COFFAutoImportGenerator>>
+ Load(ExecutionSession &ES, ObjectLinkingLayer &L, DylibManager &DylibMgr,
+ const char *LibraryPath);
+
+ Error tryToGenerate(LookupState &LS, LookupKind K, JITDylib &JD,
+ JITDylibLookupFlags JDLookupFlags,
+ const SymbolLookupSet &Symbols) override;
+
+ /// Returns the ResourceTracker that owns the stubs synthesized by this
+ /// generator, or null if none have been synthesized yet. Calling remove() on
+ /// it reclaims every synthesized __imp_ slot and thunk without affecting
+ /// other definitions in the JITDylib; synthesis afterwards transparently
+ /// starts a fresh tracker. Not thread-safe with respect to lookups that may
+ /// concurrently trigger synthesis -- reclaim at a quiescent point.
+ ResourceTrackerSP getImportStubsResourceTracker() const {
+ return ImportStubsRT;
+ }
+
+private:
+ COFFAutoImportGenerator(ExecutionSession &ES, ObjectLinkingLayer &L,
+ DylibManager &DylibMgr,
+ tpctypes::DylibHandle LibHandle,
+ jitlink::AnonymousPointerCreator CreatePointer,
+ jitlink::PointerJumpStubCreator CreateStub)
+ : ES(ES), L(L), DylibMgr(DylibMgr), LibHandle(LibHandle),
+ CreatePointer(std::move(CreatePointer)),
+ CreateStub(std::move(CreateStub)) {}
+
+ Expected<std::unique_ptr<jitlink::LinkGraph>>
+ createStubsGraph(const SymbolMap &Resolved);
+
+ static constexpr StringLiteral getImpPrefix() { return "__imp_"; }
+ static constexpr StringLiteral getSectionName() {
+ return "$__AUTOIMPORT_STUBS";
+ }
+
+ ExecutionSession &ES;
+ ObjectLinkingLayer &L;
+ DylibManager &DylibMgr;
+ tpctypes::DylibHandle LibHandle;
+
+ /// Cached at Load() time so unsupported architectures are rejected eagerly,
+ /// rather than later inside tryToGenerate's asynchronous lookup callback.
+ jitlink::AnonymousPointerCreator CreatePointer;
+ jitlink::PointerJumpStubCreator CreateStub;
+
+ /// Owns the synthesized stubs; (re)created lazily on first synthesis.
+ ResourceTrackerSP ImportStubsRT;
+};
+
+} // namespace llvm::orc
+
+#endif // LLVM_EXECUTIONENGINE_ORC_COFFAUTOIMPORTGENERATOR_H
diff --git a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp
index 2144b2c255d47..49fd25aec62c5 100644
--- a/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp
+++ b/llvm/lib/ExecutionEngine/JITLink/COFF_x86_64.cpp
@@ -263,8 +263,8 @@ class COFFLinkGraphLowering_x86_64 {
//
// X is left external, so its address is provided by whatever resolves the
// JITDylib's externals (an import library, a DynamicLibrarySearchGenerator,
-// AutoImportGenerator, ...). If X is unresolvable the link fails, exactly as a
-// static link against the corresponding import library would.
+// COFFAutoImportGenerator, ...). If X is unresolvable the link fails, exactly
+// as a static link against the corresponding import library would.
//
// This is the COFF analog of the ELF/Mach-O GOT builder, but deliberately NOT
// written as a TableManager/visitEdge pass like x86_64::GOTTableManager. ELF's
@@ -279,7 +279,7 @@ class COFFLinkGraphLowering_x86_64 {
//
// Direct (non-dllimport) references such as `callq foo` are intentionally not
// handled here: those are either kept in range by the slab allocator or thunked
-// by the opt-in AutoImportGenerator -- both outside this pass.
+// by the opt-in COFFAutoImportGenerator -- both outside this pass.
Error synthesizeIATEntries_COFF_x86_64(LinkGraph &G) {
static constexpr StringRef ImpPrefix = "__imp_";
diff --git a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
index 40505ef980a66..e3cf6cfae3ad6 100644
--- a/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
+++ b/llvm/lib/ExecutionEngine/Orc/CMakeLists.txt
@@ -10,6 +10,7 @@ add_llvm_component_library(LLVMOrcJIT
AbsoluteSymbols.cpp
BacktraceTools.cpp
COFF.cpp
+ COFFAutoImportGenerator.cpp
COFFVCRuntimeSupport.cpp
COFFPlatform.cpp
CompileOnDemandLayer.cpp
diff --git a/llvm/lib/ExecutionEngine/Orc/COFFAutoImportGenerator.cpp b/llvm/lib/ExecutionEngine/Orc/COFFAutoImportGenerator.cpp
new file mode 100644
index 0000000000000..9474945f50b77
--- /dev/null
+++ b/llvm/lib/ExecutionEngine/Orc/COFFAutoImportGenerator.cpp
@@ -0,0 +1,132 @@
+//===- COFFAutoImportGenerator.cpp - COFF dllimport auto-import ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/Orc/COFFAutoImportGenerator.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/Twine.h"
+#include "llvm/ExecutionEngine/JITLink/JITLink.h"
+#include "llvm/ExecutionEngine/Orc/Shared/ExecutorSymbolDef.h"
+
+namespace llvm {
+namespace orc {
+
+Expected<std::unique_ptr<COFFAutoImportGenerator>>
+COFFAutoImportGenerator::Load(ExecutionSession &ES, ObjectLinkingLayer &L,
+ DylibManager &DylibMgr, const char *LibraryPath) {
+ Triple TT = ES.getTargetTriple();
+
+ auto CreatePointer = jitlink::getAnonymousPointerCreator(TT);
+ if (!CreatePointer)
+ return make_error<StringError>(
+ "COFFAutoImportGenerator: no pointer creator for " + TT.str(),
+ inconvertibleErrorCode());
+
+ auto CreateStub = jitlink::getPointerJumpStubCreator(TT);
+ if (!CreateStub)
+ return make_error<StringError>(
+ "COFFAutoImportGenerator: no stub creator for " + TT.str(),
+ inconvertibleErrorCode());
+
+ auto LibHandle = DylibMgr.loadDylib(LibraryPath);
+ if (!LibHandle)
+ return LibHandle.takeError();
+
+ return std::unique_ptr<COFFAutoImportGenerator>(new COFFAutoImportGenerator(
+ ES, L, DylibMgr, *LibHandle, std::move(CreatePointer),
+ std::move(CreateStub)));
+}
+
+Error COFFAutoImportGenerator::tryToGenerate(LookupState &LS, LookupKind K,
+ JITDylib &JD,
+ JITDylibLookupFlags JDLookupFlags,
+ const SymbolLookupSet &Symbols) {
+ if (Symbols.empty())
+ return Error::success();
+
+ // Weakly reference each symbol (minus any __imp_ prefix) so unexported names
+ // are left unresolved; de-dup __imp_X and X into one lookup.
+ SymbolLookupSet LookupSymbols;
+ DenseSet<SymbolStringPtr> Seen;
+ for (auto &KV : Symbols) {
+ StringRef Base = *KV.first;
+ if (Base.starts_with(getImpPrefix()))
+ Base = Base.drop_front(getImpPrefix().size());
+ SymbolStringPtr BaseName = ES.intern(Base);
+ if (Seen.insert(BaseName).second)
+ LookupSymbols.add(BaseName, SymbolLookupFlags::WeaklyReferencedSymbol);
+ }
+
+ DylibMgr.lookupSymbolsAsync(
+ LibHandle, LookupSymbols,
+ [this, &JD, LS = std::move(LS), LookupSymbols](auto Result) mutable {
+ if (!Result)
+ return LS.continueLookup(Result.takeError());
+
+ // Keep the exported (non-null) results.
+ SymbolMap Resolved;
+ for (auto [Sym, Addr] : llvm::zip_equal(LookupSymbols, *Result))
+ if (Addr && *Addr)
+ Resolved[Sym.first] = {*Addr, JITSymbolFlags::Exported |
+ JITSymbolFlags::Callable};
+
+ if (Resolved.empty())
+ return LS.continueLookup(Error::success());
+
+ auto G = createStubsGraph(Resolved);
+ if (!G)
+ return LS.continueLookup(G.takeError());
+
+ // One tracker owns all stubs so they can be reclaimed together.
+ if (!ImportStubsRT || ImportStubsRT->isDefunct())
+ ImportStubsRT = JD.createResourceTracker();
+ LS.continueLookup(L.add(ImportStubsRT, std::move(*G)));
+ });
+
+ return Error::success();
+}
+
+// FIXME: Pull this into a helper shared with
+// DLLImportDefinitionGenerator::createStubsGraph (ExecutionUtils.cpp), which
+// builds the same __imp_X + thunk stubs. Until then, fixes here may need to
+// be mirrored there too.
+Expected<std::unique_ptr<jitlink::LinkGraph>>
+COFFAutoImportGenerator::createStubsGraph(const SymbolMap &Resolved) {
+ Triple TT = ES.getTargetTriple();
+
+ auto G = std::make_unique<jitlink::LinkGraph>(
+ "<AUTOIMPORT_STUBS>", ES.getSymbolStringPool(), TT, SubtargetFeatures(),
+ jitlink::getGenericEdgeKindName);
+ jitlink::Section &Sec =
+ G->createSection(getSectionName(), MemProt::Read | MemProt::Exec);
+
+ for (auto &KV : Resolved) {
+ // X's address as a local absolute symbol, referenced only by __imp_ (so it
+ // can't collide with the X thunk below).
+ jitlink::Symbol &Target = G->addAbsoluteSymbol(
+ *KV.first, KV.second.getAddress(), G->getPointerSize(),
+ jitlink::Linkage::Strong, jitlink::Scope::Local, false);
+
+ // __imp_X: pointer slot holding X's address.
+ jitlink::Symbol &Ptr = CreatePointer(*G, Sec, &Target, 0);
+ Ptr.setName(G->intern((Twine(getImpPrefix()) + *KV.first).str()));
+ // Weak: a later real definition overrides this fallback (link.exe-style).
+ Ptr.setLinkage(jitlink::Linkage::Weak);
+ Ptr.setScope(jitlink::Scope::Default);
+
+ // X: thunk "jmpq *__imp_X(%rip)" so direct calls work too.
+ jitlink::Symbol &Stub = CreateStub(*G, Sec, Ptr);
+ Stub.setName(G->intern(*KV.first));
+ Stub.setLinkage(jitlink::Linkage::Weak);
+ Stub.setScope(jitlink::Scope::Default);
+ }
+
+ return std::move(G);
+}
+
+} // end namespace orc
+} // end namespace llvm
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
index 51454fd47ae93..d62c70ed4b48f 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
@@ -17,6 +17,7 @@
#include "llvm/Config/llvm-config.h" // for LLVM_ON_UNIX, LLVM_ENABLE_THREADS
#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
#include "llvm/ExecutionEngine/Orc/BacktraceTools.h"
+#include "llvm/ExecutionEngine/Orc/COFFAutoImportGenerator.h"
#include "llvm/ExecutionEngine/Orc/COFFPlatform.h"
#include "llvm/ExecutionEngine/Orc/Debugging/DebugInfoSupport.h"
#include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupportPlugin.h"
@@ -167,6 +168,12 @@ static cl::list<std::string> WeakLibraries(
"resolve to null"),
cl::cat(JITLinkCategory));
+static cl::list<std::string>
+ LibrariesAuto("auto-l",
+ cl::desc("Link against library X in the library search paths "
+ "(auto-generate corresponding import library)"),
+ cl::Prefix, cl::cat(JITLinkCategory));
+
static cl::opt<bool> SearchSystemLibrary(
"search-sys-lib",
cl::desc("Add system library paths to library search paths"),
@@ -1498,9 +1505,8 @@ void Session::modifyPassConfig(LinkGraph &G, PassConfiguration &PassConfig) {
Expected<JITDylib *> Session::getOrLoadDynamicLibrary(StringRef LibPath) {
auto It = DynLibJDs.find(LibPath);
- if (It != DynLibJDs.end()) {
+ if (It != DynLibJDs.end())
return It->second;
- }
auto G =
EPCDynamicLibrarySearchGenerator::Load(ES, *DylibMgr, LibPath.data());
if (!G)
@@ -1528,6 +1534,37 @@ Error Session::loadAndLinkDynamicLibrary(JITDylib &JD, StringRef LibPath) {
return Error::success();
}
+Expected<JITDylib *> Session::getOrLoadAutoImportDLL(StringRef LibPath) {
+ auto It = AutoImportJDs.find(LibPath);
+ if (It != AutoImportJDs.end())
+ return It->second;
+ auto G = orc::COFFAutoImportGenerator::Load(ES, *ObjLayer, *DylibMgr,
+ LibPath.data());
+ if (!G)
+ return G.takeError();
+ auto JD = &ES.createBareJITDylib(LibPath.str());
+
+ JD->addGenerator(std::move(*G));
+ AutoImportJDs.emplace(LibPath.str(), JD);
+ LLVM_DEBUG({
+ dbgs() << "Loaded auto-import dynamic library " << LibPath.data() << " for "
+ << LibPath << "\n";
+ });
+ return JD;
+}
+
+Error Session::loadAndLinkAutoImportDLL(JITDylib &JD, StringRef LibPath) {
+ auto DL = getOrLoadAutoImportDLL(LibPath);
+ if (!DL)
+ return DL.takeError();
+ JD.addToLinkOrder(**DL);
+ LLVM_DEBUG({
+ dbgs() << "Linking auto-import dynamic library " << LibPath << " to "
+ << JD.getName() << "\n";
+ });
+ return Error::success();
+}
+
Error Session::FileInfo::registerGOTEntry(
LinkGraph &G, Symbol &Sym, GetSymbolTargetFunction GetSymbolTarget) {
if (Sym.isSymbolZeroFill())
@@ -2312,7 +2349,7 @@ static Error addLibraries(Session &S,
bool IsPath = false;
unsigned Position;
ArrayRef<StringRef> CandidateExtensions;
- enum { Standard, Hidden, Weak } Modifier;
+ enum { Standard, Hidden, Weak, Auto } Modifier;
};
// Queue to load library as in the order as it appears in the argument list.
@@ -2398,6 +2435,18 @@ static Error addLibraries(Session &S,
LibraryLoadQueue.push_back(std::move(LL));
}
+ // Add -auto-lx arguments to LibraryLoads.
+ for (auto LibAutoItr = LibrariesAuto.begin(),
+ LibAutoEnd = LibrariesAuto.end();
+ LibAutoItr != LibAutoEnd; ++LibAutoItr) {
+ LibraryLoad LL;
+ LL.LibName = *LibAutoItr;
+ LL.Position = LibrariesAuto.getPosition(LibAutoItr - LibrariesAuto.begin());
+ LL.CandidateExtensions = DynLibExtensionsOnly;
+ LL.Modifier = LibraryLoad::Auto;
+ LibraryLoadQueue.push_back(std::move(LL));
+ }
+
// Sort library loads by position in the argument list.
llvm::sort(LibraryLoadQueue,
[](const LibraryLoad &LHS, const LibraryLoad &RHS) {
@@ -2418,6 +2467,7 @@ static Error addLibraries(Session &S,
S.HiddenArchives.insert(Path);
break;
case LibraryLoad::Weak:
+ case LibraryLoad::Auto:
llvm_unreachable("Unsupported");
break;
}
@@ -2489,6 +2539,10 @@ static Error addLibraries(Session &S,
"Can't use -weak-lx or -weak_library to load JITDylib " +
LL.LibName,
inconvertibleErrorCode());
+ if (LL.Modifier == LibraryLoad::Auto)
+ return make_error<StringError>("Can't use -auto-lx to load JITDylib " +
+ LL.LibName,
+ inconvertibleErrorCode());
JD.addToLinkOrder(*LJD);
continue;
}
@@ -2559,6 +2613,9 @@ static Error addLibraries(Session &S,
JD.addGenerator(std::move(*G));
else
return G.takeError();
+ } else if (LL.Modifier == LibraryLoad::Auto) {
+ if (auto Err = S.loadAndLinkAutoImportDLL(JD, LibPath.data()))
+ return Err;
} else {
if (auto Err = S.loadAndLinkDynamicLibrary(JD, LibPath.data()))
return Err;
diff --git a/llvm/tools/llvm-jitlink/llvm-jitlink.h b/llvm/tools/llvm-jitlink/llvm-jitlink.h
index 1be4df684b9b3..3619929a7d0d4 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.h
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.h
@@ -133,6 +133,9 @@ struct Session {
Expected<orc::JITDylib *> getOrLoadDynamicLibrary(StringRef LibPath);
Error loadAndLinkDynamicLibrary(orc::JITDylib &JD, StringRef LibPath);
+ Expected<orc::JITDylib *> getOrLoadAutoImportDLL(StringRef LibPath);
+ Error loadAndLinkAutoImportDLL(orc::JITDylib &JD, StringRef LibPath);
+
orc::ObjectLayer &getLinkLayer(bool Lazy) {
assert((!Lazy || LazyLinking) &&
"Lazy linking requested but not available");
@@ -155,6 +158,7 @@ struct Session {
Twine ErrorMsgStem);
DynLibJDMap DynLibJDs;
+ DynLibJDMap AutoImportJDs;
std::mutex M;
std::condition_variable ActiveLinksCV;
diff --git a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
index 82744eafd3e33..aa4beffe30b9c 100644
--- a/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
+++ b/llvm/unittests/ExecutionEngine/Orc/CMakeLists.txt
@@ -18,6 +18,7 @@ set(LLVM_LINK_COMPONENTS
)
add_llvm_unittest(OrcJITTests
+ COFFAutoImportGeneratorTest.cpp
CallableTraitsHelperTest.cpp
CallSPSViaEPCTest.cpp
CoreAPIsTest.cpp
diff --git a/llvm/unittests/ExecutionEngine/Orc/COFFAutoImportGeneratorTest.cpp b/llvm/unittests/ExecutionEngine/Orc/COFFAutoImportGeneratorTest.cpp
new file mode 100644
index 0000000000000..be5ed872cf365
--- /dev/null
+++ b/llvm/unittests/ExecutionEngine/Orc/COFFAutoImportGeneratorTest.cpp
@@ -0,0 +1,178 @@
+//===- COFFAutoImportGeneratorTest.cpp - COFFAutoImportGenerator tests ---===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/Orc/COFFAutoImportGenerator.h"
+#include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"
+#include "llvm/ExecutionEngine/Orc/Core.h"
+#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
+#include "llvm/ExecutionEngine/Orc/SelfExecutorProcessControl.h"
+#include "llvm/Support/DynamicLibrary.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+#include "OrcTestCommon.h"
+
+// COFFAutoImportGenerator itself builds the __imp_ pointer slot and the
+// jump-thunk (it defines them in its own stub graph) and resolves the
+// imported address through the executor's DylibManager (in-process:
+// GetProcAddress/dlsym). These tests cover the generator in isolation: its
+// slot+thunk synthesis, its export-table authority, and its ResourceTracker
+// lifecycle.
+//
+// The tests execute the generator's thunk in-process, so an x86_64 host is
+// required (the thunk is native code we call); they import a no-argument,
+// integer-returning function the host exports -- kernel32!GetCurrentProcessId
+// on Windows, libc getpid on POSIX. Such functions are ABI-safe to call
+// regardless of the stub graph's calling convention: no argument registers to
+// mismatch, and the result comes back in EAX under both the Win64 and SysV
+// ABIs.
+#if defined(__x86_64__) || defined(_M_X64)
+
+#if defined(_WIN32)
+// Declared here (rather than via <windows.h>) to avoid the Windows macro soup
+// in an LLVM unit test. kernel32 is auto-linked, so these resolve at link time.
+extern "C" unsigned long GetCurrentProcessId(void);
+extern "C" unsigned long GetCurrentThreadId(void);
+#define AIG_IMPORT_LIB "kernel32.dll"
+#define AIG_SYM1 "GetCurrentProcessId"
+#define AIG_SYM2 "GetCurrentThreadId"
+#else
+#include <unistd.h>
+#define AIG_IMPORT_LIB nullptr // resolve against the current process (libc)
+#define AIG_SYM1 "getpid"
+#define AIG_SYM2 "getppid"
+#endif
+
+using namespace llvm;
+using namespace llvm::orc;
+
+namespace {
+
+// Call the real AIG_SYM1 directly, to compare against the thunk's result. Both
+// the POSIX and Windows choices return a 32-bit value in EAX, so we read the
+// thunk as a 32-bit-returning function.
+static unsigned callRealSym1() {
+#if defined(_WIN32)
+ return static_cast<unsigned>(GetCurrentProcessId());
+#else
+ return static_cast<unsigned>(::getpid());
+#endif
+}
+
+class COFFAutoImportGeneratorTest : public testing::Test {
+public:
+ ~COFFAutoImportGeneratorTest() override {
+ if (auto Err = ES.endSession())
+ ES.reportError(std::move(Err));
+ }
+
+protected:
+ // Use SelfExecutorProcessControl so the generator has a real (in-process)
+ // DylibManager to load the library and resolve its exports through. The
+ // architecture (x86_64) is the only real constraint on synthesis; the
+ // synthesized slot+thunk are linked and executed in-process below.
+ ExecutionSession ES{cantFail(SelfExecutorProcessControl::Create())};
+ std::unique_ptr<DylibManager> DylibMgr{
+ cantFail(ES.getExecutorProcessControl().createDefaultDylibMgr())};
+ JITDylib &JD = ES.createBareJITDylib("main");
+ ObjectLinkingLayer ObjLinkingLayer{
+ ES, std::make_unique<jitlink::InProcessMemoryManager>(4096)};
+
+ // The address the generator itself will resolve a name to, fetched through
+ // the very same loader path (DynamicLibrary::getAddressOfSymbol on the same
+ // library) so the slot contents can be compared exactly.
+ void *realAddr(const char *Name) {
+ std::string Err;
+ auto Lib = sys::DynamicLibrary::getPermanentLibrary(AIG_IMPORT_LIB, &Err);
+ EXPECT_TRUE(Lib.isValid()) << Err;
+ return Lib.getAddressOfSymbol(Name);
+ }
+};
+
+// For a symbol the library exports, the generator must synthesize both an
+// __imp_X IAT slot holding X's real address and an X thunk that jumps through
+// it -- and both must be usable. This covers the dllimport (__imp_-mediated)
+// path and the direct-call path in one shot.
+TEST_F(COFFAutoImportGeneratorTest, SynthesizesImpSlotAndThunk) {
+ void *RealSym1 = realAddr(AIG_SYM1);
+ ASSERT_NE(RealSym1, nullptr);
+
+ auto AIGOrErr = COFFAutoImportGenerator::Load(ES, ObjLinkingLayer, *DylibMgr,
+ AIG_IMPORT_LIB);
+ ASSERT_THAT_EXPECTED(AIGOrErr, Succeeded());
+ JD.addGenerator(std::move(*AIGOrErr));
+
+ // The __imp_ slot holds the symbol's real address in the library.
+ auto ImpSym = ES.lookup(&JD, "__imp_" AIG_SYM1);
+ ASSERT_THAT_EXPECTED(ImpSym, Succeeded());
+ void **Slot = ImpSym->getAddress().toPtr<void **>();
+ EXPECT_EQ(*Slot, RealSym1);
+
+ // The thunk is a distinct, synthesized definition (so &X yields the thunk,
+ // not the implementation in the library) ...
+ auto ThunkSym = ES.lookup(&JD, AIG_SYM1);
+ ASSERT_THAT_EXPECTED(ThunkSym, Succeeded());
+ EXPECT_NE(ThunkSym->getAddress(), ImpSym->getAddress());
+ EXPECT_NE(ThunkSym->getAddress().toPtr<void *>(), RealSym1);
+
+ // ... and calling it jumps through the slot to the real implementation.
+ auto Thunk = ThunkSym->getAddress().toPtr<unsigned (*)()>();
+ EXPECT_EQ(Thunk(), callRealSym1());
+}
+
+// The library's export table is the authority: a name it does not export must
+// be left unresolved, so the link fails exactly as a static link would.
+TEST_F(COFFAutoImportGeneratorTest, UnexportedSymbolFailsToLink) {
+ // The failed lookup surfaces as an Expected error; swallow any asynchronous
+ // report so it does not pollute the test log.
+ ES.setErrorReporter(consumeError);
+
+ auto AIGOrErr = COFFAutoImportGenerator::Load(ES, ObjLinkingLayer, *DylibMgr,
+ AIG_IMPORT_LIB);
+ ASSERT_THAT_EXPECTED(AIGOrErr, Succeeded());
+ JD.addGenerator(std::move(*AIGOrErr));
+
+ EXPECT_THAT_EXPECTED(
+ ES.lookup(&JD, "__imp_this_symbol_is_definitely_not_exported_zzz"),
+ Failed());
+}
+
+// All synthesized stubs are owned by a single, generator-managed
+// ResourceTracker that the client can reclaim in one step; a subsequent import
+// transparently starts a fresh tracker.
+TEST_F(COFFAutoImportGeneratorTest, StubsResourceTrackerLifecycle) {
+ auto AIGOrErr = COFFAutoImportGenerator::Load(ES, ObjLinkingLayer, *DylibMgr,
+ AIG_IMPORT_LIB);
+ ASSERT_THAT_EXPECTED(AIGOrErr, Succeeded());
+ COFFAutoImportGenerator &AIG = **AIGOrErr;
+ JD.addGenerator(std::move(*AIGOrErr));
+
+ // No stubs synthesized yet.
+ EXPECT_EQ(AIG.getImportStubsResourceTracker(), nullptr);
+
+ ASSERT_THAT_EXPECTED(ES.lookup(&JD, "__imp_" AIG_SYM1), Succeeded());
+ ResourceTrackerSP RT1 = AIG.getImportStubsResourceTracker();
+ ASSERT_NE(RT1, nullptr);
+ EXPECT_FALSE(RT1->isDefunct());
+
+ // Reclaim every synthesized slot and thunk in one step, without tearing down
+ // the JITDylib.
+ EXPECT_THAT_ERROR(RT1->remove(), Succeeded());
+ EXPECT_TRUE(RT1->isDefunct());
+
+ // A later import transparently starts a fresh tracker.
+ ASSERT_THAT_EXPECTED(ES.lookup(&JD, "__imp_" AIG_SYM2), Succeeded());
+ ResourceTrackerSP RT2 = AIG.getImportStubsResourceTracker();
+ ASSERT_NE(RT2, nullptr);
+ EXPECT_NE(RT2, RT1);
+ EXPECT_FALSE(RT2->isDefunct());
+}
+
+} // namespace
+
+#endif // x86_64
diff --git a/llvm/utils/gn/secondary/llvm/lib/ExecutionEngine/Orc/BUILD.gn b/llvm/utils/gn/secondary/llvm/lib/ExecutionEngine/Orc/BUILD.gn
index 915c81bfd7049..c7dd6be414af9 100644
--- a/llvm/utils/gn/secondary/llvm/lib/ExecutionEngine/Orc/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/lib/ExecutionEngine/Orc/BUILD.gn
@@ -18,6 +18,7 @@ static_library("Orc") {
"AbsoluteSymbols.cpp",
"BacktraceTools.cpp",
"COFF.cpp",
+ "COFFAutoImportGenerator.cpp",
"COFFPlatform.cpp",
"COFFVCRuntimeSupport.cpp",
"CompileOnDemandLayer.cpp",
diff --git a/llvm/utils/gn/secondary/llvm/unittests/ExecutionEngine/Orc/BUILD.gn b/llvm/utils/gn/secondary/llvm/unittests/ExecutionEngine/Orc/BUILD.gn
index 007cf6b0ea4cf..e465f3173a899 100644
--- a/llvm/utils/gn/secondary/llvm/unittests/ExecutionEngine/Orc/BUILD.gn
+++ b/llvm/utils/gn/secondary/llvm/unittests/ExecutionEngine/Orc/BUILD.gn
@@ -15,6 +15,7 @@ unittest("OrcJITTests") {
"//llvm/lib/Testing/Support",
]
sources = [
+ "COFFAutoImportGeneratorTest.cpp",
"CallSPSViaEPCTest.cpp",
"CallableTraitsHelperTest.cpp",
"CoreAPIsTest.cpp",
More information about the llvm-commits
mailing list