[llvm] [orc-rt] Add Windows dynamic library support (PR #225076)

Jared Wyles via llvm-commits llvm-commits at lists.llvm.org
Mon Sep 21 05:55:32 PDT 2026


https://github.com/jaredwy created https://github.com/llvm/llvm-project/pull/225076

Dylib windows implementation. 

 LoadLibraryA and FreeLibrary for library lifetime management.
 GetProcAddress for symbol lookup in individual libraries.

We use a sentinel value to emulate global lookup (address of a local linkage variable). 
Uses the same reverse ordering as orc-v1. 

>From 93b5c56d69517eb9e0226b5a584d11da005e6542 Mon Sep 17 00:00:00 2001
From: Jared Wyles <jared.wyles at gmail.com>
Date: Fri, 18 Sep 2026 14:59:24 +1000
Subject: [PATCH 1/6] [orc-rt] Implement Windows target triple detection

---
 orc-rt/lib/bedrock/sys/windows/TargetTriple.cpp   |  8 +++++++-
 orc-rt/test/unit/bedrock/sys/TargetTripleTest.cpp | 11 +++++++----
 2 files changed, 14 insertions(+), 5 deletions(-)

diff --git a/orc-rt/lib/bedrock/sys/windows/TargetTriple.cpp b/orc-rt/lib/bedrock/sys/windows/TargetTriple.cpp
index 465a24afc82f8a..7a5db97b98f7b1 100644
--- a/orc-rt/lib/bedrock/sys/windows/TargetTriple.cpp
+++ b/orc-rt/lib/bedrock/sys/windows/TargetTriple.cpp
@@ -10,6 +10,12 @@
 
 namespace orc_rt::sys {
 
-std::string detectTargetTriple() noexcept { return {}; }
+std::string detectTargetTriple() noexcept {
+#if defined(_M_X64)
+  return "x86_64-pc-windows-msvc";
+#else
+#error "Unsupported Windows architecture"
+#endif
+}
 
 } // namespace orc_rt::sys
diff --git a/orc-rt/test/unit/bedrock/sys/TargetTripleTest.cpp b/orc-rt/test/unit/bedrock/sys/TargetTripleTest.cpp
index 65675d8db58d60..ad1a230dabe780 100644
--- a/orc-rt/test/unit/bedrock/sys/TargetTripleTest.cpp
+++ b/orc-rt/test/unit/bedrock/sys/TargetTripleTest.cpp
@@ -16,7 +16,7 @@
 #include <algorithm>
 
 using namespace orc_rt;
-#ifndef _WIN32
+
 TEST(TargetTripleTest, NotEmpty) {
   EXPECT_FALSE(sys::detectTargetTriple().empty());
 }
@@ -46,13 +46,16 @@ TEST(TargetTripleTest, ArchMatchesCompileTarget) {
 
 TEST(TargetTripleTest, OSMatchesCompileTarget) {
   auto Triple = sys::detectTargetTriple();
+
 #if defined(__APPLE__)
-  EXPECT_NE(Triple.find("-apple-"), std::string::npos);
+    EXPECT_NE(Triple.find("-apple-"), std::string::npos);
 #elif defined(__linux__)
-  EXPECT_NE(Triple.find("-linux-"), std::string::npos);
+      EXPECT_NE(Triple.find("-linux-"), std::string::npos);
+#elif defined(_WIN32)
+      EXPECT_NE(Triple.find("-windows-"), std::string::npos);
 #endif
 }
-#endif
+
 TEST(TargetTripleTest, CachedResultIsIdempotent) {
   EXPECT_EQ(sys::detectTargetTriple(), sys::detectTargetTriple());
 }

>From 6e28b9b91bba48fdaf6c6c8273d0b9db8e182ef1 Mon Sep 17 00:00:00 2001
From: Jared Wyles <jared.wyles at gmail.com>
Date: Fri, 18 Sep 2026 15:10:04 +1000
Subject: [PATCH 2/6] [orc-rt] Implement Windows CPU feature detection

---
 .../lib/bedrock/sys/windows/CPUFeatures.cpp   | 55 ++++++++++++++++++-
 .../test/unit/bedrock/sys/CPUFeaturesTest.cpp | 13 +++++
 2 files changed, 67 insertions(+), 1 deletion(-)

diff --git a/orc-rt/lib/bedrock/sys/windows/CPUFeatures.cpp b/orc-rt/lib/bedrock/sys/windows/CPUFeatures.cpp
index 8cb9bfa7d7b145..9928a978c53a2f 100644
--- a/orc-rt/lib/bedrock/sys/windows/CPUFeatures.cpp
+++ b/orc-rt/lib/bedrock/sys/windows/CPUFeatures.cpp
@@ -1,7 +1,60 @@
 #include "orc-rt-internal/bedrock/sys/CPUFeatures.h"
 
+#include <intrin.h>
+#include <windows.h>
+
 namespace orc_rt::sys {
 
-std::vector<std::string_view> detectTargetCPUFeatures() { return {}; }
+std::vector<std::string_view> detectTargetCPUFeatures() {
+  std::vector<std::string_view> Features;
+
+  auto A = [&](bool Present, std::string_view Feature) {
+    if (Present)
+      Features.push_back(Feature);
+  };
+
+  int Regs[4] = {};
+  __cpuid(Regs, 0);
+
+  const unsigned MaxLeaf = static_cast<unsigned>(Regs[0]);
+  if (MaxLeaf < 1)
+    return Features;
+
+  __cpuidex(Regs, 1, 0);
+
+  const unsigned ECX = static_cast<unsigned>(Regs[2]);
+  const unsigned EDX = static_cast<unsigned>(Regs[3]);
+
+  A(EDX & (1u << 25), "sse");
+  A(EDX & (1u << 26), "sse2");
+
+  A(ECX & (1u << 0), "sse3");
+  A(ECX & (1u << 9), "ssse3");
+  A(ECX & (1u << 19), "sse4.1");
+  A(ECX & (1u << 20), "sse4.2");
+
+  const bool HasXSAVE = ECX & (1u << 26);
+  const bool HasOSXSAVE = ECX & (1u << 27);
+  const bool HasAVXHardware = ECX & (1u << 28);
+
+  uint64_t XCR0 = 0;
+  if (HasXSAVE && HasOSXSAVE)
+    XCR0 = _xgetbv(0);
+
+  const bool HasAVXState = (XCR0 & 0x6) == 0x6;
+  const bool HasAVX = HasAVXHardware && HasAVXState;
+
+  A(HasAVX, "avx");
+
+  if (MaxLeaf >= 7) {
+    __cpuidex(Regs, 7, 0);
+
+    const unsigned EBX = static_cast<unsigned>(Regs[1]);
+
+    A(HasAVX && (EBX & (1u << 5)), "avx2");
+  }
+
+  return Features;
+}
 
 } // namespace orc_rt::sys
diff --git a/orc-rt/test/unit/bedrock/sys/CPUFeaturesTest.cpp b/orc-rt/test/unit/bedrock/sys/CPUFeaturesTest.cpp
index a4cbffdf1da2a3..54304b5f857f05 100644
--- a/orc-rt/test/unit/bedrock/sys/CPUFeaturesTest.cpp
+++ b/orc-rt/test/unit/bedrock/sys/CPUFeaturesTest.cpp
@@ -25,3 +25,16 @@ TEST(CPUFeaturesTest, DetectDoesNotCrash) {
 TEST(CPUFeaturesTest, CachedResultIsIdempotent) {
   EXPECT_EQ(sys::detectTargetCPUFeatures(), sys::detectTargetCPUFeatures());
 }
+
+// A temporary test to tide us over until the next branch lands.
+// We we enable regression testing for EPI.
+#if defined(_WIN32) && defined(_M_X64)
+TEST(CPUFeaturesTest, DetectsBaselineX86Features) {
+  auto Features = sys::detectTargetCPUFeatures();
+
+  EXPECT_NE(std::find(Features.begin(), Features.end(), "sse"),
+            Features.end());
+  EXPECT_NE(std::find(Features.begin(), Features.end(), "sse2"),
+            Features.end());
+}
+#endif

>From 73107e2a4acdab6dcf04ba31ec669950739c7420 Mon Sep 17 00:00:00 2001
From: Jared Wyles <jared.wyles at gmail.com>
Date: Fri, 18 Sep 2026 15:29:23 +1000
Subject: [PATCH 3/6] [orc-rt] Add Windows page size detection

---
 orc-rt/lib/bedrock/CMakeLists.txt             |  1 +
 orc-rt/lib/bedrock/sys/windows/Memory.cpp     |  4 ----
 orc-rt/lib/bedrock/sys/windows/PageSize.cpp   | 21 +++++++++++++++++++
 .../{linux => }/check-rt-process-info.test    |  5 +++--
 .../darwin/check-rt-process-info.test         |  6 ------
 .../unit/bedrock/ExecutorProcessInfoTest.cpp  | 10 +--------
 6 files changed, 26 insertions(+), 21 deletions(-)
 create mode 100644 orc-rt/lib/bedrock/sys/windows/PageSize.cpp
 rename orc-rt/test/regression/{linux => }/check-rt-process-info.test (56%)
 delete mode 100644 orc-rt/test/regression/darwin/check-rt-process-info.test

diff --git a/orc-rt/lib/bedrock/CMakeLists.txt b/orc-rt/lib/bedrock/CMakeLists.txt
index 5dc584bca9659e..105723ebda82d1 100644
--- a/orc-rt/lib/bedrock/CMakeLists.txt
+++ b/orc-rt/lib/bedrock/CMakeLists.txt
@@ -58,6 +58,7 @@ set(ORC_RT_BEDROCK_WINDOWS_SOURCES
   sys/windows/DynamicLibrary.cpp
   sys/windows/Memory.cpp
   sys/windows/CPUFeatures.cpp
+  sys/windows/PageSize.cpp
   sys/windows/TargetTriple.cpp
 )
 
diff --git a/orc-rt/lib/bedrock/sys/windows/Memory.cpp b/orc-rt/lib/bedrock/sys/windows/Memory.cpp
index 8ae525d13c5a59..276fa1e292c232 100644
--- a/orc-rt/lib/bedrock/sys/windows/Memory.cpp
+++ b/orc-rt/lib/bedrock/sys/windows/Memory.cpp
@@ -10,10 +10,6 @@
 
 namespace orc_rt::sys {
 
-Expected<uint64_t> detectPageSize() {
-  return make_error<StringError>("Windows detectPageSize not implemented");
-}
-
 Expected<void *> reserveMemory(uint64_t) {
   return make_error<StringError>("Windows reserveMemory not implemented");
 }
diff --git a/orc-rt/lib/bedrock/sys/windows/PageSize.cpp b/orc-rt/lib/bedrock/sys/windows/PageSize.cpp
new file mode 100644
index 00000000000000..4a23b72a0195bf
--- /dev/null
+++ b/orc-rt/lib/bedrock/sys/windows/PageSize.cpp
@@ -0,0 +1,21 @@
+//===- PageSize.cpp -------------------------------------------------------===//
+//
+// 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 "orc-rt-internal/bedrock/sys/Memory.h"
+
+#include <windows.h>
+
+namespace orc_rt::sys {
+
+Expected<uint64_t> detectPageSize() {
+  SYSTEM_INFO SI;
+  GetSystemInfo(&SI);
+  return static_cast<uint64_t>(SI.dwPageSize);
+}
+
+} // namespace orc_rt::sys
diff --git a/orc-rt/test/regression/linux/check-rt-process-info.test b/orc-rt/test/regression/check-rt-process-info.test
similarity index 56%
rename from orc-rt/test/regression/linux/check-rt-process-info.test
rename to orc-rt/test/regression/check-rt-process-info.test
index 160be336d40cf2..a84d510ffcddba 100644
--- a/orc-rt/test/regression/linux/check-rt-process-info.test
+++ b/orc-rt/test/regression/check-rt-process-info.test
@@ -1,5 +1,6 @@
-# RUN: orc-rt-process-info-check --print-triple --print-page-size \
+# RUN: orc-rt-process-info-check --print-triple --print-page-size --print-cpu-features \
 # RUN:   | FileCheck %s -DARCH=%target-arch
 
-# CHECK: {{^}}[[ARCH]]-{{.*}}-linux
+# CHECK:      {{^}}[[ARCH]]-
 # CHECK-NEXT: {{4096|16384}}
+# CHECK-NEXT: {{^\+[a-z0-9_.]+(,\+[a-z0-9_.]+)*$}}
diff --git a/orc-rt/test/regression/darwin/check-rt-process-info.test b/orc-rt/test/regression/darwin/check-rt-process-info.test
deleted file mode 100644
index 8df2c09477c890..00000000000000
--- a/orc-rt/test/regression/darwin/check-rt-process-info.test
+++ /dev/null
@@ -1,6 +0,0 @@
-# RUN: orc-rt-process-info-check --print-triple --print-cpu-features --print-page-size  \
-# RUN:   | FileCheck %s -DARCH=%target-arch -DVERSION=%macos-product-version
-
-# CHECK: [[ARCH]]-apple-macosx[[VERSION]]
-# CHECK-NEXT: {{4096|16384}}
-# CHECK-NEXT: {{^\+[a-z0-9_.]+(,\+[a-z0-9_.]+)*$}}
diff --git a/orc-rt/test/unit/bedrock/ExecutorProcessInfoTest.cpp b/orc-rt/test/unit/bedrock/ExecutorProcessInfoTest.cpp
index 78b16ac2beaef3..a17fdedac7197c 100644
--- a/orc-rt/test/unit/bedrock/ExecutorProcessInfoTest.cpp
+++ b/orc-rt/test/unit/bedrock/ExecutorProcessInfoTest.cpp
@@ -19,7 +19,7 @@
 #endif
 
 using namespace orc_rt;
-#ifndef _WIN32
+
 TEST(ExecutorProcessInfoTest, DetectSucceeds) {
   auto EPI = ExecutorProcessInfo::Detect();
   EXPECT_TRUE(!!EPI);
@@ -36,14 +36,6 @@ TEST(ExecutorProcessInfoTest, DetectPageSizeAtLeast4096) {
   EXPECT_GE(EPI.pageSize(), 4096U);
 }
 
-TEST(ExecutorProcessInfoTest, DetectPageSizeMatchesSystem) {
-  auto EPI = ExecutorProcessInfo::Detect();
-  ASSERT_TRUE(EPI);
-
-  EXPECT_EQ(EPI->PageSize, static_cast<uint64_t>(sysconf(_SC_PAGESIZE)));
-}
-#endif
-
 TEST(ExecutorProcessInfoTest, ConstructWithExplicitValues) {
   ExecutorProcessInfo EPI("x86_64-unknown-linux-gnu", 4096, "+x,+a,+b");
   EXPECT_EQ(EPI.targetTriple(), "x86_64-unknown-linux-gnu");

>From 5cb47978cf52a9e2586b08ec0d6b6786c89a4098 Mon Sep 17 00:00:00 2001
From: Jared Wyles <jared.wyles at gmail.com>
Date: Mon, 21 Sep 2026 22:05:53 +1000
Subject: [PATCH 4/6] [orc-rt] Add Windows virtual memory support

---
 .../support/sys/WinErrorToORCError.h          | 43 +++++++++++++
 orc-rt/lib/bedrock/sys/windows/Memory.cpp     | 64 +++++++++++++++++--
 .../bedrock/SimpleNativeMemoryMapTest.cpp     |  2 -
 .../sps/SimpleNativeMemoryMapSPSCITest.cpp    |  3 +-
 4 files changed, 101 insertions(+), 11 deletions(-)
 create mode 100644 orc-rt/include/orc-rt-internal/support/sys/WinErrorToORCError.h

diff --git a/orc-rt/include/orc-rt-internal/support/sys/WinErrorToORCError.h b/orc-rt/include/orc-rt-internal/support/sys/WinErrorToORCError.h
new file mode 100644
index 00000000000000..3ebbf6d3dcb913
--- /dev/null
+++ b/orc-rt/include/orc-rt-internal/support/sys/WinErrorToORCError.h
@@ -0,0 +1,43 @@
+#ifndef ORC_RT_INTERNAL_SUPPORT_SYS_WINERRORTOORCERROR_H
+#define ORC_RT_INTERNAL_SUPPORT_SYS_WINERRORTOORCERROR_H
+
+#include "orc-rt/support/Error.h"
+
+#include <string>
+#include <string_view>
+
+#include <windows.h>
+
+namespace orc_rt::sys {
+
+inline std::string getWindowsErrorMessage(DWORD ErrorCode) {
+  LPSTR Message = nullptr;
+
+  const DWORD Length = FormatMessageA(
+      FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
+          FORMAT_MESSAGE_IGNORE_INSERTS,
+      nullptr, ErrorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
+      reinterpret_cast<LPSTR>(&Message), 0, nullptr);
+
+  if (!Length)
+    return "Windows error " + std::to_string(ErrorCode);
+
+  std::string Result(Message, Length);
+  LocalFree(Message);
+
+  while (!Result.empty() && (Result.back() == '\r' || Result.back() == '\n'))
+    Result.pop_back();
+
+  return Result;
+}
+
+inline Error generateErrorFromGetLastError(std::string_view Prefix) {
+  const DWORD ErrorCode = GetLastError();
+
+  return make_error<StringError>(std::string(Prefix) + ": " +
+                                 getWindowsErrorMessage(ErrorCode));
+}
+
+} // namespace orc_rt::sys
+
+#endif
diff --git a/orc-rt/lib/bedrock/sys/windows/Memory.cpp b/orc-rt/lib/bedrock/sys/windows/Memory.cpp
index 276fa1e292c232..6bfb060738370d 100644
--- a/orc-rt/lib/bedrock/sys/windows/Memory.cpp
+++ b/orc-rt/lib/bedrock/sys/windows/Memory.cpp
@@ -1,4 +1,4 @@
-//===- Memory.cpp ---------------------------------------------------------===//
+//===--- Memory.cpp - Windows memory operations ---------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -7,19 +7,69 @@
 //===----------------------------------------------------------------------===//
 
 #include "orc-rt-internal/bedrock/sys/Memory.h"
+#include "orc-rt-internal/support/sys/WinErrorToORCError.h"
+
+#include <windows.h>
 
 namespace orc_rt::sys {
 
-Expected<void *> reserveMemory(uint64_t) {
-  return make_error<StringError>("Windows reserveMemory not implemented");
+namespace {
+
+DWORD getWindowsProtection(MemProt Prot) {
+  const bool Read = (Prot & MemProt::Read) != MemProt::None;
+  const bool Write = (Prot & MemProt::Write) != MemProt::None;
+  const bool Exec = (Prot & MemProt::Exec) != MemProt::None;
+
+  if (Exec) {
+    if (Write)
+      return PAGE_EXECUTE_READWRITE;
+    if (Read)
+      return PAGE_EXECUTE_READ;
+    return PAGE_EXECUTE;
+  }
+
+  if (Write)
+    return PAGE_READWRITE;
+  if (Read)
+    return PAGE_READONLY;
+
+  return PAGE_NOACCESS;
+}
+
+} // namespace
+
+Expected<void *> reserveMemory(uint64_t Size) {
+  void *Addr = VirtualAlloc(nullptr, static_cast<SIZE_T>(Size),
+                            MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+
+  if (!Addr)
+    return generateErrorFromGetLastError("VirtualAlloc failed");
+
+  return Addr;
 }
 
-Error releaseMemory(void *, uint64_t) {
-  return make_error<StringError>("Windows releaseMemory not implemented");
+Error releaseMemory(void *Base, uint64_t) {
+  // When MEM_RELEASE is specified, dwSize must be zero and Base must be the
+  // address returned by VirtualAlloc.
+  if (!VirtualFree(Base, 0, MEM_RELEASE))
+    return generateErrorFromGetLastError("VirtualFree failed");
+
+  return Error::success();
 }
 
-Error protectMemory(void *, uint64_t, MemProt) {
-  return make_error<StringError>("Windows protectMemory not implemented");
+Error protectMemory(void *Base, uint64_t Size, MemProt Prot) {
+  DWORD OldProtect = 0;
+
+  if (!VirtualProtect(Base, static_cast<SIZE_T>(Size),
+                      getWindowsProtection(Prot), &OldProtect))
+    return generateErrorFromGetLastError("VirtualProtect failed");
+
+  if ((Prot & MemProt::Exec) != MemProt::None &&
+      !FlushInstructionCache(GetCurrentProcess(), Base,
+                             static_cast<SIZE_T>(Size)))
+    return generateErrorFromGetLastError("FlushInstructionCache failed");
+
+  return Error::success();
 }
 
 } // namespace orc_rt::sys
diff --git a/orc-rt/test/unit/bedrock/SimpleNativeMemoryMapTest.cpp b/orc-rt/test/unit/bedrock/SimpleNativeMemoryMapTest.cpp
index 5cdcb2fe1e6818..23d3cd7393d2bf 100644
--- a/orc-rt/test/unit/bedrock/SimpleNativeMemoryMapTest.cpp
+++ b/orc-rt/test/unit/bedrock/SimpleNativeMemoryMapTest.cpp
@@ -9,7 +9,6 @@
 // Test SimpleNativeMemoryMap APIs.
 //
 //===----------------------------------------------------------------------===//
-#ifndef _WIN32
 
 #include "orc-rt/bedrock/SimpleNativeMemoryMap.h"
 #include "orc-rt/bedrock/Session.h"
@@ -323,4 +322,3 @@ TEST(SimpleNativeMemoryMapTest, ReserveInitializeDetachShutdown) {
 
   EXPECT_EQ(SentinelValue, 42);
 }
-#endif
diff --git a/orc-rt/test/unit/bedrock/sps/SimpleNativeMemoryMapSPSCITest.cpp b/orc-rt/test/unit/bedrock/sps/SimpleNativeMemoryMapSPSCITest.cpp
index d3ad5bef7edefd..e455afb209574f 100644
--- a/orc-rt/test/unit/bedrock/sps/SimpleNativeMemoryMapSPSCITest.cpp
+++ b/orc-rt/test/unit/bedrock/sps/SimpleNativeMemoryMapSPSCITest.cpp
@@ -9,7 +9,7 @@
 // Tests for SimpleNativeMemoryMap's SPS Controller Interface.
 //
 //===----------------------------------------------------------------------===//
-#ifndef _WIN32
+
 #include "orc-rt/bedrock/sps/SimpleNativeMemoryMapSPSCI.h"
 #include "orc-rt/bedrock/Session.h"
 #include "orc-rt/bedrock/SimpleNativeMemoryMap.h"
@@ -321,4 +321,3 @@ TEST_F(SimpleNativeMemoryMapSPSCITest, ReserveInitializeDetachShutdown) {
 
   EXPECT_EQ(SentinelValue, 42);
 }
-#endif

>From f06811bb0b1a15b40a54bdfa5991e9ac46883602 Mon Sep 17 00:00:00 2001
From: Jared Wyles <jared.wyles at gmail.com>
Date: Mon, 21 Sep 2026 21:46:52 +1000
Subject: [PATCH 5/6] [orc-rt] Add Windows dynamic library support

---
 .../bedrock/sys/windows/DynamicLibrary.cpp    | 126 ++++++++++++++++--
 1 file changed, 118 insertions(+), 8 deletions(-)

diff --git a/orc-rt/lib/bedrock/sys/windows/DynamicLibrary.cpp b/orc-rt/lib/bedrock/sys/windows/DynamicLibrary.cpp
index c311fdc5006dca..961bae141d4ac0 100644
--- a/orc-rt/lib/bedrock/sys/windows/DynamicLibrary.cpp
+++ b/orc-rt/lib/bedrock/sys/windows/DynamicLibrary.cpp
@@ -1,4 +1,4 @@
-//===- DynamicLibrary.cpp -------------------------------------------------===//
+//===- DynamicLibrary.cpp - Windows dynamic library operations ------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -7,22 +7,132 @@
 //===----------------------------------------------------------------------===//
 
 #include "orc-rt-internal/bedrock/sys/DynamicLibrary.h"
+#include "orc-rt-internal/support/sys/WinErrorToORCError.h"
+
+#include <cassert>
+#include <cstdint>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <psapi.h>
 
 namespace orc_rt::sys {
 
-void *globalLookupHandle() { return nullptr; }
+namespace {
+
+// Window has no equivalent of RTLD_DEFAULT. nullptr means "current exe",
+// so to avoid confusiion use a sentinal address.
+// Keep the sentinel in an anonymous namespace so that it has internal linkage
+char GlobalLookupSentinel;
+bool isGlobalLookupHandle(void *Handle) {
+  return Handle == &GlobalLookupSentinel;
+}
+
+std::optional<void *> lookupSymbol(HMODULE Handle, const std::string &Name) {
+  if (auto Addr = GetProcAddress(Handle, Name.c_str()))
+    return reinterpret_cast<void *>(reinterpret_cast<uintptr_t>(Addr));
 
-Expected<void *> loadLibrary(const std::string &) {
-  return make_error<StringError>("Windows loadLibrary not implemented");
+  return std::nullopt;
 }
 
-Error unloadLibrary(void *) {
-  return make_error<StringError>("Windows unloadLibrary not implemented");
+bool getProcessModules(std::vector<HMODULE> &Modules) {
+  HANDLE Process = GetCurrentProcess();
+
+  DWORD BytesNeeded = 0;
+  if (!EnumProcessModulesEx(Process, nullptr, 0, &BytesNeeded,
+                            LIST_MODULES_64BIT))
+    return false;
+
+  for (;;) {
+    assert(BytesNeeded % sizeof(HMODULE) == 0);
+
+    Modules.resize(BytesNeeded / sizeof(HMODULE));
+
+    DWORD NewBytesNeeded = 0;
+    if (!EnumProcessModulesEx(
+            Process, Modules.data(),
+            static_cast<DWORD>(Modules.size() * sizeof(HMODULE)),
+            &NewBytesNeeded, LIST_MODULES_64BIT))
+      return false;
+
+    if (NewBytesNeeded <= Modules.size() * sizeof(HMODULE)) {
+      Modules.resize(NewBytesNeeded / sizeof(HMODULE));
+      return true;
+    }
+
+    // The module list changed between the size query and enumeration.
+    BytesNeeded = NewBytesNeeded;
+  }
+}
+
+} // namespace
+
+void *globalLookupHandle() { return &GlobalLookupSentinel; }
+
+Expected<void *> loadLibrary(const std::string &Path) {
+  assert(!Path.empty() && "loadLibrary doesn't support empty paths");
+
+  HMODULE Handle = LoadLibraryA(Path.c_str());
+  if (!Handle)
+    return generateErrorFromGetLastError("error loading \"" + Path + "\"");
+
+  return reinterpret_cast<void *>(Handle);
+}
+
+Error unloadLibrary(void *Handle) {
+  assert(Handle && "invalid library handle");
+  assert(!isGlobalLookupHandle(Handle) &&
+         "global lookup handle must not be unloaded");
+
+  if (!FreeLibrary(static_cast<HMODULE>(Handle)))
+    return generateErrorFromGetLastError("error unloading library");
+
+  return Error::success();
 }
 
 std::vector<std::optional<void *>>
-lookupLibrarySymbols(void *, const std::vector<std::string> &Names) {
-  return std::vector<std::optional<void *>>(Names.size(), std::nullopt);
+lookupLibrarySymbols(void *Handle, const std::vector<std::string> &Names) {
+  std::vector<std::optional<void *>> Result;
+  Result.reserve(Names.size());
+
+  if (isGlobalLookupHandle(Handle)) {
+    std::vector<HMODULE> Modules;
+
+    if (!getProcessModules(Modules)) {
+      Result.resize(Names.size(), std::nullopt);
+      return Result;
+    }
+
+    for (const auto &Name : Names) {
+      std::optional<void *> Addr;
+
+      // Search the executable first.
+      if (!Modules.empty())
+        Addr = lookupSymbol(Modules.front(), Name);
+
+      // Match LLVM's existing Windows DynamicLibrary behavior by searching
+      // loaded DLLs in reverse order.
+      if (!Addr && Modules.size() > 1) {
+        for (auto I = Modules.rbegin(), E = Modules.rend() - 1; I != E; ++I) {
+          Addr = lookupSymbol(*I, Name);
+          if (Addr)
+            break;
+        }
+      }
+
+      Result.push_back(Addr);
+    }
+
+    return Result;
+  }
+
+  assert(Handle && "invalid library handle");
+
+  HMODULE Library = static_cast<HMODULE>(Handle);
+  for (const auto &Name : Names)
+    Result.push_back(lookupSymbol(Library, Name));
+
+  return Result;
 }
 
 } // namespace orc_rt::sys

>From 948187c3b21eb61369e3a9f24968b99c1424ddf9 Mon Sep 17 00:00:00 2001
From: Jared Wyles <jared.wyles at gmail.com>
Date: Mon, 21 Sep 2026 22:51:12 +1000
Subject: [PATCH 6/6] [orc-rt] Add Windows dynamic library support

---
 .../lib/bedrock/sys/windows/DynamicLibrary.cpp  | 10 +++++-----
 orc-rt/test/unit/CMakeLists.txt                 |  6 ++++--
 .../unit/Inputs/NativeDylibManagerTestLib.cpp   |  6 +-----
 .../test/unit/Inputs/RTTICrossDylibTestLib.cpp  |  8 +++-----
 orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h | 17 ++++-------------
 orc-rt/test/unit/Inputs/TestVisibility.h        | 14 ++++++++++++++
 6 files changed, 31 insertions(+), 30 deletions(-)
 create mode 100644 orc-rt/test/unit/Inputs/TestVisibility.h

diff --git a/orc-rt/lib/bedrock/sys/windows/DynamicLibrary.cpp b/orc-rt/lib/bedrock/sys/windows/DynamicLibrary.cpp
index 961bae141d4ac0..835d9d65a0be5d 100644
--- a/orc-rt/lib/bedrock/sys/windows/DynamicLibrary.cpp
+++ b/orc-rt/lib/bedrock/sys/windows/DynamicLibrary.cpp
@@ -12,9 +12,8 @@
 #include <cassert>
 #include <cstdint>
 
-#define WIN32_LEAN_AND_MEAN
-#include <windows.h>
 #include <psapi.h>
+#include <windows.h>
 
 namespace orc_rt::sys {
 
@@ -68,13 +67,14 @@ bool getProcessModules(std::vector<HMODULE> &Modules) {
 } // namespace
 
 void *globalLookupHandle() { return &GlobalLookupSentinel; }
-
 Expected<void *> loadLibrary(const std::string &Path) {
   assert(!Path.empty() && "loadLibrary doesn't support empty paths");
 
   HMODULE Handle = LoadLibraryA(Path.c_str());
-  if (!Handle)
-    return generateErrorFromGetLastError("error loading \"" + Path + "\"");
+  if (!Handle) {
+    std::string Prefix = "error loading \"" + Path + "\"";
+    return generateErrorFromGetLastError(Prefix);
+  }
 
   return reinterpret_cast<void *>(Handle);
 }
diff --git a/orc-rt/test/unit/CMakeLists.txt b/orc-rt/test/unit/CMakeLists.txt
index 41132cc786d107..5685d21bd6efc6 100644
--- a/orc-rt/test/unit/CMakeLists.txt
+++ b/orc-rt/test/unit/CMakeLists.txt
@@ -137,6 +137,7 @@ add_library(NativeDylibManagerTestLib SHARED
   Inputs/NativeDylibManagerTestLib.cpp)
 set_target_properties(NativeDylibManagerTestLib PROPERTIES
   PREFIX ""
+  DEFINE_SYMBOL ORC_RT_TEST_DLL_EXPORTS
   FOLDER "orc-rt/Tests")
 target_compile_options(NativeDylibManagerTestLib PRIVATE
   ${ORC_RT_COMPILE_FLAGS})
@@ -156,10 +157,11 @@ set_target_properties(RTTICrossDylibTestLib PROPERTIES
   PREFIX ""
   CXX_VISIBILITY_PRESET hidden
   VISIBILITY_INLINES_HIDDEN ON
+  DEFINE_SYMBOL ORC_RT_TEST_DLL_EXPORTS
   FOLDER "orc-rt/Tests")
 
-target_compile_definitions(RTTICrossDylibTestLib PRIVATE
-  RTTI_CROSS_DYLIB_TEST_LIB_EXPORTS)
+set_target_properties(RTTICrossDylibTestLib PROPERTIES
+  DEFINE_SYMBOL ORC_RT_TEST_DLL_EXPORTS)
 
 target_compile_options(RTTICrossDylibTestLib PRIVATE ${ORC_RT_COMPILE_FLAGS})
 
diff --git a/orc-rt/test/unit/Inputs/NativeDylibManagerTestLib.cpp b/orc-rt/test/unit/Inputs/NativeDylibManagerTestLib.cpp
index 681bdb0b857504..5e750fa17e9025 100644
--- a/orc-rt/test/unit/Inputs/NativeDylibManagerTestLib.cpp
+++ b/orc-rt/test/unit/Inputs/NativeDylibManagerTestLib.cpp
@@ -1,10 +1,6 @@
 // A minimal shared library for NativeDylibManager tests.
 
-#if defined(_WIN32)
-#define TEST_EXPORT __declspec(dllexport)
-#else
-#define TEST_EXPORT __attribute__((visibility("default")))
-#endif
+#include "TestVisibility.h"
 
 extern "C" TEST_EXPORT int NativeDylibManagerTestFunc() { return 42; }
 extern "C" TEST_EXPORT int NativeDylibManagerTestFunc2() { return 7; }
diff --git a/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.cpp b/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.cpp
index e6c3c2eb912fbe..bae5eca45ff8d9 100644
--- a/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.cpp
+++ b/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.cpp
@@ -9,13 +9,11 @@
 using namespace orc_rt;
 using orc_rt_test::CrossDylibTestError;
 
-extern "C" RTTI_TEST_EXPORT ErrorInfoBase *
-rttiCrossDylibTest_makeError(int Code) {
+extern "C" TEST_EXPORT ErrorInfoBase *rttiCrossDylibTest_makeError(int Code) {
   return new CrossDylibTestError(Code);
 }
 
-extern "C" RTTI_TEST_EXPORT void
-rttiCrossDylibTest_destroyError(ErrorInfoBase *E) {
+extern "C" TEST_EXPORT void rttiCrossDylibTest_destroyError(ErrorInfoBase *E) {
   delete E;
 }
 
@@ -23,7 +21,7 @@ rttiCrossDylibTest_destroyError(ErrorInfoBase *E) {
 // confirm the two libraries are genuinely using distinct identities (i.e.
 // that the test below exercises the cross-library strcmp path, not the
 // same-library pointer-equality fast path).
-extern "C" RTTI_TEST_EXPORT const void *rttiCrossDylibTest_libraryID() {
+extern "C" TEST_EXPORT const void *rttiCrossDylibTest_libraryID() {
   CrossDylibTestError E(0);
   return E.libraryID();
 }
diff --git a/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h b/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h
index 5a3a990a632d78..2291cbd8a61953 100644
--- a/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h
+++ b/orc-rt/test/unit/Inputs/RTTICrossDylibTestLib.h
@@ -15,24 +15,15 @@
 #ifndef ORC_RT_TEST_RTTICROSSDYLIBTESTLIB_H
 #define ORC_RT_TEST_RTTICROSSDYLIBTESTLIB_H
 
+#include "TestVisibility.h"
 #include "orc-rt/support/Error.h"
 
-#if defined(_WIN32)
-#if defined(RTTI_CROSS_DYLIB_TEST_LIB_EXPORTS)
-#define RTTI_TEST_EXPORT __declspec(dllexport)
-#else
-#define RTTI_TEST_EXPORT __declspec(dllimport)
-#endif
-#else
-#define RTTI_TEST_EXPORT __attribute__((visibility("default")))
-#endif
-
-extern "C" RTTI_TEST_EXPORT orc_rt::ErrorInfoBase *
+extern "C" TEST_EXPORT orc_rt::ErrorInfoBase *
 rttiCrossDylibTest_makeError(int Code);
 
-extern "C" RTTI_TEST_EXPORT void
+extern "C" TEST_EXPORT void
 rttiCrossDylibTest_destroyError(orc_rt::ErrorInfoBase *E);
 
-extern "C" RTTI_TEST_EXPORT const void *rttiCrossDylibTest_libraryID();
+extern "C" TEST_EXPORT const void *rttiCrossDylibTest_libraryID();
 
 #endif // ORC_RT_TEST_RTTICROSSDYLIBTESTLIB_H
diff --git a/orc-rt/test/unit/Inputs/TestVisibility.h b/orc-rt/test/unit/Inputs/TestVisibility.h
new file mode 100644
index 00000000000000..f1d896e6ab18fd
--- /dev/null
+++ b/orc-rt/test/unit/Inputs/TestVisibility.h
@@ -0,0 +1,14 @@
+#ifndef ORC_RT_TEST_UNIT_INPUTS_TESTVISIBILITY_H
+#define ORC_RT_TEST_UNIT_INPUTS_TESTVISIBILITY_H
+
+#if defined(_WIN32)
+#if defined(ORC_RT_TEST_DLL_EXPORTS)
+#define TEST_EXPORT __declspec(dllexport)
+#else
+#define TEST_EXPORT __declspec(dllimport)
+#endif
+#else
+#define TEST_EXPORT __attribute__((visibility("default")))
+#endif
+
+#endif // ORC_RT_TEST_UNIT_INPUTS_TESTVISIBILITY_H



More information about the llvm-commits mailing list