[llvm] [ORC] Add AutoImportGenerator for COFF dllimport auto-import (PR #203914)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jun 15 07:17:49 PDT 2026


https://github.com/mkovacevic99 created https://github.com/llvm/llvm-project/pull/203914

On Windows/COFF, a dllimport call is emitted as an indirect call through an `__imp_` IAT slot (`callq *__imp_bar(%rip)`), and even a direct call to a library function is expected to bind to a thunk supplied by an import library. Today a JIT client must produce those import libraries themselves. `AutoImportGenerator` synthesizes them on demand instead.

Bound to a single dynamic library via  `AutoImportGenerator::Load(ES, ObjLinkingLayer, "/path/to/lib.dll")`.
For each referenced export `X`, lazily synthesizes an `__imp_X` pointer slot holding `X`'s address in the library plus an `X` thunk that jumps through it, so both `__imp_`-mediated and direct references resolve.


The library's export table is the authority: a name the library does not export is left unresolved, so the link fails exactly as a static link against the corresponding import library would (no silent invention of symbols).
All synthesized stubs are owned by a single `ResourceTracker` (`getImportStubsResourceTracker()`), so a client can reclaim every synthesized slot/thunk in one step; subsequent imports start a fresh tracker.

Relationship to `DLLImportDefinitionGenerator`: that generator resolves the underlying symbol through the JITDylib's link order. `AutoImportGenerator` is bound to one specific library and treats its export table as authoritative, giving fail-as-static-linker semantics.

x86_64 and in-process execution only. - "Easy mode": every import is assumed to be a function; code and data are not distinguished, so data imports are unsupported (clients with data imports must supply an import library or use `__declspec(dllimport)`). `&X` resolves to the synthesized thunk, not the implementation in the target library.
`llvm-jitlink -auto-import=<lib>` flag to attach the generator (in-process; errors if combined with out-of-process execution).
Documentation in `llvm/docs/ORCv2.rst`.

Partly implements github issue: https://github.com/llvm/llvm-project/issues/190122
In the comment section of the github issue there is this comment https://github.com/llvm/llvm-project/issues/190122#issuecomment-4617328036
This PR implements point 3 ("Easy mode" generator)

>From 104c5b24fcfdc425523a9a82438b638e437f413d 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 AutoImportGenerator 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. x86_64/in-process only. Adds an llvm-jitlink -auto-import flag
    and ORCv2.rst docs. #190122
---
 llvm/docs/ORCv2.rst                           |  36 ++++
 .../llvm/ExecutionEngine/Orc/ExecutionUtils.h |  69 +++++++
 .../ExecutionEngine/Orc/ExecutionUtils.cpp    |  97 ++++++++++
 llvm/tools/llvm-jitlink/llvm-jitlink.cpp      |  31 ++++
 .../Orc/AutoImportGeneratorTest.cpp           | 172 ++++++++++++++++++
 5 files changed, 405 insertions(+)
 create mode 100644 llvm/unittests/ExecutionEngine/Orc/AutoImportGeneratorTest.cpp

diff --git a/llvm/docs/ORCv2.rst b/llvm/docs/ORCv2.rst
index 333977a0aaa66..f03d7ca3ef486 100644
--- a/llvm/docs/ORCv2.rst
+++ b/llvm/docs/ORCv2.rst
@@ -877,6 +877,42 @@ example, to expose an allowed set of symbols from the main process:
     // and contained in the list.
     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 ``AutoImportGenerator`` 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 =
+            AutoImportGenerator::Load(ES, ObjLinkingLayer, "/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. ``AutoImportGenerator`` currently supports the x86_64 architecture and
+in-process execution only. 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/ExecutionUtils.h b/llvm/include/llvm/ExecutionEngine/Orc/ExecutionUtils.h
index e42c902568a07..151ed7a5e18ea 100644
--- a/llvm/include/llvm/ExecutionEngine/Orc/ExecutionUtils.h
+++ b/llvm/include/llvm/ExecutionEngine/Orc/ExecutionUtils.h
@@ -383,6 +383,75 @@ class LLVM_ABI DLLImportDefinitionGenerator : public DefinitionGenerator {
   ObjectLinkingLayer &L;
 };
 
+/// 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 are owned by a single ResourceTracker (shared across
+/// the JITDylib, as-if-static). A client can reclaim every synthesized slot and
+/// thunk in one step via getImportStubsResourceTracker()->remove(), without
+/// tearing down the JITDylib; subsequent imports transparently start a fresh
+/// tracker.
+///
+/// Currently only supports the x86_64 architecture and in-process execution.
+class LLVM_ABI AutoImportGenerator : public DefinitionGenerator {
+public:
+  /// Permanently loads the dynamic library at the given path and, on success,
+  /// returns an AutoImportGenerator that synthesizes imports for the symbols it
+  /// exports. On failure returns the reason the library failed to load.
+  static Expected<std::unique_ptr<AutoImportGenerator>>
+  Load(ExecutionSession &ES, ObjectLinkingLayer &L, 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:
+  AutoImportGenerator(ExecutionSession &ES, ObjectLinkingLayer &L,
+                      sys::DynamicLibrary Dylib)
+      : ES(ES), L(L), Dylib(std::move(Dylib)) {}
+
+  Expected<std::unique_ptr<jitlink::LinkGraph>>
+  createStubsGraph(const SymbolMap &Resolved);
+
+  static StringRef getImpPrefix() { return "__imp_"; }
+  static StringRef getSectionName() { return "$__AUTOIMPORT_STUBS"; }
+
+  ExecutionSession &ES;
+  ObjectLinkingLayer &L;
+  sys::DynamicLibrary Dylib;
+
+  /// Owns every stub graph this generator synthesizes (shared across the
+  /// JITDylib, as-if-static) so the client can reclaim them in one step.
+  /// Created lazily on first synthesis and recreated if the client removes it.
+  ResourceTrackerSP ImportStubsRT;
+};
+
 } // end namespace orc
 } // end namespace llvm
 
diff --git a/llvm/lib/ExecutionEngine/Orc/ExecutionUtils.cpp b/llvm/lib/ExecutionEngine/Orc/ExecutionUtils.cpp
index 6794e7621f0ca..f77f2a35d2c4d 100644
--- a/llvm/lib/ExecutionEngine/Orc/ExecutionUtils.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/ExecutionUtils.cpp
@@ -583,5 +583,102 @@ DLLImportDefinitionGenerator::createStubsGraph(const SymbolMap &Resolved) {
   return std::move(G);
 }
 
+Expected<std::unique_ptr<AutoImportGenerator>>
+AutoImportGenerator::Load(ExecutionSession &ES, ObjectLinkingLayer &L,
+                          const char *LibraryPath) {
+  // x86_64-only for now: createStubsGraph emits x86_64 pointer slots / stubs.
+  if (ES.getTargetTriple().getArch() != Triple::x86_64)
+    return make_error<StringError>(
+        "AutoImportGenerator currently only supports x86_64",
+        inconvertibleErrorCode());
+
+  // getPermanentLibrary maps the library into this process; its exported
+  // symbols become resolvable via getAddressOfSymbol (GetProcAddress/dlsym),
+  // which only ever returns exported symbols -- so the library's export table
+  // is the authority on what may be synthesized.
+  std::string ErrMsg;
+  auto Dylib = sys::DynamicLibrary::getPermanentLibrary(LibraryPath, &ErrMsg);
+  if (!Dylib.isValid())
+    return make_error<StringError>(std::move(ErrMsg), inconvertibleErrorCode());
+
+  return std::unique_ptr<AutoImportGenerator>(
+      new AutoImportGenerator(ES, L, std::move(Dylib)));
+}
+
+Error AutoImportGenerator::tryToGenerate(LookupState &LS, LookupKind K,
+                                         JITDylib &JD,
+                                         JITDylibLookupFlags JDLookupFlags,
+                                         const SymbolLookupSet &Symbols) {
+  // The library's export table is the authority on what may be synthesized:
+  // query each requested symbol (with any __imp_ prefix stripped) and only
+  // generate stubs for those the library actually exports. Anything else is
+  // left unresolved, so the link fails as a static link would.
+  SymbolMap Resolved;
+  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 (Resolved.count(BaseName))
+      continue; // __imp_X and X collapse to a single entry.
+
+    // getAddressOfSymbol is GetProcAddress/dlsym, so it only resolves exported
+    // symbols -- i.e. it is naturally bounded by the export table.
+    std::string Tmp = Base.str();
+    if (void *P = Dylib.getAddressOfSymbol(Tmp.c_str()))
+      Resolved[BaseName] = {ExecutorAddr::fromPtr(P),
+                            JITSymbolFlags::Exported | JITSymbolFlags::Callable};
+  }
+
+  if (Resolved.empty())
+    return Error::success();
+
+  auto G = createStubsGraph(Resolved);
+  if (!G)
+    return G.takeError();
+
+  // Add the synthesized stubs under a generator-owned ResourceTracker (created
+  // lazily, recreated if the client has reclaimed it) so that all synthesized
+  // __imp_ slots and thunks can be released in one step via
+  // getImportStubsResourceTracker()->remove(), without tearing down JD.
+  if (!ImportStubsRT || ImportStubsRT->isDefunct())
+    ImportStubsRT = JD.createResourceTracker();
+  return L.add(ImportStubsRT, std::move(*G));
+}
+
+Expected<std::unique_ptr<jitlink::LinkGraph>>
+AutoImportGenerator::createStubsGraph(const SymbolMap &Resolved) {
+  auto G = std::make_unique<jitlink::LinkGraph>(
+      "<AUTOIMPORT_STUBS>", ES.getSymbolStringPool(), ES.getTargetTriple(),
+      SubtargetFeatures(), jitlink::getGenericEdgeKindName);
+  jitlink::Section &Sec =
+      G->createSection(getSectionName(), MemProt::Read | MemProt::Exec);
+
+  for (auto &KV : Resolved) {
+    // The real implementation in the library, frozen as an absolute symbol.
+    // It is referenced only by the __imp_ slot's pointer edge, so it is local
+    // and never collides with the same-named thunk below.
+    jitlink::Symbol &Target = G->addAbsoluteSymbol(
+        *KV.first, KV.second.getAddress(), G->getPointerSize(),
+        jitlink::Linkage::Strong, jitlink::Scope::Local, false);
+
+    // __imp_X: an 8-byte pointer slot holding X's address in the library.
+    jitlink::Symbol &Ptr =
+        jitlink::x86_64::createAnonymousPointer(*G, Sec, &Target);
+    Ptr.setName(G->intern((Twine(getImpPrefix()) + *KV.first).str()));
+    Ptr.setLinkage(jitlink::Linkage::Strong);
+    Ptr.setScope(jitlink::Scope::Default);
+
+    // X: a thunk "jmpq *__imp_X(%rip)" so direct calls to X also work.
+    jitlink::Block &StubBlock =
+        jitlink::x86_64::createPointerJumpStubBlock(*G, Sec, Ptr);
+    G->addDefinedSymbol(StubBlock, 0, *KV.first, StubBlock.getSize(),
+                        jitlink::Linkage::Strong, jitlink::Scope::Default,
+                        /*IsCallable=*/true, /*IsLive=*/false);
+  }
+
+  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..2b280cae2a558 100644
--- a/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
+++ b/llvm/tools/llvm-jitlink/llvm-jitlink.cpp
@@ -199,6 +199,12 @@ static cl::list<std::string>
                     "required by the ORC runtime)"),
            cl::cat(JITLinkCategory));
 
