[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