[compiler-rt] ef3e521 - [ORC-RT] Add unique_function utility to the ORC runtime.
Lang Hames via llvm-commits
llvm-commits at lists.llvm.org
Sun Mar 23 21:46:50 PDT 2025
Author: Lang Hames
Date: 2025-03-24T15:46:43+11:00
New Revision: ef3e521a2b34050d92a6b711c5df811a0bde84f4
URL: https://github.com/llvm/llvm-project/commit/ef3e521a2b34050d92a6b711c5df811a0bde84f4
DIFF: https://github.com/llvm/llvm-project/commit/ef3e521a2b34050d92a6b711c5df811a0bde84f4.diff
LOG: [ORC-RT] Add unique_function utility to the ORC runtime.
A bare-bones version of LLVM's unique_function: this behaves like a
std::unique_function, except that it supports move only callable types.
This will be used in upcoming improvements to the ORC runtime.
Added:
compiler-rt/lib/orc/tests/unit/unique_function_test.cpp
compiler-rt/lib/orc/unique_function.h
Modified:
compiler-rt/lib/orc/tests/unit/CMakeLists.txt
Removed:
################################################################################
diff --git a/compiler-rt/lib/orc/tests/unit/CMakeLists.txt b/compiler-rt/lib/orc/tests/unit/CMakeLists.txt
index 5e1291142e93b..25c37c670cabd 100644
--- a/compiler-rt/lib/orc/tests/unit/CMakeLists.txt
+++ b/compiler-rt/lib/orc/tests/unit/CMakeLists.txt
@@ -11,6 +11,7 @@ set(UNITTEST_SOURCES
interval_map_test.cpp
interval_set_test.cpp
orc_unit_test_main.cpp
+ unique_function_test.cpp
wrapper_function_utils_test.cpp
simple_packed_serialization_test.cpp
string_pool_test.cpp
diff --git a/compiler-rt/lib/orc/tests/unit/unique_function_test.cpp b/compiler-rt/lib/orc/tests/unit/unique_function_test.cpp
new file mode 100644
index 0000000000000..f67cfa61c7f01
--- /dev/null
+++ b/compiler-rt/lib/orc/tests/unit/unique_function_test.cpp
@@ -0,0 +1,169 @@
+//===-- unique_function_test.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 "unique_function.h"
+#include "gtest/gtest.h"
+
+using namespace orc_rt;
+
+TEST(UniqueFunctionTest, Basic) {
+ unique_function<int(int, int)> Sum = [](int A, int B) { return A + B; };
+ EXPECT_EQ(Sum(1, 2), 3);
+
+ unique_function<int(int, int)> Sum2 = std::move(Sum);
+ EXPECT_EQ(Sum2(1, 2), 3);
+
+ unique_function<int(int, int)> Sum3 = [](int A, int B) { return A + B; };
+ Sum2 = std::move(Sum3);
+ EXPECT_EQ(Sum2(1, 2), 3);
+
+ Sum2 = unique_function<int(int, int)>([](int A, int B) { return A + B; });
+ EXPECT_EQ(Sum2(1, 2), 3);
+
+ // Explicit self-move test.
+ *&Sum2 = std::move(Sum2);
+ EXPECT_EQ(Sum2(1, 2), 3);
+
+ Sum2 = unique_function<int(int, int)>();
+ EXPECT_FALSE(Sum2);
+
+ // Make sure we can forward through l-value reference parameters.
+ unique_function<void(int &)> Inc = [](int &X) { ++X; };
+ int X = 42;
+ Inc(X);
+ EXPECT_EQ(X, 43);
+
+ // Make sure we can forward through r-value reference parameters with
+ // move-only types.
+ unique_function<int(std::unique_ptr<int> &&)> ReadAndDeallocByRef =
+ [](std::unique_ptr<int> &&Ptr) {
+ int V = *Ptr;
+ Ptr.reset();
+ return V;
+ };
+ std::unique_ptr<int> Ptr{new int(13)};
+ EXPECT_EQ(ReadAndDeallocByRef(std::move(Ptr)), 13);
+ EXPECT_FALSE((bool)Ptr);
+
+ // Make sure we can pass a move-only temporary as opposed to a local variable.
+ EXPECT_EQ(ReadAndDeallocByRef(std::unique_ptr<int>(new int(42))), 42);
+
+ // Make sure we can pass a move-only type by-value.
+ unique_function<int(std::unique_ptr<int>)> ReadAndDeallocByVal =
+ [](std::unique_ptr<int> Ptr) {
+ int V = *Ptr;
+ Ptr.reset();
+ return V;
+ };
+ Ptr.reset(new int(13));
+ EXPECT_EQ(ReadAndDeallocByVal(std::move(Ptr)), 13);
+ EXPECT_FALSE((bool)Ptr);
+
+ EXPECT_EQ(ReadAndDeallocByVal(std::unique_ptr<int>(new int(42))), 42);
+}
+
+TEST(UniqueFunctionTest, Captures) {
+ long A = 1, B = 2, C = 3, D = 4, E = 5;
+
+ unique_function<long()> Tmp;
+
+ unique_function<long()> C1 = [A]() { return A; };
+ EXPECT_EQ(C1(), 1);
+ Tmp = std::move(C1);
+ EXPECT_EQ(Tmp(), 1);
+
+ unique_function<long()> C2 = [A, B]() { return A + B; };
+ EXPECT_EQ(C2(), 3);
+ Tmp = std::move(C2);
+ EXPECT_EQ(Tmp(), 3);
+
+ unique_function<long()> C3 = [A, B, C]() { return A + B + C; };
+ EXPECT_EQ(C3(), 6);
+ Tmp = std::move(C3);
+ EXPECT_EQ(Tmp(), 6);
+
+ unique_function<long()> C4 = [A, B, C, D]() { return A + B + C + D; };
+ EXPECT_EQ(C4(), 10);
+ Tmp = std::move(C4);
+ EXPECT_EQ(Tmp(), 10);
+
+ unique_function<long()> C5 = [A, B, C, D, E]() { return A + B + C + D + E; };
+ EXPECT_EQ(C5(), 15);
+ Tmp = std::move(C5);
+ EXPECT_EQ(Tmp(), 15);
+}
+
+TEST(UniqueFunctionTest, MoveOnly) {
+ struct SmallCallable {
+ std::unique_ptr<int> A = std::make_unique<int>(1);
+ int operator()(int B) { return *A + B; }
+ };
+
+ unique_function<int(int)> Small = SmallCallable();
+ EXPECT_EQ(Small(2), 3);
+ unique_function<int(int)> Small2 = std::move(Small);
+ EXPECT_EQ(Small2(2), 3);
+}
+
+TEST(UniqueFunctionTest, CountForwardingCopies) {
+ struct CopyCounter {
+ int &CopyCount;
+
+ CopyCounter(int &CopyCount) : CopyCount(CopyCount) {}
+ CopyCounter(const CopyCounter &Arg) : CopyCount(Arg.CopyCount) {
+ ++CopyCount;
+ }
+ };
+
+ unique_function<void(CopyCounter)> ByValF = [](CopyCounter) {};
+ int CopyCount = 0;
+ ByValF(CopyCounter(CopyCount));
+ EXPECT_EQ(1, CopyCount);
+
+ CopyCount = 0;
+ {
+ CopyCounter Counter{CopyCount};
+ ByValF(Counter);
+ }
+ EXPECT_EQ(2, CopyCount);
+
+ // Check that we don't generate a copy at all when we can bind a reference all
+ // the way down, even if that reference could *in theory* allow copies.
+ unique_function<void(const CopyCounter &)> ByRefF = [](const CopyCounter &) {
+ };
+ CopyCount = 0;
+ ByRefF(CopyCounter(CopyCount));
+ EXPECT_EQ(0, CopyCount);
+
+ CopyCount = 0;
+ {
+ CopyCounter Counter{CopyCount};
+ ByRefF(Counter);
+ }
+ EXPECT_EQ(0, CopyCount);
+
+ // If we use a reference, we can make a stronger guarantee that *no* copy
+ // occurs.
+ struct Uncopyable {
+ Uncopyable() = default;
+ Uncopyable(const Uncopyable &) = delete;
+ };
+ unique_function<void(const Uncopyable &)> UncopyableF =
+ [](const Uncopyable &) {};
+ UncopyableF(Uncopyable());
+ Uncopyable X;
+ UncopyableF(X);
+}
+
+TEST(UniqueFunctionTest, BooleanConversion) {
+ unique_function<void()> D;
+ EXPECT_FALSE(D);
+
+ unique_function<void()> F = []() {};
+ EXPECT_TRUE(F);
+}
diff --git a/compiler-rt/lib/orc/unique_function.h b/compiler-rt/lib/orc/unique_function.h
new file mode 100644
index 0000000000000..63d67b99193ae
--- /dev/null
+++ b/compiler-rt/lib/orc/unique_function.h
@@ -0,0 +1,80 @@
+//===----- unique_function.h - moveable type-erasing function ---*- 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
+//
+//===----------------------------------------------------------------------===//
+///
+/// unique_function works like std::function, but supports move-only callable
+/// objects.
+///
+/// TODO: Use LLVM's unique_function (llvm/include/llvm/ADT/FunctionExtras.h),
+/// which uses some extra inline storage to avoid heap allocations for
+/// small objects. Using LLVM's unique_function will require first
+/// porting some other utilities like PointerIntPair, PointerUnion, and
+/// PointerLikeTypeTraits. (These are likely to be independently useful
+/// in the orc runtime, so porting will have additional benefits).
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef ORC_RT_UNIQUE_FUNCTION_H
+#define ORC_RT_UNIQUE_FUNCTION_H
+
+#include <memory>
+
+namespace orc_rt {
+
+namespace unique_function_detail {
+
+template <typename RetT, typename... ArgTs> class Callable {
+public:
+ virtual ~Callable() = default;
+ virtual RetT call(ArgTs &&...Args) = 0;
+};
+
+template <typename CallableT, typename RetT, typename... ArgTs>
+class CallableImpl : public Callable<RetT, ArgTs...> {
+public:
+ CallableImpl(CallableT &&Callable) : Callable(std::move(Callable)) {}
+ RetT call(ArgTs &&...Args) override {
+ return Callable(std::forward<ArgTs>(Args)...);
+ }
+
+private:
+ CallableT Callable;
+};
+
+} // namespace unique_function_detail
+
+template <typename FnT> class unique_function;
+
+template <typename RetT, typename... ArgTs>
+class unique_function<RetT(ArgTs...)> {
+public:
+ unique_function() = default;
+ unique_function(std::nullptr_t) {}
+ unique_function(unique_function &&) = default;
+ unique_function(const unique_function &&) = delete;
+ unique_function &operator=(unique_function &&) = default;
+ unique_function &operator=(const unique_function &&) = delete;
+
+ template <typename CallableT>
+ unique_function(CallableT &&Callable)
+ : C(std::make_unique<
+ unique_function_detail::CallableImpl<CallableT, RetT, ArgTs...>>(
+ std::forward<CallableT>(Callable))) {}
+
+ RetT operator()(ArgTs... Params) {
+ return C->call(std::forward<ArgTs>(Params)...);
+ }
+
+ explicit operator bool() const { return !!C; }
+
+private:
+ std::unique_ptr<unique_function_detail::Callable<RetT, ArgTs...>> C;
+};
+
+} // namespace orc_rt
+
+#endif // ORC_RT_UNIQUE_FUNCTION_H
More information about the llvm-commits
mailing list