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

Jared Wyles via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 28 04:46:48 PDT 2026


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

>From b02decd6589e0de731b1ff724111386178ed805a 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] WIP: cpu feature detection for orc-rt

---
 orc-rt/include/CMakeLists.txt                 |   2 +
 orc-rt/include/orc-rt/ExecutorProcessInfo.h   |  30 ++++-
 orc-rt/include/orc-rt/StringExtras.h          |  58 +++++++++
 orc-rt/include/orc-rt/TargetDetails.h         |  51 ++++++++
 orc-rt/lib/executor/BootstrapInfo.cpp         |   2 +
 orc-rt/lib/executor/CMakeLists.txt            |  44 +++++--
 orc-rt/lib/executor/ExecutorProcessInfo.cpp   |  62 +++++-----
 .../executor/InProcessControllerAccess.cpp    |   4 +
 orc-rt/lib/executor/darwin/CPUFeatures.cpp    |  60 ++++++++++
 orc-rt/lib/executor/darwin/TargetTriple.cpp   | 103 ++++++++++++++++
 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               |   1 +
 orc-rt/test/unit/CommonTestUtils.h            |   3 +-
 orc-rt/test/unit/ExecutorProcessInfoTest.cpp  | 113 +++++++++++++++++-
 .../unit/InProcessControllerAccessTest.cpp    |  39 +++++-
 orc-rt/test/unit/StringExtrasTest.cpp         |  36 ++++++
 25 files changed, 693 insertions(+), 53 deletions(-)
 create mode 100644 orc-rt/include/orc-rt/StringExtras.h
 create mode 100644 orc-rt/include/orc-rt/TargetDetails.h
 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
 create mode 100644 orc-rt/test/unit/StringExtrasTest.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/include/orc-rt/StringExtras.h b/orc-rt/include/orc-rt/StringExtras.h
new file mode 100644
index 0000000000000..85b0b5d189ae3
--- /dev/null
+++ b/orc-rt/include/orc-rt/StringExtras.h
@@ -0,0 +1,58 @@
+//===---------- StringExtras.h - Stolen from llvm/ADT/StringExtras.h ---------*-
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_STRINGEXTRAS_H
+#define ORC_RT_STRINGEXTRAS_H
+
+#include <cassert>
+#include <iterator>
+#include <string>
+#include <type_traits>
+namespace orc_rt {
+/// A simplification of what is in llvm/ADT/StringExtras.h
+/// Preserving behaviour so we can switch if we ever pull
+/// a common util out of llvm. It removes the tag dispatch
+/// for simplicity sake. Added an assert to bail instead.
+template <typename IteratorT>
+std::string join(IteratorT Begin, IteratorT End, std::string_view Separator) {
+  using Category = typename std::iterator_traits<IteratorT>::iterator_category;
+  static_assert(std::is_base_of_v<std::forward_iterator_tag, Category>,
+                "join requires forward iterators (range is traversed twice)");
+  if (Begin == End)
+    return {};
+
+  size_t Size = 0, Count = 0;
+  for (IteratorT I = Begin; I != End; ++I, ++Count)
+    Size += std::string_view(*I).size();
+
+  std::string Result;
+  Result.reserve(Size + (Count - 1) * Separator.size());
+  const size_t PrevCapacity = Result.capacity();
+  (void)PrevCapacity;
+
+  Result += std::string_view(*Begin);
+  while (++Begin != End) {
+    Result += Separator;
+    Result += std::string_view(*Begin);
+  }
+
+  assert(PrevCapacity == Result.capacity() && "String grew during building");
+  return Result;
+}
+
+template <typename Range>
+std::string join(Range &&R, std::string_view Separator) {
+  return join(std::begin(R), std::end(R), Separator);
+}
+
+inline std::string join(std::initializer_list<std::string_view> Elements,
+                        std::string_view Separator) {
+  return join(Elements.begin(), Elements.end(), Separator);
+}
+} // namespace orc_rt
+#endif
diff --git a/orc-rt/include/orc-rt/TargetDetails.h b/orc-rt/include/orc-rt/TargetDetails.h
new file mode 100644
index 0000000000000..65b8ce16011ee
--- /dev/null
+++ b/orc-rt/include/orc-rt/TargetDetails.h
@@ -0,0 +1,51 @@
+//===- TargetDetails.h - Names for target triple components -----*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_TARGETDETAILS_H
+#define ORC_RT_TARGETDETAILS_H
+
+#include <string_view>
+
+namespace orc_rt::target_detail {
+
+/// Architecture names. Darwin spells AArch64 "arm64"; everywhere else it is
+/// "aarch64", so both are present.
+namespace arch {
+inline constexpr std::string_view aarch64 = "aarch64";
+inline constexpr std::string_view arm64 = "arm64";
+inline constexpr std::string_view arm64e = "arm64e";
+inline constexpr std::string_view i386 = "i386";
+inline constexpr std::string_view x86_64 = "x86_64";
+inline constexpr std::string_view x86_64h = "x86_64h";
+} // namespace arch
+
+namespace vendor {
+inline constexpr std::string_view apple = "apple";
+inline constexpr std::string_view pc = "pc";
+inline constexpr std::string_view unknown = "unknown";
+} // namespace vendor
+
+/// These should match llvm feature names.
+namespace feature {
+namespace x86 {
+inline constexpr std::string_view sse4_1 = "sse4.1";
+inline constexpr std::string_view sse4_2 = "sse4.2";
+inline constexpr std::string_view avx = "avx";
+inline constexpr std::string_view avx2 = "avx2";
+} // namespace x86
+namespace aarch64 {
+inline constexpr std::string_view neon = "neon";
+inline constexpr std::string_view dotprod = "dotprod";
+inline constexpr std::string_view fullfp16 = "fullfp16";
+inline constexpr std::string_view sha3 = "sha3";
+} // namespace aarch64
+} // namespace feature
+
+} // namespace orc_rt::target_detail
+
+#endif // ORC_RT_TARGETDETAILS_H
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..931e8a648b383 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,49 @@ 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)
 
 # 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 22921256e8928..c6b27efe92a62 100644
