Skip to content

Commit bfe32b0

Browse files
committed
SILOptimizer: switch std::function to inplace_function to avoid memory allocation overhead.
1 parent 27ad938 commit bfe32b0

File tree

3 files changed

+391
-6
lines changed

3 files changed

+391
-6
lines changed
Lines changed: 383 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,383 @@
1+
/*
2+
* Boost Software License - Version 1.0 - August 17th, 2003
3+
*
4+
* Permission is hereby granted, free of charge, to any person or organization
5+
* obtaining a copy of the software and accompanying documentation covered by
6+
* this license (the "Software") to use, reproduce, display, distribute,
7+
* execute, and transmit the Software, and to prepare derivative works of the
8+
* Software, and to permit third-parties to whom the Software is furnished to
9+
* do so, all subject to the following:
10+
*
11+
* The copyright notices in the Software and this entire statement, including
12+
* the above license grant, this restriction and the following disclaimer,
13+
* must be included in all copies of the Software, in whole or in part, and
14+
* all derivative works of the Software, unless such copies or derivative
15+
* works are solely in the form of machine-executable object code generated by
16+
* a source language processor.
17+
*
18+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20+
* FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
21+
* SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
22+
* FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
23+
* ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
24+
* DEALINGS IN THE SOFTWARE.
25+
*/
26+
27+
#pragma once
28+
29+
#include <cassert>
30+
#include <type_traits>
31+
#include <utility>
32+
#include <functional>
33+
34+
#ifndef SG14_INPLACE_FUNCTION_THROW
35+
#define SG14_INPLACE_FUNCTION_THROW(x) assert(0 && "Bad function call")
36+
#endif
37+
38+
namespace stdext {
39+
40+
namespace inplace_function_detail {
41+
42+
static constexpr size_t InplaceFunctionDefaultCapacity = 32;
43+
44+
#ifndef SG14_USE_STD_ALIGNED_STORAGE
45+
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61458
46+
// MSVC 32-bit has the same bug.
47+
// libc++ and MSVC 64-bit seem to work fine right now, but why run the risk?
48+
template<size_t Cap>
49+
union aligned_storage_helper {
50+
struct double1 { double a; };
51+
struct double4 { double a[4]; };
52+
template<class T> using maybe = std::conditional_t<(Cap >= sizeof(T)), T, char>;
53+
char real_data[Cap];
54+
maybe<int> a;
55+
maybe<long> b;
56+
maybe<long long> c;
57+
maybe<void*> d;
58+
maybe<void(*)()> e;
59+
maybe<double1> f;
60+
maybe<double4> g;
61+
maybe<long double> h;
62+
};
63+
64+
template<size_t Cap, size_t Align = alignof(aligned_storage_helper<Cap>)>
65+
struct aligned_storage {
66+
using type = std::aligned_storage_t<Cap, Align>;
67+
};
68+
69+
template<size_t Cap, size_t Align = alignof(aligned_storage_helper<Cap>)>
70+
using aligned_storage_t = typename aligned_storage<Cap, Align>::type;
71+
static_assert(sizeof(aligned_storage_t<sizeof(void*)>) == sizeof(void*), "A");
72+
static_assert(alignof(aligned_storage_t<sizeof(void*)>) == alignof(void*), "B");
73+
#else
74+
using std::aligned_storage;
75+
using std::aligned_storage_t;
76+
static_assert(sizeof(std::aligned_storage_t<sizeof(void*)>) == sizeof(void*), "C");
77+
static_assert(alignof(std::aligned_storage_t<sizeof(void*)>) == alignof(void*), "D");
78+
#endif
79+
80+
template<class T> struct wrapper
81+
{
82+
using type = T;
83+
};
84+
85+
template<class R, class... Args> struct vtable
86+
{
87+
using storage_ptr_t = void*;
88+
89+
using invoke_ptr_t = R(*)(storage_ptr_t, Args&&...);
90+
using process_ptr_t = void(*)(storage_ptr_t, storage_ptr_t);
91+
using destructor_ptr_t = void(*)(storage_ptr_t);
92+
93+
const invoke_ptr_t invoke_ptr;
94+
const process_ptr_t copy_ptr;
95+
const process_ptr_t relocate_ptr;
96+
const destructor_ptr_t destructor_ptr;
97+
98+
explicit constexpr vtable() noexcept :
99+
invoke_ptr{ [](storage_ptr_t, Args&&...) -> R
100+
{ SG14_INPLACE_FUNCTION_THROW(std::bad_function_call()); }
101+
},
102+
copy_ptr{ [](storage_ptr_t, storage_ptr_t) -> void {} },
103+
relocate_ptr{ [](storage_ptr_t, storage_ptr_t) -> void {} },
104+
destructor_ptr{ [](storage_ptr_t) -> void {} }
105+
{}
106+
107+
template<class C> explicit constexpr vtable(wrapper<C>) noexcept :
108+
invoke_ptr{ [](storage_ptr_t storage_ptr, Args&&... args) -> R
109+
{ return (*static_cast<C*>(storage_ptr))(
110+
static_cast<Args&&>(args)...
111+
); }
112+
},
113+
copy_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) -> void
114+
{ ::new (dst_ptr) C{ (*static_cast<C*>(src_ptr)) }; }
115+
},
116+
relocate_ptr{ [](storage_ptr_t dst_ptr, storage_ptr_t src_ptr) -> void
117+
{
118+
::new (dst_ptr) C{ std::move(*static_cast<C*>(src_ptr)) };
119+
static_cast<C*>(src_ptr)->~C();
120+
}
121+
},
122+
destructor_ptr{ [](storage_ptr_t src_ptr) -> void
123+
{ static_cast<C*>(src_ptr)->~C(); }
124+
}
125+
{}
126+
127+
vtable(const vtable&) = delete;
128+
vtable(vtable&&) = delete;
129+
130+
vtable& operator= (const vtable&) = delete;
131+
vtable& operator= (vtable&&) = delete;
132+
133+
~vtable() = default;
134+
};
135+
136+
template<class R, class... Args>
137+
#if __cplusplus >= 201703L
138+
inline constexpr
139+
#endif
140+
vtable<R, Args...> empty_vtable{};
141+
142+
template<size_t DstCap, size_t DstAlign, size_t SrcCap, size_t SrcAlign>
143+
struct is_valid_inplace_dst : std::true_type
144+
{
145+
static_assert(DstCap >= SrcCap,
146+
"Can't squeeze larger inplace_function into a smaller one"
147+
);
148+
149+
static_assert(DstAlign % SrcAlign == 0,
150+
"Incompatible inplace_function alignments"
151+
);
152+
};
153+
154+
// C++11 MSVC compatible implementation of std::is_invocable_r.
155+
156+
template<class R> void accept(R);
157+
158+
template<class, class R, class F, class... Args> struct is_invocable_r_impl : std::false_type {};
159+
160+
template<class F, class... Args> struct is_invocable_r_impl<
161+
decltype(std::declval<F>()(std::declval<Args>()...), void()),
162+
void,
163+
F,
164+
Args...
165+
> : std::true_type {};
166+
167+
template<class F, class... Args> struct is_invocable_r_impl<
168+
decltype(std::declval<F>()(std::declval<Args>()...), void()),
169+
const void,
170+
F,
171+
Args...
172+
> : std::true_type {};
173+
174+
template<class R, class F, class... Args> struct is_invocable_r_impl<
175+
decltype(accept<R>(std::declval<F>()(std::declval<Args>()...))),
176+
R,
177+
F,
178+
Args...
179+
> : std::true_type {};
180+
181+
template<class R, class F, class... Args> using is_invocable_r = is_invocable_r_impl<
182+
void,
183+
R,
184+
F,
185+
Args...
186+
>;
187+
} // namespace inplace_function_detail
188+
189+
template<
190+
class Signature,
191+
size_t Capacity = inplace_function_detail::InplaceFunctionDefaultCapacity,
192+
size_t Alignment = alignof(inplace_function_detail::aligned_storage_t<Capacity>)
193+
>
194+
class inplace_function; // unspecified
195+
196+
namespace inplace_function_detail {
197+
template<class> struct is_inplace_function : std::false_type {};
198+
template<class Sig, size_t Cap, size_t Align>
199+
struct is_inplace_function<inplace_function<Sig, Cap, Align>> : std::true_type {};
200+
} // namespace inplace_function_detail
201+
202+
template<
203+
class R,
204+
class... Args,
205+
size_t Capacity,
206+
size_t Alignment
207+
>
208+
class inplace_function<R(Args...), Capacity, Alignment>
209+
{
210+
using storage_t = inplace_function_detail::aligned_storage_t<Capacity, Alignment>;
211+
using vtable_t = inplace_function_detail::vtable<R, Args...>;
212+
using vtable_ptr_t = const vtable_t*;
213+
214+
template <class, size_t, size_t> friend class inplace_function;
215+
216+
public:
217+
using capacity = std::integral_constant<size_t, Capacity>;
218+
using alignment = std::integral_constant<size_t, Alignment>;
219+
220+
inplace_function() noexcept :
221+
vtable_ptr_{std::addressof(inplace_function_detail::empty_vtable<R, Args...>)}
222+
{}
223+
224+
template<
225+
class T,
226+
class C = std::decay_t<T>,
227+
class = std::enable_if_t<
228+
!inplace_function_detail::is_inplace_function<C>::value
229+
&& inplace_function_detail::is_invocable_r<R, C&, Args...>::value
230+
>
231+
>
232+
inplace_function(T&& closure)
233+
{
234+
static_assert(std::is_copy_constructible<C>::value,
235+
"inplace_function cannot be constructed from non-copyable type"
236+
);
237+
238+
static_assert(sizeof(C) <= Capacity,
239+
"inplace_function cannot be constructed from object with this (large) size"
240+
);
241+
242+
static_assert(Alignment % alignof(C) == 0,
243+
"inplace_function cannot be constructed from object with this (large) alignment"
244+
);
245+
246+
static const vtable_t vt{inplace_function_detail::wrapper<C>{}};
247+
vtable_ptr_ = std::addressof(vt);
248+
249+
::new (std::addressof(storage_)) C{std::forward<T>(closure)};
250+
}
251+
252+
template<size_t Cap, size_t Align>
253+
inplace_function(const inplace_function<R(Args...), Cap, Align>& other)
254+
: inplace_function(other.vtable_ptr_, other.vtable_ptr_->copy_ptr, std::addressof(other.storage_))
255+
{
256+
static_assert(inplace_function_detail::is_valid_inplace_dst<
257+
Capacity, Alignment, Cap, Align
258+
>::value, "conversion not allowed");
259+
}
260+
261+
template<size_t Cap, size_t Align>
262+
inplace_function(inplace_function<R(Args...), Cap, Align>&& other) noexcept
263+
: inplace_function(other.vtable_ptr_, other.vtable_ptr_->relocate_ptr, std::addressof(other.storage_))
264+
{
265+
static_assert(inplace_function_detail::is_valid_inplace_dst<
266+
Capacity, Alignment, Cap, Align
267+
>::value, "conversion not allowed");
268+
269+
other.vtable_ptr_ = std::addressof(inplace_function_detail::empty_vtable<R, Args...>);
270+
}
271+
272+
inplace_function(std::nullptr_t) noexcept :
273+
vtable_ptr_{std::addressof(inplace_function_detail::empty_vtable<R, Args...>)}
274+
{}
275+
276+
inplace_function(const inplace_function& other) :
277+
vtable_ptr_{other.vtable_ptr_}
278+
{
279+
vtable_ptr_->copy_ptr(
280+
std::addressof(storage_),
281+
std::addressof(other.storage_)
282+
);
283+
}
284+
285+
inplace_function(inplace_function&& other) noexcept :
286+
vtable_ptr_{std::exchange(other.vtable_ptr_, std::addressof(inplace_function_detail::empty_vtable<R, Args...>))}
287+
{
288+
vtable_ptr_->relocate_ptr(
289+
std::addressof(storage_),
290+
std::addressof(other.storage_)
291+
);
292+
}
293+
294+
inplace_function& operator= (std::nullptr_t) noexcept
295+
{
296+
vtable_ptr_->destructor_ptr(std::addressof(storage_));
297+
vtable_ptr_ = std::addressof(inplace_function_detail::empty_vtable<R, Args...>);
298+
return *this;
299+
}
300+
301+
inplace_function& operator= (inplace_function other) noexcept
302+
{
303+
vtable_ptr_->destructor_ptr(std::addressof(storage_));
304+
305+
vtable_ptr_ = std::exchange(other.vtable_ptr_, std::addressof(inplace_function_detail::empty_vtable<R, Args...>));
306+
vtable_ptr_->relocate_ptr(
307+
std::addressof(storage_),
308+
std::addressof(other.storage_)
309+
);
310+
return *this;
311+
}
312+
313+
~inplace_function()
314+
{
315+
vtable_ptr_->destructor_ptr(std::addressof(storage_));
316+
}
317+
318+
R operator() (Args... args) const
319+
{
320+
return vtable_ptr_->invoke_ptr(
321+
std::addressof(storage_),
322+
std::forward<Args>(args)...
323+
);
324+
}
325+
326+
constexpr bool operator== (std::nullptr_t) const noexcept
327+
{
328+
return !operator bool();
329+
}
330+
331+
constexpr bool operator!= (std::nullptr_t) const noexcept
332+
{
333+
return operator bool();
334+
}
335+
336+
explicit constexpr operator bool() const noexcept
337+
{
338+
return vtable_ptr_ != std::addressof(inplace_function_detail::empty_vtable<R, Args...>);
339+
}
340+
341+
void swap(inplace_function& other) noexcept
342+
{
343+
if (this == std::addressof(other)) return;
344+
345+
storage_t tmp;
346+
vtable_ptr_->relocate_ptr(
347+
std::addressof(tmp),
348+
std::addressof(storage_)
349+
);
350+
351+
other.vtable_ptr_->relocate_ptr(
352+
std::addressof(storage_),
353+
std::addressof(other.storage_)
354+
);
355+
356+
vtable_ptr_->relocate_ptr(
357+
std::addressof(other.storage_),
358+
std::addressof(tmp)
359+
);
360+
361+
std::swap(vtable_ptr_, other.vtable_ptr_);
362+
}
363+
364+
friend void swap(inplace_function& lhs, inplace_function& rhs) noexcept
365+
{
366+
lhs.swap(rhs);
367+
}
368+
369+
private:
370+
vtable_ptr_t vtable_ptr_;
371+
mutable storage_t storage_;
372+
373+
inplace_function(
374+
vtable_ptr_t vtable_ptr,
375+
typename vtable_t::process_ptr_t process_ptr,
376+
typename vtable_t::storage_ptr_t storage_ptr
377+
) : vtable_ptr_{vtable_ptr}
378+
{
379+
process_ptr(std::addressof(storage_), storage_ptr);
380+
}
381+
};
382+
383+
} // namespace stdext

0 commit comments

Comments
 (0)