[llvm] Cpu features cross platform (PR #205447)

Jared Wyles via llvm-commits llvm-commits at lists.llvm.org
Mon Aug 3 03:37:18 PDT 2026


https://github.com/jaredwy updated https://github.com/llvm/llvm-project/pull/205447

>From 80fff50c2772a59c0f04c62ca829f0545058c6f3 Mon Sep 17 00:00:00 2001
From: Jared Wyles <jared.wyles at gmail.com>
Date: Fri, 3 Apr 2026 18:50:42 +1100
Subject: [PATCH] [ORC-RT] adds cpu detection via syscalls for darwin

Also adds the place holders for other OS's as well as the structure for
cross platform code going forward.

Prefer sys calls as we can't be sure what runtime we may end up linking with
Some features can be controlled via the kernel so this makes it easier
than relying on __cpuid for now.

Adds a new process regression test as well, with a best effort
guess that the triple returned is correct.
---
 orc-rt/include/CMakeLists.txt                 |   2 +
 orc-rt/include/orc-rt/ExecutorProcessInfo.h   |  30 ++++-
 orc-rt/lib/executor/BootstrapInfo.cpp         |   2 +
 orc-rt/lib/executor/CMakeLists.txt            |  51 ++++++--
 orc-rt/lib/executor/ExecutorProcessInfo.cpp   |  62 +++++-----
 .../executor/InProcessControllerAccess.cpp    |   1 +
 orc-rt/lib/executor/darwin/CPUFeatures.cpp    |  60 ++++++++++
 orc-rt/lib/executor/darwin/TargetTriple.cpp   | 107 +++++++++++++++++
 orc-rt/lib/executor/linux/CPUFeatures.cpp     |   9 ++
 orc-rt/lib/executor/linux/TargetTriple.cpp    |  14 +++
 orc-rt/lib/executor/windows/CPUFeatures.cpp   |   9 ++
 orc-rt/lib/executor/windows/TargetTriple.cpp  |  15 +++
 orc-rt/test/CMakeLists.txt                    |   2 +-
 .../regression/check-rt-processi-info.test    |   8 ++
 orc-rt/test/regression/lit.cfg.py             |   6 +-
 orc-rt/test/tools/CMakeLists.txt              |   5 +
 .../test/tools/orc-rt-process-info-check.cpp  |  62 ++++++++++
 orc-rt/test/unit/BootstrapInfoTest.cpp        |   8 ++
 orc-rt/test/unit/CMakeLists.txt               |   5 +-
 orc-rt/test/unit/CommonTestUtils.h            |   3 +-
 orc-rt/test/unit/ExecutorProcessInfoTest.cpp  | 113 +++++++++++++++++-
 .../unit/InProcessControllerAccessTest.cpp    |  39 +++++-
 22 files changed, 559 insertions(+), 54 deletions(-)
 create mode 100644 orc-rt/lib/executor/darwin/CPUFeatures.cpp
 create mode 100644 orc-rt/lib/executor/darwin/TargetTriple.cpp
 create mode 100644 orc-rt/lib/executor/linux/CPUFeatures.cpp
 create mode 100644 orc-rt/lib/executor/linux/TargetTriple.cpp
 create mode 100644 orc-rt/lib/executor/windows/CPUFeatures.cpp
 create mode 100644 orc-rt/lib/executor/windows/TargetTriple.cpp
 create mode 100644 orc-rt/test/regression/check-rt-processi-info.test
 create mode 100644 orc-rt/test/tools/orc-rt-process-info-check.cpp

diff --git a/orc-rt/include/CMakeLists.txt b/orc-rt/include/CMakeLists.txt
index 9663f4f3291c4..9d047e2dc8365 100644
--- a/orc-rt/include/CMakeLists.txt
+++ b/orc-rt/include/CMakeLists.txt
@@ -32,6 +32,8 @@ set(ORC_RT_HEADERS
     orc-rt/SimplePackedSerialization.h
     orc-rt/SimpleSymbolTable.h
     orc-rt/StandaloneMachOUnwindInfoRegistrar.h
+    orc-rt/StringExtras.h
+    orc-rt/TargetDetails.h
     orc-rt/TaskGroup.h
     orc-rt/ThreadPoolRunner.h
     orc-rt/WrapperFunction.h
diff --git a/orc-rt/include/orc-rt/ExecutorProcessInfo.h b/orc-rt/include/orc-rt/ExecutorProcessInfo.h
index dec427a20e3fc..be7c450d41eec 100644
--- a/orc-rt/include/orc-rt/ExecutorProcessInfo.h
+++ b/orc-rt/include/orc-rt/ExecutorProcessInfo.h
@@ -15,7 +15,11 @@
 #define ORC_RT_EXECUTORPROCESSINFO_H
 
 #include "orc-rt/Error.h"
+
+#include <initializer_list>
 #include <string>
+#include <string_view>
+#include <vector>
 
 namespace orc_rt {
 
@@ -24,23 +28,47 @@ namespace orc_rt {
 class ExecutorProcessInfo {
 public:
   /// Create an ExecutorProcessInfo from the given values.
-  ExecutorProcessInfo(std::string Triple, size_t PageSize) noexcept;
+  ExecutorProcessInfo(std::string Triple, size_t PageSize,
+                      std::string CPUFeatures) noexcept;
 
   /// Create an ExecutorProcessInfo, auto-detecting values.
   static Expected<ExecutorProcessInfo> Detect() noexcept;
 
+  /// Returns a string that is usable in SubtargetFeatures for the host process.
+  const std::string &targetCPUFeatures() const noexcept { return CPUFeatures; }
+
   /// Returns a target triple string for the host process.
   const std::string &targetTriple() const noexcept { return Triple; }
 
   /// Returns the host process's page size.
   size_t pageSize() const noexcept { return PageSize; }
 
+  /// This will return a string that can be forwarded to SubtargetFeatures
+  /// It will only return "+" turning off features, is left to caller.
+  /// This calls syscalls so result is cached
+  static std::string detectCPUFeatures() noexcept;
+  /// This calls syscalls so result is cached
   static std::string detectTargetTriple() noexcept;
+
   static Expected<size_t> detectPageSize() noexcept;
 
 private:
+  friend struct ExecutorProcessInfoTestAccess;
+
+  // Storage of string_views is static, so will last the lifetime of the runtime
+  static std::vector<std::string_view> detectTargetCPUFeatures();
+
+  /// Formats vector of feature names as an SubtargetFeatures valid string, e.g.
+  /// "+avx,+avx2".
+  static std::string
+  formatCPUFeatures(const std::vector<std::string_view> &Features);
+
+  static std::string
+  makeTargetTriple(std::initializer_list<std::string_view> Components);
+
   std::string Triple;
   size_t PageSize;
+  std::string CPUFeatures;
 };
 
 } // namespace orc_rt
diff --git a/orc-rt/lib/executor/BootstrapInfo.cpp b/orc-rt/lib/executor/BootstrapInfo.cpp
index a06f0b482df54..5e80e43eee026 100644
--- a/orc-rt/lib/executor/BootstrapInfo.cpp
+++ b/orc-rt/lib/executor/BootstrapInfo.cpp
@@ -38,6 +38,8 @@ BootstrapInfo::CreateDefault(Session &S,
       return std::move(Err);
 
   ValueMap InitialValues;
+  InitialValues["orc-rt.Executor.SubtargetFeatures"] =
+      S.processInfo().targetCPUFeatures();
   if (AddInitialValues)
     if (auto Err = AddInitialValues(InitialValues))
       return std::move(Err);
diff --git a/orc-rt/lib/executor/CMakeLists.txt b/orc-rt/lib/executor/CMakeLists.txt
index 77ebb8c00e914..dc2e114432588 100644
--- a/orc-rt/lib/executor/CMakeLists.txt
+++ b/orc-rt/lib/executor/CMakeLists.txt
@@ -1,4 +1,5 @@
-set(files
+
+set(ORC_RT_SOURCES
   BootstrapInfo.cpp
   Environment.cpp
   Error.cpp
@@ -21,22 +22,56 @@ set(files
   sps-ci/StandaloneMachOUnwindInfoRegistrarSPSCI.cpp
   )
 
+
 # The printf logging backend needs a runtime implementation; the none backend
 # is header-only and the os_log backend is not yet implemented.
 if(ORC_RT_LOG_BACKEND STREQUAL "printf")
-  list(APPEND files Logging_printf.cpp)
+  list(APPEND ORC_RT_SOURCES Logging_printf.cpp)
 elseif(ORC_RT_LOG_BACKEND STREQUAL "os_log")
-  list(APPEND files Logging_oslog.cpp)
+  list(APPEND ORC_RT_SOURCES Logging_oslog.cpp)
 endif()
 
-add_library(orc-rt-executor STATIC ${files})
-target_link_libraries(orc-rt-executor
-  PUBLIC orc-rt-headers
-  )
+set(ORC_RT_DARWIN_SOURCES
+  darwin/CPUFeatures.cpp
+  darwin/TargetTriple.cpp
+)
+
+set(ORC_RT_LINUX_SOURCES
+  linux/CPUFeatures.cpp
+  linux/TargetTriple.cpp
+)
+
+set(ORC_RT_WINDOWS_SOURCES
+  windows/CPUFeatures.cpp
+  windows/TargetTriple.cpp
+)
+
+if (APPLE)
+    list(APPEND ORC_RT_SOURCES ${ORC_RT_DARWIN_SOURCES})
+elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux")
+    list(APPEND ORC_RT_SOURCES ${ORC_RT_LINUX_SOURCES})
+  elseif (WIN32)
+    list(APPEND ORC_RT_SOURCES ${ORC_RT_WINDOWS_SOURCES})
+  else()
+    message(WARNING
+      "orc-rt: no platform implementation for ${CMAKE_SYSTEM_NAME}; "
+      "Add an orc-rt/lib/executor/<platform>/ directory")
+endif()
+
+add_library(orc-rt-executor STATIC ${ORC_RT_SOURCES})
+
+target_link_libraries(orc-rt-executor PUBLIC orc-rt-headers)
+
+add_library(orc-rt-executor-impl-headers INTERFACE)
+# Add it via lib so that to include impl details its "executor/"
+# to prevent collisions down the line
+target_include_directories(orc-rt-executor-impl-headers INTERFACE
+  $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/lib>
+)
 
 # Apply RTTI and exceptions compile flags
-# TODO: Use common runtimes infrastructure for output and install paths
 target_compile_options(orc-rt-executor PRIVATE ${ORC_RT_COMPILE_FLAGS})
+# TODO: Use common runtimes infrastructure for output and install paths
 install(TARGETS orc-rt-executor
   ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
           COMPONENT OrcRT_Development
diff --git a/orc-rt/lib/executor/ExecutorProcessInfo.cpp b/orc-rt/lib/executor/ExecutorProcessInfo.cpp
index 51d58ca1ef061..b5a5f72ce0e04 100644
--- a/orc-rt/lib/executor/ExecutorProcessInfo.cpp
+++ b/orc-rt/lib/executor/ExecutorProcessInfo.cpp
@@ -13,6 +13,7 @@
 
 #include "orc-rt/ExecutorProcessInfo.h"
 #include "orc-rt/Math.h"
+#include "orc-rt/StringExtras.h"
 
 #include <cassert>
 #include <cstring>
@@ -20,51 +21,54 @@
 
 namespace orc_rt {
 
-ExecutorProcessInfo::ExecutorProcessInfo(std::string Triple,
-                                         size_t PageSize) noexcept
-    : Triple(std::move(Triple)), PageSize(PageSize) {
+ExecutorProcessInfo::ExecutorProcessInfo(std::string Triple, size_t PageSize,
+                                         std::string CPUFeatures) noexcept
+    : Triple(std::move(Triple)), PageSize(PageSize),
+      CPUFeatures(std::move(CPUFeatures)) {
   assert(!this->Triple.empty() && "triple cannot be empty");
   assert(isPowerOf2(this->PageSize) && "page-size is not a power of two");
 }
 
 /// Create an ExecutorProcessInfo, auto-detecting property values.
 Expected<ExecutorProcessInfo> ExecutorProcessInfo::Detect() noexcept {
+  auto CPUFeatures = detectCPUFeatures();
   auto Triple = detectTargetTriple();
   auto PageSize = detectPageSize();
   if (!PageSize)
     return PageSize.takeError();
-  return ExecutorProcessInfo(std::move(Triple), std::move(*PageSize));
+  return ExecutorProcessInfo(std::move(Triple), std::move(*PageSize),
+                             std::move(CPUFeatures));
 }
 
-std::string ExecutorProcessInfo::detectTargetTriple() noexcept {
-  std::string Triple;
+std::string ExecutorProcessInfo::formatCPUFeatures(
+    const std::vector<std::string_view> &Features) {
+  if (Features.empty())
+    return {};
 
-// Arch
-#if defined(__x86_64__) || defined(_M_X64)
-  Triple += "x86_64";
-#elif defined(__aarch64__) || defined(_M_ARM64)
-  Triple += "aarch64";
-#else
-#error "Unsupported architecture"
-#endif
+  // Every feature is emitted with a '+' prefix, so the separator carries the
+  // prefix for all but the first.
+  return "+" + join(Features, ",+");
+}
+
+std::string ExecutorProcessInfo::detectCPUFeatures() noexcept {
+  // Detection involves system calls, so cache the result. Function-local
+  // static initialization is thread safe.
+  static const std::string Cache = formatCPUFeatures(detectTargetCPUFeatures());
+  return Cache;
+}
 
-  // Vendor
-#if defined(__APPLE__)
-  Triple += "-apple";
-#else
-  Triple += "-unknown";
-#endif
+std::string ExecutorProcessInfo::makeTargetTriple(
+    std::initializer_list<std::string_view> Components) {
+  const std::string_view *First = Components.begin();
+  const std::string_view *Last = Components.end();
 
-  // OS
-#if defined(__APPLE__)
-  Triple += "-darwin";
-#elif defined(__linux__)
-  Triple += "-linux";
-#else
-#error "Unsupported OS"
-#endif
+  // Trailing empty components have to be dropped
+  // Empty parts in the middle results in "--"
+  // this matches llvm behaviour
+  while (Last != First && (Last - 1)->empty())
+    --Last;
 
-  return Triple;
+  return join(First, Last, "-");
 }
 
 Expected<size_t> ExecutorProcessInfo::detectPageSize() noexcept {
diff --git a/orc-rt/lib/executor/InProcessControllerAccess.cpp b/orc-rt/lib/executor/InProcessControllerAccess.cpp
index a4a39f10ca4f3..99f35ec2bccb8 100644
--- a/orc-rt/lib/executor/InProcessControllerAccess.cpp
+++ b/orc-rt/lib/executor/InProcessControllerAccess.cpp
@@ -114,6 +114,7 @@ struct InProcessControllerAccess::BootstrapInfoAccessImpl
   }
 
 private:
+
   uint64_t getPageSize() const noexcept { return BI.processInfo().pageSize(); }
 
   static uint64_t getPageSizeEntry(void *BIA) noexcept {
diff --git a/orc-rt/lib/executor/darwin/CPUFeatures.cpp b/orc-rt/lib/executor/darwin/CPUFeatures.cpp
new file mode 100644
index 0000000000000..5373cd4255f9b
--- /dev/null
+++ b/orc-rt/lib/executor/darwin/CPUFeatures.cpp
@@ -0,0 +1,60 @@
+//===- CPUFeaturesDarwin.cpp - Darwin CPU feature detection ---------------===//
+//
+// 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/ExecutorProcessInfo.h"
+
+#include "../TargetDetails.h"
+
+#include <cstdint>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+
+namespace orc_rt {
+
+namespace {
+
+/// Reads an integer hw.optional.* flag. Absent flags are reported as false,
+bool sysctlFlag(const char *Name) noexcept {
+  int32_t V = 0;
+  size_t S = sizeof(V);
+  return sysctlbyname(Name, &V, &S, nullptr, 0) == 0 && V != 0;
+}
+
+} // namespace
+std::vector<std::string_view> ExecutorProcessInfo::detectTargetCPUFeatures() {
+  using namespace orc_rt::target_detail;
+  std::vector<std::string_view> Features;
+
+#if defined(__x86_64__) || defined(__i386__)
+  // The hw.optional flags already account for kernel support of the extended
+  // register state, so no OSXSAVE / XCR0 check is required here unlike linux
+  // this is why we diverge from using the compiler intrinsics here.
+  if (sysctlFlag("hw.optional.sse4_1"))
+    Features.push_back(feature::x86::sse4_1);
+  if (sysctlFlag("hw.optional.sse4_2"))
+    Features.push_back(feature::x86::sse4_2);
+  if (sysctlFlag("hw.optional.avx1_0"))
+    Features.push_back(feature::x86::avx);
+  if (sysctlFlag("hw.optional.avx2_0"))
+    Features.push_back(feature::x86::avx2);
+
+#elif defined(__arm64__) || defined(__aarch64__)
+  // NEON is mandatory on all Apple AArch64 hardware.
+  Features.push_back(feature::aarch64::neon);
+
+  if (sysctlFlag("hw.optional.arm.FEAT_DotProd"))
+    Features.push_back(feature::aarch64::dotprod);
+  if (sysctlFlag("hw.optional.arm.FEAT_FP16"))
+    Features.push_back(feature::aarch64::fullfp16);
+  if (sysctlFlag("hw.optional.arm.FEAT_SHA3"))
+    Features.push_back(feature::aarch64::sha3);
+#endif
+
+  return Features;
+}
+
+} // namespace orc_rt
diff --git a/orc-rt/lib/executor/darwin/TargetTriple.cpp b/orc-rt/lib/executor/darwin/TargetTriple.cpp
new file mode 100644
index 0000000000000..f14812c4b95de
--- /dev/null
+++ b/orc-rt/lib/executor/darwin/TargetTriple.cpp
@@ -0,0 +1,107 @@
+//===- TargetTriple.cpp - Darwin target triple detection ------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Target triple detection on Darwin.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/ExecutorProcessInfo.h"
+#include "../TargetDetails.h"
+
+#include <TargetConditionals.h>
+#include <cstdlib>
+#include <cstring>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+
+namespace orc_rt {
+
+namespace {
+// FIXME: jared - Add in error handling rather than an empty string.
+std::string sysctlString(const char *Name) noexcept {
+  size_t S = 0;
+  if (sysctlbyname(Name, nullptr, &S, nullptr, 0) != 0)
+    return {};
+  if (S == 0)
+    return {};
+
+  std::string V(S - 1, '\0');
+  if (sysctlbyname(Name, V.data(), &S, nullptr, 0) != 0)
+    return {};
+
+  return V;
+}
+
+} // namespace
+
+std::string ExecutorProcessInfo::detectTargetTriple() noexcept {
+  // Detection may involve system calls, so cache the result.
+  static const std::string Cache = [] {
+    using namespace target_detail;
+
+#if defined(__arm64e__)
+    constexpr std::string_view Arch = arch::arm64e;
+#elif defined(__arm64__) || defined(__aarch64__)
+    constexpr std::string_view Arch = arch::arm64;
+#elif defined(__x86_64h__)
+    constexpr std::string_view Arch = arch::x86_64h;
+#elif defined(__x86_64__)
+    constexpr std::string_view Arch = arch::x86_64;
+#elif defined(__i386__)
+    constexpr std::string_view Arch = arch::i386;
+#else
+#error "Unsupported architecture"
+#endif
+
+    // As I have learned, order is important here.
+    // TARGET_OS_MACCATALYST needs to come first.
+#if TARGET_OS_MACCATALYST
+    constexpr std::string_view Platform = "ios";
+#elif TARGET_OS_VISION
+    constexpr std::string_view Platform = "xros";
+#elif TARGET_OS_TV
+    constexpr std::string_view Platform = "tvos";
+#elif TARGET_OS_WATCH
+    constexpr std::string_view Platform = "watchos";
+#elif TARGET_OS_IOS
+    constexpr std::string_view Platform = "ios";
+#elif TARGET_OS_OSX
+    constexpr std::string_view Platform = "macosx";
+#else
+#error "Unsupported Darwin platform"
+#endif
+
+#if TARGET_OS_MACCATALYST
+    constexpr std::string_view Environment = "macabi";
+#elif TARGET_OS_SIMULATOR
+    constexpr std::string_view Environment = "simulator";
+#else
+    constexpr std::string_view Environment = "";
+#endif
+
+#if TARGET_OS_MACCATALYST
+    constexpr const char *VersionSysctl = "kern.iossupportversion";
+#else
+    constexpr const char *VersionSysctl = "kern.osproductversion";
+#endif
+
+    std::string Version;
+#if TARGET_OS_SIMULATOR
+    if (const char *SimVersion = std::getenv("SIMULATOR_RUNTIME_VERSION"))
+      Version = SimVersion;
+#endif
+    if (Version.empty())
+      Version = sysctlString(VersionSysctl);
+
+    return makeTargetTriple(
+        {Arch, vendor::apple, std::string(Platform) + Version, Environment});
+  }();
+  return Cache;
+}
+
+} // namespace orc_rt
diff --git a/orc-rt/lib/executor/linux/CPUFeatures.cpp b/orc-rt/lib/executor/linux/CPUFeatures.cpp
new file mode 100644
index 0000000000000..20be7e6516825
--- /dev/null
+++ b/orc-rt/lib/executor/linux/CPUFeatures.cpp
@@ -0,0 +1,9 @@
+#include "orc-rt/ExecutorProcessInfo.h"
+
+namespace orc_rt {
+
+std::vector<std::string_view> ExecutorProcessInfo::detectTargetCPUFeatures() {
+  return {};
+}
+
+} // namespace orc_rt
diff --git a/orc-rt/lib/executor/linux/TargetTriple.cpp b/orc-rt/lib/executor/linux/TargetTriple.cpp
new file mode 100644
index 0000000000000..f4c3c32faf3f8
--- /dev/null
+++ b/orc-rt/lib/executor/linux/TargetTriple.cpp
@@ -0,0 +1,14 @@
+//===- TargetTriple.cpp - Linux target triple detection -------------------===//
+//
+// 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/ExecutorProcessInfo.h"
+
+namespace orc_rt {
+
+std::string ExecutorProcessInfo::detectTargetTriple() noexcept { return {}; }
+
+} // namespace orc_rt
diff --git a/orc-rt/lib/executor/windows/CPUFeatures.cpp b/orc-rt/lib/executor/windows/CPUFeatures.cpp
new file mode 100644
index 0000000000000..20be7e6516825
--- /dev/null
+++ b/orc-rt/lib/executor/windows/CPUFeatures.cpp
@@ -0,0 +1,9 @@
+#include "orc-rt/ExecutorProcessInfo.h"
+
+namespace orc_rt {
+
+std::vector<std::string_view> ExecutorProcessInfo::detectTargetCPUFeatures() {
+  return {};
+}
+
+} // namespace orc_rt
diff --git a/orc-rt/lib/executor/windows/TargetTriple.cpp b/orc-rt/lib/executor/windows/TargetTriple.cpp
new file mode 100644
index 0000000000000..0462f6fafbe67
--- /dev/null
+++ b/orc-rt/lib/executor/windows/TargetTriple.cpp
@@ -0,0 +1,15 @@
+//===- TargetTriple.cpp - Windows target triple detection -----------------===//
+//
+// 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/ExecutorProcessInfo.h"
+
+namespace orc_rt {
+
+std::string ExecutorProcessInfo::detectTargetTriple() noexcept { return {}; }
+
+} // namespace orc_rt
diff --git a/orc-rt/test/CMakeLists.txt b/orc-rt/test/CMakeLists.txt
index cfe268c4acc80..a65cf39213781 100644
--- a/orc-rt/test/CMakeLists.txt
+++ b/orc-rt/test/CMakeLists.txt
@@ -14,6 +14,7 @@ if (ORC_RT_LLVM_TOOLS_AVAILABLE)
   list(APPEND ORC_RT_TEST_DEPS
     ogre
     orc-rt-log-check
+    orc-rt-process-info-check
     orc-rt-smoke-check
   )
 
@@ -46,4 +47,3 @@ add_lit_testsuite(check-orc-rt-unit "Running orc-rt unittest suites"
   ${CMAKE_CURRENT_BINARY_DIR}/unit
   EXCLUDE_FROM_CHECK_ALL
   DEPENDS OrcRTUnitTests)
-
diff --git a/orc-rt/test/regression/check-rt-processi-info.test b/orc-rt/test/regression/check-rt-processi-info.test
new file mode 100644
index 0000000000000..e421536169380
--- /dev/null
+++ b/orc-rt/test/regression/check-rt-processi-info.test
@@ -0,0 +1,8 @@
+# REQUIRES: system-darwin
+#
+# RUN: orc-rt-process-info-check --print-triple --print-cpu-features --print-page-size  \
+# RUN:   | FileCheck %s -DVERSION=%macos-product-version
+
+# CHECK: {{arm64e?}}-apple-macosx[[VERSION]]
+# CHECK-NEXT: {{4096|16384}}
+# CHECK-NEXT: {{(\+[a-z0-9_.]+,)*\+neon(,\+[a-z0-9_.]+)*}}
diff --git a/orc-rt/test/regression/lit.cfg.py b/orc-rt/test/regression/lit.cfg.py
index f3b006f4834e9..fa99beabb1fb1 100644
--- a/orc-rt/test/regression/lit.cfg.py
+++ b/orc-rt/test/regression/lit.cfg.py
@@ -8,6 +8,7 @@
 
 from lit.llvm import llvm_config
 from lit.llvm.subst import ToolSubst
+import platform
 
 config.name = "ORC-RT"
 config.test_format = lit.formats.ShTest()
@@ -60,7 +61,6 @@ def add_logging_features():
     for level in levels.split():
         config.available_features.add("orc-rt-log-level-" + level.lower())
 
-
 add_logging_features()
 
 # The os_log delivery tests scrape the unified log (via `log show`), which is
@@ -86,3 +86,7 @@ def add_logging_features():
 # inherited from the developer's shell. Tests opt in with `env ORC_RT_LOG=...`.
 for var in ("ORC_RT_LOG", "ORC_RT_LOG_OUTPUT"):
     config.environment.pop(var, None)
+
+if platform.system() == "Darwin":
+    config.substitutions.append(("%macos-product-version", platform.mac_ver()[0]))
+config.substitutions.append(("%target_triple", config.target_triple))
diff --git a/orc-rt/test/tools/CMakeLists.txt b/orc-rt/test/tools/CMakeLists.txt
index a150ac18e2731..f5af23650fed2 100644
--- a/orc-rt/test/tools/CMakeLists.txt
+++ b/orc-rt/test/tools/CMakeLists.txt
@@ -8,3 +8,8 @@ add_executable(orc-rt-smoke-check orc-rt-smoke-check.cpp)
 add_executable(orc-rt-log-check orc-rt-log-check.cpp)
 target_compile_options(orc-rt-log-check PRIVATE ${ORC_RT_COMPILE_FLAGS})
 target_link_libraries(orc-rt-log-check PRIVATE orc-rt-executor)
+
+
+add_executable(orc-rt-process-info-check orc-rt-process-info-check.cpp)
+target_compile_options(orc-rt-process-info-check PRIVATE ${ORC_RT_COMPILE_FLAGS})
+target_link_libraries(orc-rt-process-info-check PRIVATE orc-rt-executor)
diff --git a/orc-rt/test/tools/orc-rt-process-info-check.cpp b/orc-rt/test/tools/orc-rt-process-info-check.cpp
new file mode 100644
index 0000000000000..3c9d86abbaed5
--- /dev/null
+++ b/orc-rt/test/tools/orc-rt-process-info-check.cpp
@@ -0,0 +1,62 @@
+//===- orc-rt-process-info-check.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-utils/CommandLine.h"
+#include "orc-rt/ExecutorProcessInfo.h"
+#include <iostream>
+
+int main(int argc, char *argv[]) {
+
+  bool PrintTriple = false;
+  bool PrintPageSize = false;
+  bool PrintCPUFeatures = false;
+  bool PrintHelp = false;
+
+  {
+    orc_rt::CommandLineParser P;
+    P.addFlag("print-triple", "Print the detected target triple", false,
+              PrintTriple)
+        .addFlag("print-page-size", "Print the detected page size", false,
+                 PrintPageSize)
+        .addFlag("print-cpu-features",
+                 "Print the detected LLVM target-feature string", false,
+                 PrintCPUFeatures)
+        .addFlag("help", "Print help", false, PrintHelp);
+
+    if (auto Err = P.parse(argc, argv)) {
+      std::cerr << "error: " << orc_rt::toString(std::move(Err)) << "\n";
+      P.printHelp(std::cerr, argv[0]);
+      return 1;
+    }
+
+    if (PrintHelp) {
+      P.printHelp(std::cerr, argv[0]);
+      return 0;
+    }
+  }
+
+  auto EPI = orc_rt::ExecutorProcessInfo::Detect();
+  if (!EPI) {
+    std::cerr << "error: " << orc_rt::toString(EPI.takeError()) << "\n";
+    return 1;
+  }
+
+  if (PrintTriple)
+    std::cout << EPI->targetTriple() << "\n";
+
+  if (PrintPageSize)
+    std::cout << EPI->pageSize() << "\n";
+
+  if (PrintCPUFeatures)
+    std::cout << EPI->targetCPUFeatures() << "\n";
+
+  if (PrintTriple || PrintPageSize || PrintCPUFeatures)
+    return 0;
+
+  return 1;
+}
diff --git a/orc-rt/test/unit/BootstrapInfoTest.cpp b/orc-rt/test/unit/BootstrapInfoTest.cpp
index 9d409c7c0c5ff..0ec3ee4b69094 100644
--- a/orc-rt/test/unit/BootstrapInfoTest.cpp
+++ b/orc-rt/test/unit/BootstrapInfoTest.cpp
@@ -127,3 +127,11 @@ TEST(BootstrapInfoTest, MutableSymbolsAndValues) {
   EXPECT_EQ(BI.symbols().size(), 1U);
   EXPECT_EQ(BI.values().size(), 1U);
 }
+
+TEST(BootstrapInfoTest, CreateDefaultContainsSubtargetFeatures) {
+  Session S(mockExecutorProcessInfo(), noDispatch, noErrors);
+  auto BI = cantFail(BootstrapInfo::CreateDefault(S));
+  ASSERT_TRUE(BI.values().count("orc-rt.Executor.SubtargetFeatures"));
+  EXPECT_EQ(BI.values().at("orc-rt.Executor.SubtargetFeatures"),
+            S.processInfo().targetCPUFeatures());
+}
diff --git a/orc-rt/test/unit/CMakeLists.txt b/orc-rt/test/unit/CMakeLists.txt
index 18f76a283bcd6..7219c9897de1e 100644
--- a/orc-rt/test/unit/CMakeLists.txt
+++ b/orc-rt/test/unit/CMakeLists.txt
@@ -63,7 +63,10 @@ add_orc_rt_unittest(CoreTests
   DISABLE_LLVM_LINK_LLVM_DYLIB
   )
 target_compile_options(CoreTests PRIVATE ${ORC_RT_COMPILE_FLAGS})
-target_link_libraries(CoreTests PRIVATE orc-rt-executor)
+target_link_libraries(CoreTests PRIVATE
+  orc-rt-executor
+  orc-rt-executor-impl-headers
+  )
 
 # Build a shared library for NativeDylibManager tests.
 add_library(NativeDylibManagerTestLib SHARED
diff --git a/orc-rt/test/unit/CommonTestUtils.h b/orc-rt/test/unit/CommonTestUtils.h
index 8682c8ec7316b..0938e35047791 100644
--- a/orc-rt/test/unit/CommonTestUtils.h
+++ b/orc-rt/test/unit/CommonTestUtils.h
@@ -44,7 +44,8 @@ class AccumulateErrors {
 };
 
 inline orc_rt::ExecutorProcessInfo mockExecutorProcessInfo() noexcept {
-  return orc_rt::ExecutorProcessInfo("arm64-apple-darwin", 16384);
+  return orc_rt::ExecutorProcessInfo("arm64-apple-darwin", 16384,
+                                     "+neon, +fullfp16");
 }
 
 /// DispatchFn for tests that should never dispatch a task. Records a test
diff --git a/orc-rt/test/unit/ExecutorProcessInfoTest.cpp b/orc-rt/test/unit/ExecutorProcessInfoTest.cpp
index a46035c100799..2aa82e9ab0d75 100644
--- a/orc-rt/test/unit/ExecutorProcessInfoTest.cpp
+++ b/orc-rt/test/unit/ExecutorProcessInfoTest.cpp
@@ -12,12 +12,34 @@
 
 #include "orc-rt/ExecutorProcessInfo.h"
 #include "orc-rt/Math.h"
+#include "executor/TargetDetails.h"
 #include "gtest/gtest.h"
 
 #include <algorithm>
 #include <unistd.h>
 
 using namespace orc_rt;
+using namespace orc_rt::target_detail;
+
+namespace orc_rt {
+/// Friend of ExecutorProcessInfo; forwards to the private helpers so the
+/// tests below can exercise them without widening the public API.
+struct ExecutorProcessInfoTestAccess {
+  static std::vector<std::string_view> detectTargetCPUFeatures() {
+    return ExecutorProcessInfo::detectTargetCPUFeatures();
+  }
+  static std::string
+  formatCPUFeatures(const std::vector<std::string_view> &Features) {
+    return ExecutorProcessInfo::formatCPUFeatures(Features);
+  }
+  static std::string
+  makeTargetTriple(std::initializer_list<std::string_view> Components) {
+    return ExecutorProcessInfo::makeTargetTriple(Components);
+  }
+};
+} // namespace orc_rt
+
+using TestAccess = orc_rt::ExecutorProcessInfoTestAccess;
 
 TEST(ExecutorProcessInfoTest, DetectSucceeds) {
   auto EPI = ExecutorProcessInfo::Detect();
@@ -56,24 +78,103 @@ TEST(ExecutorProcessInfoTest, DetectTargetTripleHasValidStructure) {
 
 TEST(ExecutorProcessInfoTest, DetectTargetTripleArchMatchesCompileTarget) {
   auto EPI = cantFail(ExecutorProcessInfo::Detect());
-#if defined(__x86_64__) || defined(_M_X64)
-  EXPECT_EQ(EPI.targetTriple().substr(0, 6), "x86_64");
+#if defined(__arm64e__)
+  EXPECT_EQ(EPI.targetTriple().substr(0, 7), "arm64e-");
+#elif defined(__APPLE__) && (defined(__arm64__) || defined(__aarch64__))
+  EXPECT_EQ(EPI.targetTriple().substr(0, 6), "arm64-");
 #elif defined(__aarch64__) || defined(_M_ARM64)
-  EXPECT_EQ(EPI.targetTriple().substr(0, 7), "aarch64");
+  EXPECT_EQ(EPI.targetTriple().substr(0, 8), "aarch64-");
+#elif defined(__x86_64h__)
+  EXPECT_EQ(EPI.targetTriple().substr(0, 8), "x86_64h-");
+#elif defined(__x86_64__) || defined(_M_X64)
+  EXPECT_EQ(EPI.targetTriple().substr(0, 7), "x86_64-");
 #endif
 }
 
 TEST(ExecutorProcessInfoTest, DetectTargetTripleOSMatchesCompileTarget) {
   auto EPI = cantFail(ExecutorProcessInfo::Detect());
 #if defined(__APPLE__)
-  EXPECT_NE(EPI.targetTriple().find("darwin"), std::string::npos);
+  EXPECT_NE(EPI.targetTriple().find("-apple-"), std::string::npos);
 #elif defined(__linux__)
-  EXPECT_NE(EPI.targetTriple().find("linux"), std::string::npos);
+  EXPECT_NE(EPI.targetTriple().find("-linux-"), std::string::npos);
 #endif
 }
 
 TEST(ExecutorProcessInfoTest, ConstructWithExplicitValues) {
-  ExecutorProcessInfo EPI("x86_64-unknown-linux-gnu", 4096);
+  ExecutorProcessInfo EPI("x86_64-unknown-linux-gnu", 4096, "+x,+a,+b");
   EXPECT_EQ(EPI.targetTriple(), "x86_64-unknown-linux-gnu");
   EXPECT_EQ(EPI.pageSize(), 4096U);
+  EXPECT_EQ(EPI.targetCPUFeatures(), "+x,+a,+b");
+}
+
+TEST(ExecutorProcessInfoTest, FormatEmptyFeatures) {
+  std::vector<std::string_view> V;
+
+  EXPECT_EQ(TestAccess::formatCPUFeatures(V), "");
+}
+
+TEST(ExecutorProcessInfoTest, FormatSingleFeature) {
+  std::vector<std::string_view> V = {feature::x86::avx2};
+
+  EXPECT_EQ(TestAccess::formatCPUFeatures(V), "+avx2");
+}
+
+TEST(ExecutorProcessInfoTest, FormatMultipleFeatures) {
+  std::vector<std::string_view> V = {feature::aarch64::neon,
+                                     feature::aarch64::dotprod,
+                                     feature::aarch64::fullfp16};
+
+  EXPECT_EQ(TestAccess::formatCPUFeatures(V), "+neon,+dotprod,+fullfp16");
+}
+
+TEST(ExecutorProcessInfoTest, DetectDoesNotCrash) {
+  [[maybe_unused]] auto _ = TestAccess::detectTargetCPUFeatures();
+  SUCCEED();
+}
+
+// this test should catch any issues that arise from out of order tests
+// hopefully.
+TEST(ExecutorProcessInfoTest, CachedCPUFeaturesResultIsIdempotent) {
+  EXPECT_EQ(ExecutorProcessInfo::detectCPUFeatures(),
+            ExecutorProcessInfo::detectCPUFeatures());
+}
+
+TEST(ExecutorProcessInfoTest, CachedStringMatchesFormatted) {
+  std::string F =
+      TestAccess::formatCPUFeatures(TestAccess::detectTargetCPUFeatures());
+  std::string C = ExecutorProcessInfo::detectCPUFeatures();
+
+  EXPECT_EQ(F, C);
+}
+
+TEST(ExecutorProcessInfoTest, MakeTargetTripleAllComponents) {
+  EXPECT_EQ(
+      TestAccess::makeTargetTriple({"arm64", "apple", "ios26.0", "macabi"}),
+      "arm64-apple-ios26.0-macabi");
+}
+
+TEST(ExecutorProcessInfoTest, MakeTargetTripleDropsTrailingEmpty) {
+  EXPECT_EQ(TestAccess::makeTargetTriple({"arm64", "apple", "macosx26.1", ""}),
+            "arm64-apple-macosx26.1");
+}
+
+TEST(ExecutorProcessInfoTest, MakeTargetTripleDoubleDashMiddleEmpty) {
+  EXPECT_EQ(TestAccess::makeTargetTriple({"x86_64", "", "linux", "gnu"}),
+            "x86_64--linux-gnu");
+  EXPECT_EQ(TestAccess::makeTargetTriple({"x86_64", "", "linux", ""}),
+            "x86_64--linux");
+}
+
+TEST(ExecutorProcessInfoTest, MakeTargetTripleEmpty) {
+  EXPECT_EQ(TestAccess::makeTargetTriple({}), "");
+}
+
+TEST(ExecutorProcessInfoTest, MakeTargetTripleExtraComponents) {
+  EXPECT_EQ(TestAccess::makeTargetTriple({"a", "b", "c", "d", "e"}),
+            "a-b-c-d-e");
+}
+
+TEST(ExecutorProcessInfoTest, CachedTargetTripleResultIsIdempotent) {
+  EXPECT_EQ(ExecutorProcessInfo::detectTargetTriple(),
+            ExecutorProcessInfo::detectTargetTriple());
 }
diff --git a/orc-rt/test/unit/InProcessControllerAccessTest.cpp b/orc-rt/test/unit/InProcessControllerAccessTest.cpp
index 45eee3d6cf8a7..2325b7a80d030 100644
--- a/orc-rt/test/unit/InProcessControllerAccessTest.cpp
+++ b/orc-rt/test/unit/InProcessControllerAccessTest.cpp
@@ -109,17 +109,26 @@ class MockIPEPC {
 
 // Convenience: attach an InProcessControllerAccess to S, constructing a
 // MockIPEPC into MockOut from inside OnConnect.
-void attachWithMock(Session &S, std::unique_ptr<MockIPEPC> &MockOut) {
+// an optional OnBootstrap is called on from inside onconnect with
+// BootstrapInfoAccess
+void attachWithMock(
+    Session &S, std::unique_ptr<MockIPEPC> &MockOut,
+    std::optional<BootstrapInfo> BI = std::nullopt,
+    move_only_function<void(InProcessControllerAccess::BootstrapInfoAccess *)>
+        OnBootstrap = {}) {
   S.attach<InProcessControllerAccess>(
-      BootstrapInfo(S),
-      [&MockOut](InProcessControllerAccess &, BootstrapInfo &,
-                 InProcessControllerAccess::Connection *C,
-                 InProcessControllerAccess::BootstrapInfoAccess *) -> Error {
+      BI ? std::move(*BI) : BootstrapInfo(S),
+      [&MockOut, OnBootstrap = std::move(OnBootstrap)](
+          InProcessControllerAccess &, BootstrapInfo &,
+          InProcessControllerAccess::Connection *C,
+          InProcessControllerAccess::BootstrapInfoAccess *BIA) mutable
+          -> Error {
         MockOut = std::make_unique<MockIPEPC>(C);
+        if (OnBootstrap)
+          OnBootstrap(BIA);
         return Error::success();
       });
 }
-
 } // namespace
 
 TEST(InProcessControllerAccessTest, ConstructAndDestroyWithoutConnect) {
@@ -317,3 +326,21 @@ TEST(InProcessControllerAccessTest, CallFromControllerSuccess) {
   ASSERT_TRUE(Result);
   EXPECT_EQ(*Result, "world");
 }
+
+TEST(InProcessControllerAccessTest, BootstrapValuesExposeSubtargetFeatures) {
+  Session S(mockExecutorProcessInfo(), noDispatch, noErrors);
+  std::optional<std::string> Features;
+  std::unique_ptr<MockIPEPC> Mock;
+  attachWithMock(S, Mock, cantFail(BootstrapInfo::CreateDefault(S)),
+                 [&](InProcessControllerAccess::BootstrapInfoAccess *BIA) {
+                   const char *Name = nullptr;
+                   const char *Bytes = nullptr;
+                   uint64_t Size = 0;
+                   while (BIA->GetNextValue(BIA, &Name, &Bytes, &Size))
+                     if (std::string_view(Name) ==
+                         "orc-rt.Executor.SubtargetFeatures")
+                       Features = std::string(Bytes, Size);
+                 });
+
+  EXPECT_EQ(*Features, S.processInfo().targetCPUFeatures());
+}



More information about the llvm-commits mailing list