+static cl::list<std::string> AutoImportDylibs(
+    "auto-import",
+    cl::desc("Synthesize COFF __imp_ slots and thunks for symbols exported by "
+             "the given dynamic library (in-process; x86_64/COFF only)"),
+    cl::cat(JITLinkCategory));
+
 static cl::list<std::string> InputArgv("args", cl::Positional,
                                        cl::desc("<program arguments>..."),
                                        cl::PositionalEatsArgs,
@@ -1976,6 +1982,27 @@ static void addPhonyExternalsGenerator(Session &S) {
   S.MainJD->addGenerator(std::make_unique<PhonyExternalsGenerator>());
 }
 
+static Error addAutoImportGenerators(Session &S) {
+  if (AutoImportDylibs.empty())
+    return Error::success();
+
+  // AutoImportGenerator resolves symbols in-process (via getPermanentLibrary),
+  // so it is incompatible with out-of-process execution.
+  if (OutOfProcessExecutor.getNumOccurrences() ||
+      OutOfProcessExecutorConnect.getNumOccurrences())
+    return make_error<StringError>(
+        "-" + AutoImportDylibs.ArgStr + " requires in-process execution",
+        inconvertibleErrorCode());
+
+  for (const auto &LibPath : AutoImportDylibs) {
+    auto G = orc::AutoImportGenerator::Load(S.ES, *S.ObjLayer, LibPath.c_str());
+    if (!G)
+      return G.takeError();
+    S.MainJD->addGenerator(std::move(*G));
+  }
+  return Error::success();
+}
+
 static Error createJITDylibs(Session &S,
                              std::map<unsigned, JITDylib *> &IdxToJD) {
   // First, set up JITDylibs.
@@ -3026,6 +3053,10 @@ int main(int argc, char *argv[]) {
     ExitOnErr(addSessionInputs(*S));
   }
 
+  // Attach auto-import generators before the phony-externals catch-all so that
+  // real library symbols win over phony definitions.
+  ExitOnErr(addAutoImportGenerators(*S));
+
   if (PhonyExternals)
     addPhonyExternalsGenerator(*S);
 
diff --git a/llvm/unittests/ExecutionEngine/Orc/AutoImportGeneratorTest.cpp b/llvm/unittests/ExecutionEngine/Orc/AutoImportGeneratorTest.cpp
new file mode 100644
index 0000000000000..8f56217b95bbf
--- /dev/null
+++ b/llvm/unittests/ExecutionEngine/Orc/AutoImportGeneratorTest.cpp
@@ -0,0 +1,172 @@
+//===- AutoImportGeneratorTest.cpp - AutoImportGenerator unit tests -------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"
+#include "llvm/ExecutionEngine/Orc/Core.h"
+#include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
+#include "llvm/ExecutionEngine/Orc/ObjectLinkingLayer.h"
+#include "llvm/Support/DynamicLibrary.h"
+#include "llvm/TargetParser/Host.h"
+#include "llvm/Testing/Support/Error.h"
+#include "gtest/gtest.h"
+
+#include "OrcTestCommon.h"
+
+// AutoImportGenerator 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 host's dynamic loader (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 AutoImportGeneratorTest : public testing::Test {
+public:
+  ~AutoImportGeneratorTest() override {
+    if (auto Err = ES.endSession())
+      ES.reportError(std::move(Err));
+  }
+
+protected:
+  // AutoImportGenerator::Load only constrains the architecture (x86_64); the
+  // OS/object-format is irrelevant to the slot+thunk it synthesizes, so we link
+  // the synthetic stub graph with the host's own triple for a robust in-process
+  // link-and-execute.
+  ExecutionSession ES{std::make_unique<UnsupportedExecutorProcessControl>(
+      nullptr, nullptr, sys::getProcessTriple())};
+  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(AutoImportGeneratorTest, SynthesizesImpSlotAndThunk) {
+  void *RealSym1 = realAddr(AIG_SYM1);
+  ASSERT_NE(RealSym1, nullptr);
+
+  auto AIGOrErr = AutoImportGenerator::Load(ES, ObjLinkingLayer, 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(AutoImportGeneratorTest, 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 = AutoImportGenerator::Load(ES, ObjLinkingLayer, 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(AutoImportGeneratorTest, StubsResourceTrackerLifecycle) {
+  auto AIGOrErr = AutoImportGenerator::Load(ES, ObjLinkingLayer, AIG_IMPORT_LIB);
+  ASSERT_THAT_EXPECTED(AIGOrErr, Succeeded());
+  AutoImportGenerator &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



More information about the llvm-commits mailing list