--- a/orc-rt/lib/executor/InProcessControllerAccess.cpp
+++ b/orc-rt/lib/executor/InProcessControllerAccess.cpp
@@ -114,6 +114,10 @@ struct InProcessControllerAccess::BootstrapInfoAccessImpl
   }
 
 private:
+  const char *getTargetCPUFeatures() const noexcept {
+    return BI.processInfo().targetCPUFeatures().c_str();
+  }
+
   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..b16d661de01d5
--- /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 "orc-rt/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..65c2b1680154e
--- /dev/null
+++ b/orc-rt/lib/executor/darwin/TargetTriple.cpp
@@ -0,0 +1,103 @@
+//===- 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 "orc-rt/TargetDetails.h"
+
+#include <TargetConditionals.h>
+#include <cstdlib>
+#include <cstring>
+#include <sys/sysctl.h>
+#include <sys/types.h>
+
+namespace orc_rt {
+
+namespace {
+
+std::string sysctlString(const char *Name) noexcept {
+  // This should be large enough for everything we need it for
+  // this upfront oversized allocation avoids a second syscall.
+  char Buf[64] = {};
+  size_t S = sizeof(Buf);
+  if (sysctlbyname(Name, Buf, &S, nullptr, 0) != 0)
+    return {};
+  return std::string(Buf, strnlen(Buf, S));
+}
+
+} // 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 7485a18ed5c36..18f76a283bcd6 100644
--- a/orc-rt/test/unit/CMakeLists.txt
+++ b/orc-rt/test/unit/CMakeLists.txt
@@ -49,6 +49,7 @@ add_orc_rt_unittest(CoreTests
   SimplePackedSerializationTest.cpp
   SimpleSymbolTableTest.cpp
   StandaloneMachOUnwindInfoRegistrarTest.cpp
+  StringExtrasTest.cpp
   TaskGroupTest.cpp
   ThreadPoolRunnerTest.cpp
   WrapperFunctionBufferTest.cpp
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..4b826c27cdfb2 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 "orc-rt/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 1d444b1181b34..8141faf39d82f 100644
--- a/orc-rt/test/unit/InProcessControllerAccessTest.cpp
+++ b/orc-rt/test/unit/InProcessControllerAccessTest.cpp
@@ -108,17 +108,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) {
@@ -316,3 +325,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());
+}
diff --git a/orc-rt/test/unit/StringExtrasTest.cpp b/orc-rt/test/unit/StringExtrasTest.cpp
new file mode 100644
index 0000000000000..2de586e258b72
--- /dev/null
+++ b/orc-rt/test/unit/StringExtrasTest.cpp
@@ -0,0 +1,36 @@
+//===- StringExtrasTest.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
+//
+//===----------------------------------------------------------------------===//
+//
+// Tests for string utility APIs.
+//
+//===----------------------------------------------------------------------===//
+
+#include "orc-rt/StringExtras.h"
+#include "gtest/gtest.h"
+
+using namespace orc_rt;
+
+TEST(StringExtrasTest, JoinSingleString) { EXPECT_EQ(join({"x"}, "-"), "x"); }
+
+TEST(StringExtrasTest, JoinMultipleString) {
+  EXPECT_EQ(join({"a", "b", "c"}, "-"), "a-b-c");
+}
+
+TEST(StringExtrasTest, JoinMantainsEmptyString) {
+  EXPECT_EQ(join({"a", "", "c"}, "-"), "a--c");
+  EXPECT_EQ(join({"", "b"}, "-"), "-b");
+  EXPECT_EQ(join({"a", ""}, "-"), "a-");
+}
+
+TEST(StringExtrasTest, JoinEmptySeparator) {
+  EXPECT_EQ(join({"a", "b", "c"}, ""), "abc");
+}
+
+TEST(StringExtrasTest, JoinMultiSeparator) {
+  EXPECT_EQ(join({"a", "b", "c"}, ", "), "a, b, c");
+}



More information about the llvm-commits mailing list