.. and also the follow-ups r348336 r348338.
It broke stand-alone compiler-rt builds with GCC 4.8:
In file included from /work/llvm/projects/compiler-rt/lib/xray/xray_function_call_trie.h:20:0,
from /work/llvm/projects/compiler-rt/lib/xray/xray_profile_collector.h:21,
from /work/llvm/projects/compiler-rt/lib/xray/xray_profile_collector.cc:15:
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h: In instantiation of ‘T* __xray::Array<T>::AppendEmplace(Args&& ...) [with Args = {const __xray::FunctionCallTrie::mergeInto(__xray::FunctionCallTrie&) const::NodeAndTarget&}; T = __xray::FunctionCallTrie::mergeInto(__xray::FunctionCallTrie&) const::NodeAndTarget]’:
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h:383:71: required from ‘T* __xray::Array<T>::Append(const T&) [with T = __xray::FunctionCallTrie::mergeInto(__xray::FunctionCallTrie&) const::NodeAndTarget]’
/work/llvm/projects/compiler-rt/lib/xray/xray_function_call_trie.h:517:54: required from here
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h:378:5: error: could not convert ‘{std::forward<const __xray::FunctionCallTrie::mergeInto(__xray::FunctionCallTrie&) const::NodeAndTarget&>((* & args#0))}’ from ‘<brace-enclosed initializer list>’ to ‘__xray::FunctionCallTrie::mergeInto(__xray::FunctionCallTrie&) const::NodeAndTarget’
new (AlignedOffset) T{std::forward<Args>(args)...};
^
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h: In instantiation of ‘T* __xray::Array<T>::AppendEmplace(Args&& ...) [with Args = {const __xray::profileCollectorService::{anonymous}::ThreadTrie&}; T = __xray::profileCollectorService::{anonymous}::ThreadTrie]’:
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h:383:71: required from ‘T* __xray::Array<T>::Append(const T&) [with T = __xray::profileCollectorService::{anonymous}::ThreadTrie]’
/work/llvm/projects/compiler-rt/lib/xray/xray_profile_collector.cc:98:34: required from here
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h:378:5: error: could not convert ‘{std::forward<const __xray::profileCollectorService::{anonymous}::ThreadTrie&>((* & args#0))}’ from
‘<brace-enclosed initializer list>’ to ‘__xray::profileCollectorService::{anonymous}::ThreadTrie’
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h: In instantiation of ‘T* __xray::Array<T>::AppendEmplace(Args&& ...) [with Args = {const __xray::profileCollectorService::{anonymous}::ProfileBuffer&}; T = __xray::profileCollectorService::{anonymous}::ProfileBuffer]’:
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h:383:71: required from ‘T* __xray::Array<T>::Append(const T&) [with T = __xray::profileCollectorService::{anonymous}::ProfileBuffer]
’
/work/llvm/projects/compiler-rt/lib/xray/xray_profile_collector.cc:244:44: required from here
/work/llvm/projects/compiler-rt/lib/xray/xray_segmented_array.h:378:5: error: could not convert ‘{std::forward<const __xray::profileCollectorService::{anonymous}::ProfileBuffer&>((* & args#0))}’ from ‘<brace-enclosed initializer list>’ to ‘__xray::profileCollectorService::{anonymous}::ProfileBuffer’
> Summary:
> This change makes the allocator and function call trie implementations
> move-aware and remove the FunctionCallTrie's reliance on a
> heap-allocated set of allocators.
>
> The change makes it possible to always have storage associated with
> Allocator instances, not necessarily having heap-allocated memory
> obtainable from these allocator instances. We also use thread-local
> uninitialised storage.
>
> We've also re-worked the segmented array implementation to have more
> precondition and post-condition checks when built in debug mode. This
> enables us to better implement some of the operations with surrounding
> documentation as well. The `trim` algorithm now has more documentation
> on the implementation, reducing the requirement to handle special
> conditions, and being more rigorous on the computations involved.
>
> In this change we also introduce an initialisation guard, through which
> we prevent an initialisation operation from racing with a cleanup
> operation.
>
> We also ensure that the ThreadTries array is not destroyed while copies
> into the elements are still being performed by other threads submitting
> profiles.
>
> Note that this change still has an issue with accessing thread-local
> storage from signal handlers that are instrumented with XRay. We also
> learn that with the testing of this patch, that there will be cases
> where calls to mmap(...) (through internal_mmap(...)) might be called in
> signal handlers, but are not async-signal-safe. Subsequent patches will
> address this, by re-using the `BufferQueue` type used in the FDR mode
> implementation for pre-allocated memory segments per active, tracing
> thread.
>
> We still want to land this change despite the known issues, with fixes
> forthcoming.
>
> Reviewers: mboerger, jfb
>
> Subscribers: jfb, llvm-commits
>
> Differential Revision: https://reviews.llvm.org/D54989
llvm-svn: 348346
226 lines
6.5 KiB
C++
226 lines
6.5 KiB
C++
#include "test_helpers.h"
|
|
#include "xray_segmented_array.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace __xray {
|
|
namespace {
|
|
|
|
using ::testing::SizeIs;
|
|
|
|
struct TestData {
|
|
s64 First;
|
|
s64 Second;
|
|
|
|
// Need a constructor for emplace operations.
|
|
TestData(s64 F, s64 S) : First(F), Second(S) {}
|
|
};
|
|
|
|
void PrintTo(const TestData &D, std::ostream *OS) {
|
|
*OS << "{ " << D.First << ", " << D.Second << " }";
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, ConstructWithAllocators) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 4);
|
|
Array<TestData> Data(A);
|
|
(void)Data;
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, ConstructAndPopulate) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 4);
|
|
Array<TestData> data(A);
|
|
ASSERT_NE(data.Append(TestData{0, 0}), nullptr);
|
|
ASSERT_NE(data.Append(TestData{1, 1}), nullptr);
|
|
ASSERT_EQ(data.size(), 2u);
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, ConstructPopulateAndLookup) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 4);
|
|
Array<TestData> data(A);
|
|
ASSERT_NE(data.Append(TestData{0, 1}), nullptr);
|
|
ASSERT_EQ(data.size(), 1u);
|
|
ASSERT_EQ(data[0].First, 0);
|
|
ASSERT_EQ(data[0].Second, 1);
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, PopulateWithMoreElements) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 24);
|
|
Array<TestData> data(A);
|
|
static const auto kMaxElements = 100u;
|
|
for (auto I = 0u; I < kMaxElements; ++I) {
|
|
ASSERT_NE(data.Append(TestData{I, I + 1}), nullptr);
|
|
}
|
|
ASSERT_EQ(data.size(), kMaxElements);
|
|
for (auto I = 0u; I < kMaxElements; ++I) {
|
|
ASSERT_EQ(data[I].First, I);
|
|
ASSERT_EQ(data[I].Second, I + 1);
|
|
}
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, AppendEmplace) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 4);
|
|
Array<TestData> data(A);
|
|
ASSERT_NE(data.AppendEmplace(1, 1), nullptr);
|
|
ASSERT_EQ(data[0].First, 1);
|
|
ASSERT_EQ(data[0].Second, 1);
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, AppendAndTrim) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 4);
|
|
Array<TestData> data(A);
|
|
ASSERT_NE(data.AppendEmplace(1, 1), nullptr);
|
|
ASSERT_EQ(data.size(), 1u);
|
|
data.trim(1);
|
|
ASSERT_EQ(data.size(), 0u);
|
|
ASSERT_TRUE(data.empty());
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, IteratorAdvance) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 4);
|
|
Array<TestData> data(A);
|
|
ASSERT_TRUE(data.empty());
|
|
ASSERT_EQ(data.begin(), data.end());
|
|
auto I0 = data.begin();
|
|
ASSERT_EQ(I0++, data.begin());
|
|
ASSERT_NE(I0, data.begin());
|
|
for (const auto &D : data) {
|
|
(void)D;
|
|
FAIL();
|
|
}
|
|
ASSERT_NE(data.AppendEmplace(1, 1), nullptr);
|
|
ASSERT_EQ(data.size(), 1u);
|
|
ASSERT_NE(data.begin(), data.end());
|
|
auto &D0 = *data.begin();
|
|
ASSERT_EQ(D0.First, 1);
|
|
ASSERT_EQ(D0.Second, 1);
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, IteratorRetreat) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 4);
|
|
Array<TestData> data(A);
|
|
ASSERT_TRUE(data.empty());
|
|
ASSERT_EQ(data.begin(), data.end());
|
|
ASSERT_NE(data.AppendEmplace(1, 1), nullptr);
|
|
ASSERT_EQ(data.size(), 1u);
|
|
ASSERT_NE(data.begin(), data.end());
|
|
auto &D0 = *data.begin();
|
|
ASSERT_EQ(D0.First, 1);
|
|
ASSERT_EQ(D0.Second, 1);
|
|
|
|
auto I0 = data.end();
|
|
ASSERT_EQ(I0--, data.end());
|
|
ASSERT_NE(I0, data.end());
|
|
ASSERT_EQ(I0, data.begin());
|
|
ASSERT_EQ(I0->First, 1);
|
|
ASSERT_EQ(I0->Second, 1);
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, IteratorTrimBehaviour) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
AllocatorType A(1 << 20);
|
|
Array<TestData> Data(A);
|
|
ASSERT_TRUE(Data.empty());
|
|
auto I0Begin = Data.begin(), I0End = Data.end();
|
|
// Add enough elements in Data to have more than one chunk.
|
|
constexpr auto Segment = Array<TestData>::SegmentSize;
|
|
constexpr auto SegmentX2 = Segment * 2;
|
|
for (auto i = SegmentX2; i > 0u; --i) {
|
|
Data.AppendEmplace(static_cast<s64>(i), static_cast<s64>(i));
|
|
}
|
|
ASSERT_EQ(Data.size(), SegmentX2);
|
|
{
|
|
auto &Back = Data.back();
|
|
ASSERT_EQ(Back.First, 1);
|
|
ASSERT_EQ(Back.Second, 1);
|
|
}
|
|
|
|
// Trim one chunk's elements worth.
|
|
Data.trim(Segment);
|
|
ASSERT_EQ(Data.size(), Segment);
|
|
|
|
// Check that we are still able to access 'back' properly.
|
|
{
|
|
auto &Back = Data.back();
|
|
ASSERT_EQ(Back.First, static_cast<s64>(Segment + 1));
|
|
ASSERT_EQ(Back.Second, static_cast<s64>(Segment + 1));
|
|
}
|
|
|
|
// Then trim until it's empty.
|
|
Data.trim(Segment);
|
|
ASSERT_TRUE(Data.empty());
|
|
|
|
// Here our iterators should be the same.
|
|
auto I1Begin = Data.begin(), I1End = Data.end();
|
|
EXPECT_EQ(I0Begin, I1Begin);
|
|
EXPECT_EQ(I0End, I1End);
|
|
|
|
// Then we ensure that adding elements back works just fine.
|
|
for (auto i = SegmentX2; i > 0u; --i) {
|
|
Data.AppendEmplace(static_cast<s64>(i), static_cast<s64>(i));
|
|
}
|
|
EXPECT_EQ(Data.size(), SegmentX2);
|
|
}
|
|
|
|
TEST(SegmentedArrayTest, HandleExhaustedAllocator) {
|
|
using AllocatorType = typename Array<TestData>::AllocatorType;
|
|
constexpr auto Segment = Array<TestData>::SegmentSize;
|
|
constexpr auto MaxElements = Array<TestData>::ElementsPerSegment;
|
|
AllocatorType A(Segment);
|
|
Array<TestData> Data(A);
|
|
for (auto i = MaxElements; i > 0u; --i)
|
|
EXPECT_NE(Data.AppendEmplace(static_cast<s64>(i), static_cast<s64>(i)),
|
|
nullptr);
|
|
EXPECT_EQ(Data.AppendEmplace(0, 0), nullptr);
|
|
EXPECT_THAT(Data, SizeIs(MaxElements));
|
|
|
|
// Trimming more elements than there are in the container should be fine.
|
|
Data.trim(MaxElements + 1);
|
|
EXPECT_THAT(Data, SizeIs(0u));
|
|
}
|
|
|
|
struct ShadowStackEntry {
|
|
uint64_t EntryTSC = 0;
|
|
uint64_t *NodePtr = nullptr;
|
|
ShadowStackEntry(uint64_t T, uint64_t *N) : EntryTSC(T), NodePtr(N) {}
|
|
};
|
|
|
|
TEST(SegmentedArrayTest, SimulateStackBehaviour) {
|
|
using AllocatorType = typename Array<ShadowStackEntry>::AllocatorType;
|
|
AllocatorType A(1 << 10);
|
|
Array<ShadowStackEntry> Data(A);
|
|
static uint64_t Dummy = 0;
|
|
constexpr uint64_t Max = 9;
|
|
|
|
for (uint64_t i = 0; i < Max; ++i) {
|
|
auto P = Data.Append({i, &Dummy});
|
|
ASSERT_NE(P, nullptr);
|
|
ASSERT_EQ(P->NodePtr, &Dummy);
|
|
auto &Back = Data.back();
|
|
ASSERT_EQ(Back.NodePtr, &Dummy);
|
|
ASSERT_EQ(Back.EntryTSC, i);
|
|
}
|
|
|
|
// Simulate a stack by checking the data from the end as we're trimming.
|
|
auto Counter = Max;
|
|
ASSERT_EQ(Data.size(), size_t(Max));
|
|
while (!Data.empty()) {
|
|
const auto &Top = Data.back();
|
|
uint64_t *TopNode = Top.NodePtr;
|
|
EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter;
|
|
Data.trim(1);
|
|
--Counter;
|
|
ASSERT_EQ(Data.size(), size_t(Counter));
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace __xray
|