Skip to content

Commit ef3e521

Browse files
committed
[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.
1 parent 1019457 commit ef3e521

File tree

3 files changed

+250
-0
lines changed

3 files changed

+250
-0
lines changed

compiler-rt/lib/orc/tests/unit/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ set(UNITTEST_SOURCES
1111
interval_map_test.cpp
1212
interval_set_test.cpp
1313
orc_unit_test_main.cpp
14+
unique_function_test.cpp
1415
wrapper_function_utils_test.cpp
1516
simple_packed_serialization_test.cpp
1617
string_pool_test.cpp
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
//===-- unique_function_test.cpp ------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "unique_function.h"
10+
#include "gtest/gtest.h"
11+
12+
using namespace orc_rt;
13+
14+
TEST(UniqueFunctionTest, Basic) {
15+
unique_function<int(int, int)> Sum = [](int A, int B) { return A + B; };
16+
EXPECT_EQ(Sum(1, 2), 3);
17+
18+
unique_function<int(int, int)> Sum2 = std::move(Sum);
19+
EXPECT_EQ(Sum2(1, 2), 3);
20+
21+
unique_function<int(int, int)> Sum3 = [](int A, int B) { return A + B; };
22+
Sum2 = std::move(Sum3);
23+
EXPECT_EQ(Sum2(1, 2), 3);
24+
25+
Sum2 = unique_function<int(int, int)>([](int A, int B) { return A + B; });
26+
EXPECT_EQ(Sum2(1, 2), 3);
27+
28+
// Explicit self-move test.
29+
*&Sum2 = std::move(Sum2);
30+
EXPECT_EQ(Sum2(1, 2), 3);
31+
32+
Sum2 = unique_function<int(int, int)>();
33+
EXPECT_FALSE(Sum2);
34+
35+
// Make sure we can forward through l-value reference parameters.
36+
unique_function<void(int &)> Inc = [](int &X) { ++X; };
37+
int X = 42;
38+
Inc(X);
39+
EXPECT_EQ(X, 43);
40+
41+
// Make sure we can forward through r-value reference parameters with
42+
// move-only types.
43+
unique_function<int(std::unique_ptr<int> &&)> ReadAndDeallocByRef =
44+
[](std::unique_ptr<int> &&Ptr) {
45+
int V = *Ptr;
46+
Ptr.reset();
47+
return V;
48+
};
49+
std::unique_ptr<int> Ptr{new int(13)};
50+
EXPECT_EQ(ReadAndDeallocByRef(std::move(Ptr)), 13);
51+
EXPECT_FALSE((bool)Ptr);
52+
53+
// Make sure we can pass a move-only temporary as opposed to a local variable.
54+
EXPECT_EQ(ReadAndDeallocByRef(std::unique_ptr<int>(new int(42))), 42);
55+
56+
// Make sure we can pass a move-only type by-value.
57+
unique_function<int(std::unique_ptr<int>)> ReadAndDeallocByVal =
58+
[](std::unique_ptr<int> Ptr) {
59+
int V = *Ptr;
60+
Ptr.reset();
61+
return V;
62+
};
63+
Ptr.reset(new int(13));
64+
EXPECT_EQ(ReadAndDeallocByVal(std::move(Ptr)), 13);
65+
EXPECT_FALSE((bool)Ptr);
66+
67+
EXPECT_EQ(ReadAndDeallocByVal(std::unique_ptr<int>(new int(42))), 42);
68+
}
69+
70+
TEST(UniqueFunctionTest, Captures) {
71+
long A = 1, B = 2, C = 3, D = 4, E = 5;
72+
73+
unique_function<long()> Tmp;
74+
75+
unique_function<long()> C1 = [A]() { return A; };
76+
EXPECT_EQ(C1(), 1);
77+
Tmp = std::move(C1);
78+
EXPECT_EQ(Tmp(), 1);
79+
80+
unique_function<long()> C2 = [A, B]() { return A + B; };
81+
EXPECT_EQ(C2(), 3);
82+
Tmp = std::move(C2);
83+
EXPECT_EQ(Tmp(), 3);
84+
85+
unique_function<long()> C3 = [A, B, C]() { return A + B + C; };
86+
EXPECT_EQ(C3(), 6);
87+
Tmp = std::move(C3);
88+
EXPECT_EQ(Tmp(), 6);
89+
90+
unique_function<long()> C4 = [A, B, C, D]() { return A + B + C + D; };
91+
EXPECT_EQ(C4(), 10);
92+
Tmp = std::move(C4);
93+
EXPECT_EQ(Tmp(), 10);
94+
95+
unique_function<long()> C5 = [A, B, C, D, E]() { return A + B + C + D + E; };
96+
EXPECT_EQ(C5(), 15);
97+
Tmp = std::move(C5);
98+
EXPECT_EQ(Tmp(), 15);
99+
}
100+
101+
TEST(UniqueFunctionTest, MoveOnly) {
102+
struct SmallCallable {
103+
std::unique_ptr<int> A = std::make_unique<int>(1);
104+
int operator()(int B) { return *A + B; }
105+
};
106+
107+
unique_function<int(int)> Small = SmallCallable();
108+
EXPECT_EQ(Small(2), 3);
109+
unique_function<int(int)> Small2 = std::move(Small);
110+
EXPECT_EQ(Small2(2), 3);
111+
}
112+
113+
TEST(UniqueFunctionTest, CountForwardingCopies) {
114+
struct CopyCounter {
115+
int &CopyCount;
116+
117+
CopyCounter(int &CopyCount) : CopyCount(CopyCount) {}
118+
CopyCounter(const CopyCounter &Arg) : CopyCount(Arg.CopyCount) {
119+
++CopyCount;
120+
}
121+
};
122+
123+
unique_function<void(CopyCounter)> ByValF = [](CopyCounter) {};
124+
int CopyCount = 0;
125+
ByValF(CopyCounter(CopyCount));
126+
EXPECT_EQ(1, CopyCount);
127+
128+
CopyCount = 0;
129+
{
130+
CopyCounter Counter{CopyCount};
131+
ByValF(Counter);
132+
}
133+
EXPECT_EQ(2, CopyCount);
134+
135+
// Check that we don't generate a copy at all when we can bind a reference all
136+
// the way down, even if that reference could *in theory* allow copies.
137+
unique_function<void(const CopyCounter &)> ByRefF = [](const CopyCounter &) {
138+
};
139+
CopyCount = 0;
140+
ByRefF(CopyCounter(CopyCount));
141+
EXPECT_EQ(0, CopyCount);
142+
143+
CopyCount = 0;
144+
{
145+
CopyCounter Counter{CopyCount};
146+
ByRefF(Counter);
147+
}
148+
EXPECT_EQ(0, CopyCount);
149+
150+
// If we use a reference, we can make a stronger guarantee that *no* copy
151+
// occurs.
152+
struct Uncopyable {
153+
Uncopyable() = default;
154+
Uncopyable(const Uncopyable &) = delete;
155+
};
156+
unique_function<void(const Uncopyable &)> UncopyableF =
157+
[](const Uncopyable &) {};
158+
UncopyableF(Uncopyable());
159+
Uncopyable X;
160+
UncopyableF(X);
161+
}
162+
163+
TEST(UniqueFunctionTest, BooleanConversion) {
164+
unique_function<void()> D;
165+
EXPECT_FALSE(D);
166+
167+
unique_function<void()> F = []() {};
168+
EXPECT_TRUE(F);
169+
}

compiler-rt/lib/orc/unique_function.h

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
//===----- unique_function.h - moveable type-erasing function ---*- C++ -*-===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
///
9+
/// unique_function works like std::function, but supports move-only callable
10+
/// objects.
11+
///
12+
/// TODO: Use LLVM's unique_function (llvm/include/llvm/ADT/FunctionExtras.h),
13+
/// which uses some extra inline storage to avoid heap allocations for
14+
/// small objects. Using LLVM's unique_function will require first
15+
/// porting some other utilities like PointerIntPair, PointerUnion, and
16+
/// PointerLikeTypeTraits. (These are likely to be independently useful
17+
/// in the orc runtime, so porting will have additional benefits).
18+
///
19+
//===----------------------------------------------------------------------===//
20+
21+
#ifndef ORC_RT_UNIQUE_FUNCTION_H
22+
#define ORC_RT_UNIQUE_FUNCTION_H
23+
24+
#include <memory>
25+
26+
namespace orc_rt {
27+
28+
namespace unique_function_detail {
29+
30+
template <typename RetT, typename... ArgTs> class Callable {
31+
public:
32+
virtual ~Callable() = default;
33+
virtual RetT call(ArgTs &&...Args) = 0;
34+
};
35+
36+
template <typename CallableT, typename RetT, typename... ArgTs>
37+
class CallableImpl : public Callable<RetT, ArgTs...> {
38+
public:
39+
CallableImpl(CallableT &&Callable) : Callable(std::move(Callable)) {}
40+
RetT call(ArgTs &&...Args) override {
41+
return Callable(std::forward<ArgTs>(Args)...);
42+
}
43+
44+
private:
45+
CallableT Callable;
46+
};
47+
48+
} // namespace unique_function_detail
49+
50+
template <typename FnT> class unique_function;
51+
52+
template <typename RetT, typename... ArgTs>
53+
class unique_function<RetT(ArgTs...)> {
54+
public:
55+
unique_function() = default;
56+
unique_function(std::nullptr_t) {}
57+
unique_function(unique_function &&) = default;
58+
unique_function(const unique_function &&) = delete;
59+
unique_function &operator=(unique_function &&) = default;
60+
unique_function &operator=(const unique_function &&) = delete;
61+
62+
template <typename CallableT>
63+
unique_function(CallableT &&Callable)
64+
: C(std::make_unique<
65+
unique_function_detail::CallableImpl<CallableT, RetT, ArgTs...>>(
66+
std::forward<CallableT>(Callable))) {}
67+
68+
RetT operator()(ArgTs... Params) {
69+
return C->call(std::forward<ArgTs>(Params)...);
70+
}
71+
72+
explicit operator bool() const { return !!C; }
73+
74+
private:
75+
std::unique_ptr<unique_function_detail::Callable<RetT, ArgTs...>> C;
76+
};
77+
78+
} // namespace orc_rt
79+
80+
#endif // ORC_RT_UNIQUE_FUNCTION_H

0 commit comments

Comments
 (0)