diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2018-07-28 10:51:19 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2018-07-28 10:51:19 +0000 |
commit | eb11fae6d08f479c0799db45860a98af528fa6e7 (patch) | |
tree | 44d492a50c8c1a7eb8e2d17ea3360ec4d066f042 /unittests | |
parent | b8a2042aa938069e862750553db0e4d82d25822c (diff) |
Notes
Diffstat (limited to 'unittests')
179 files changed, 13692 insertions, 1195 deletions
diff --git a/unittests/ADT/APFloatTest.cpp b/unittests/ADT/APFloatTest.cpp index 8b88c123b197..1212b45fb575 100644 --- a/unittests/ADT/APFloatTest.cpp +++ b/unittests/ADT/APFloatTest.cpp @@ -983,6 +983,13 @@ TEST(APFloatTest, toString) { ASSERT_EQ("8.73183400000000010e+02", convertToString(873.1834, 0, 0, false)); ASSERT_EQ("1.79769313486231570e+308", convertToString(1.7976931348623157E+308, 0, 0, false)); + + { + SmallString<64> Str; + APFloat UnnormalZero(APFloat::x87DoubleExtended(), APInt(80, {0, 1})); + UnnormalZero.toString(Str); + ASSERT_EQ("NaN", Str); + } } TEST(APFloatTest, toInteger) { diff --git a/unittests/ADT/APIntTest.cpp b/unittests/ADT/APIntTest.cpp index 05fad386064c..48f91195e446 100644 --- a/unittests/ADT/APIntTest.cpp +++ b/unittests/ADT/APIntTest.cpp @@ -1060,6 +1060,38 @@ TEST(APIntTest, divremuint) { APInt{1024, 1}); } +TEST(APIntTest, divrem_simple) { + // Test simple cases. + APInt A(65, 2), B(65, 2); + APInt Q, R; + + // X / X + APInt::sdivrem(A, B, Q, R); + EXPECT_EQ(Q, APInt(65, 1)); + EXPECT_EQ(R, APInt(65, 0)); + APInt::udivrem(A, B, Q, R); + EXPECT_EQ(Q, APInt(65, 1)); + EXPECT_EQ(R, APInt(65, 0)); + + // 0 / X + APInt O(65, 0); + APInt::sdivrem(O, B, Q, R); + EXPECT_EQ(Q, APInt(65, 0)); + EXPECT_EQ(R, APInt(65, 0)); + APInt::udivrem(O, B, Q, R); + EXPECT_EQ(Q, APInt(65, 0)); + EXPECT_EQ(R, APInt(65, 0)); + + // X / 1 + APInt I(65, 1); + APInt::sdivrem(A, I, Q, R); + EXPECT_EQ(Q, A); + EXPECT_EQ(R, APInt(65, 0)); + APInt::udivrem(A, I, Q, R); + EXPECT_EQ(Q, A); + EXPECT_EQ(R, APInt(65, 0)); +} + TEST(APIntTest, fromString) { EXPECT_EQ(APInt(32, 0), APInt(32, "0", 2)); EXPECT_EQ(APInt(32, 1), APInt(32, "1", 2)); @@ -1659,6 +1691,38 @@ TEST(APIntTest, isShiftedMask) { } } +// Test that self-move works, but only when we're using MSVC. +#if defined(_MSC_VER) +#if defined(__clang__) +// Disable the pragma warning from versions of Clang without -Wself-move +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunknown-pragmas" +// Disable the warning that triggers on exactly what is being tested. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wself-move" +#endif +TEST(APIntTest, SelfMoveAssignment) { + APInt X(32, 0xdeadbeef); + X = std::move(X); + EXPECT_EQ(32u, X.getBitWidth()); + EXPECT_EQ(0xdeadbeefULL, X.getLimitedValue()); + + uint64_t Bits[] = {0xdeadbeefdeadbeefULL, 0xdeadbeefdeadbeefULL}; + APInt Y(128, Bits); + Y = std::move(Y); + EXPECT_EQ(128u, Y.getBitWidth()); + EXPECT_EQ(~0ULL, Y.getLimitedValue()); + const uint64_t *Raw = Y.getRawData(); + EXPECT_EQ(2u, Y.getNumWords()); + EXPECT_EQ(0xdeadbeefdeadbeefULL, Raw[0]); + EXPECT_EQ(0xdeadbeefdeadbeefULL, Raw[1]); +} +#if defined(__clang__) +#pragma clang diagnostic pop +#pragma clang diagnostic pop +#endif +#endif // _MSC_VER + TEST(APIntTest, reverseBits) { EXPECT_EQ(1, APInt(1, 1).reverseBits()); EXPECT_EQ(0, APInt(1, 0).reverseBits()); @@ -1768,6 +1832,13 @@ TEST(APIntTest, extractBits) { i257.extractBits(128, 1).getSExtValue()); EXPECT_EQ(static_cast<int64_t>(0xFFFFFFFFFF80007Full), i257.extractBits(129, 1).getSExtValue()); + + EXPECT_EQ(APInt(48, 0), + APInt(144, "281474976710655", 10).extractBits(48, 48)); + EXPECT_EQ(APInt(48, 0x0000ffffffffffffull), + APInt(144, "281474976710655", 10).extractBits(48, 0)); + EXPECT_EQ(APInt(48, 0x00007fffffffffffull), + APInt(144, "281474976710655", 10).extractBits(48, 1)); } TEST(APIntTest, getLowBitsSet) { @@ -2219,4 +2290,71 @@ TEST(APIntTest, multiply) { EXPECT_EQ(64U, i96.countTrailingZeros()); } +TEST(APIntTest, RoundingUDiv) { + for (uint64_t Ai = 1; Ai <= 255; Ai++) { + APInt A(8, Ai); + APInt Zero(8, 0); + EXPECT_EQ(0, APIntOps::RoundingUDiv(Zero, A, APInt::Rounding::UP)); + EXPECT_EQ(0, APIntOps::RoundingUDiv(Zero, A, APInt::Rounding::DOWN)); + EXPECT_EQ(0, APIntOps::RoundingUDiv(Zero, A, APInt::Rounding::TOWARD_ZERO)); + + for (uint64_t Bi = 1; Bi <= 255; Bi++) { + APInt B(8, Bi); + { + APInt Quo = APIntOps::RoundingUDiv(A, B, APInt::Rounding::UP); + auto Prod = Quo.zext(16) * B.zext(16); + EXPECT_TRUE(Prod.uge(Ai)); + if (Prod.ugt(Ai)) { + EXPECT_TRUE(((Quo - 1).zext(16) * B.zext(16)).ult(Ai)); + } + } + { + APInt Quo = A.udiv(B); + EXPECT_EQ(Quo, APIntOps::RoundingUDiv(A, B, APInt::Rounding::TOWARD_ZERO)); + EXPECT_EQ(Quo, APIntOps::RoundingUDiv(A, B, APInt::Rounding::DOWN)); + } + } + } +} + +TEST(APIntTest, RoundingSDiv) { + for (int64_t Ai = -128; Ai <= 127; Ai++) { + APInt A(8, Ai); + + if (Ai != 0) { + APInt Zero(8, 0); + EXPECT_EQ(0, APIntOps::RoundingSDiv(Zero, A, APInt::Rounding::UP)); + EXPECT_EQ(0, APIntOps::RoundingSDiv(Zero, A, APInt::Rounding::DOWN)); + EXPECT_EQ(0, APIntOps::RoundingSDiv(Zero, A, APInt::Rounding::TOWARD_ZERO)); + } + + for (uint64_t Bi = -128; Bi <= 127; Bi++) { + if (Bi == 0) + continue; + + APInt B(8, Bi); + { + APInt Quo = APIntOps::RoundingSDiv(A, B, APInt::Rounding::UP); + auto Prod = Quo.sext(16) * B.sext(16); + EXPECT_TRUE(Prod.uge(A)); + if (Prod.ugt(A)) { + EXPECT_TRUE(((Quo - 1).sext(16) * B.sext(16)).ult(A)); + } + } + { + APInt Quo = APIntOps::RoundingSDiv(A, B, APInt::Rounding::DOWN); + auto Prod = Quo.sext(16) * B.sext(16); + EXPECT_TRUE(Prod.ule(A)); + if (Prod.ult(A)) { + EXPECT_TRUE(((Quo + 1).sext(16) * B.sext(16)).ugt(A)); + } + } + { + APInt Quo = A.sdiv(B); + EXPECT_EQ(Quo, APIntOps::RoundingSDiv(A, B, APInt::Rounding::TOWARD_ZERO)); + } + } + } +} + } // end anonymous namespace diff --git a/unittests/ADT/AnyTest.cpp b/unittests/ADT/AnyTest.cpp new file mode 100644 index 000000000000..658f6b6ce3b7 --- /dev/null +++ b/unittests/ADT/AnyTest.cpp @@ -0,0 +1,175 @@ +//===- llvm/unittest/Support/AnyTest.cpp - Any tests ---===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/Any.h" +#include "gtest/gtest.h" +#include <cstdlib> + +using namespace llvm; + +namespace { + +// Make sure we can construct, copy-construct, move-construct, and assign Any's. +TEST(AnyTest, ConstructionAndAssignment) { + llvm::Any A; + llvm::Any B{7}; + llvm::Any C{8}; + llvm::Any D{"hello"}; + llvm::Any E{3.7}; + + // An empty Any is not anything. + EXPECT_FALSE(A.hasValue()); + EXPECT_FALSE(any_isa<int>(A)); + + // An int is an int but not something else. + EXPECT_TRUE(B.hasValue()); + EXPECT_TRUE(any_isa<int>(B)); + EXPECT_FALSE(any_isa<float>(B)); + + EXPECT_TRUE(C.hasValue()); + EXPECT_TRUE(any_isa<int>(C)); + + // A const char * is a const char * but not an int. + EXPECT_TRUE(D.hasValue()); + EXPECT_TRUE(any_isa<const char *>(D)); + EXPECT_FALSE(any_isa<int>(D)); + + // A double is a double but not a float. + EXPECT_TRUE(E.hasValue()); + EXPECT_TRUE(any_isa<double>(E)); + EXPECT_FALSE(any_isa<float>(E)); + + // After copy constructing from an int, the new item and old item are both + // ints. + llvm::Any F(B); + EXPECT_TRUE(B.hasValue()); + EXPECT_TRUE(F.hasValue()); + EXPECT_TRUE(any_isa<int>(F)); + EXPECT_TRUE(any_isa<int>(B)); + + // After move constructing from an int, the new item is an int and the old one + // isn't. + llvm::Any G(std::move(C)); + EXPECT_FALSE(C.hasValue()); + EXPECT_TRUE(G.hasValue()); + EXPECT_TRUE(any_isa<int>(G)); + EXPECT_FALSE(any_isa<int>(C)); + + // After copy-assigning from an int, the new item and old item are both ints. + A = F; + EXPECT_TRUE(A.hasValue()); + EXPECT_TRUE(F.hasValue()); + EXPECT_TRUE(any_isa<int>(A)); + EXPECT_TRUE(any_isa<int>(F)); + + // After move-assigning from an int, the new item and old item are both ints. + B = std::move(G); + EXPECT_TRUE(B.hasValue()); + EXPECT_FALSE(G.hasValue()); + EXPECT_TRUE(any_isa<int>(B)); + EXPECT_FALSE(any_isa<int>(G)); +} + +TEST(AnyTest, GoodAnyCast) { + llvm::Any A; + llvm::Any B{7}; + llvm::Any C{8}; + llvm::Any D{"hello"}; + llvm::Any E{'x'}; + + // Check each value twice to make sure it isn't damaged by the cast. + EXPECT_EQ(7, llvm::any_cast<int>(B)); + EXPECT_EQ(7, llvm::any_cast<int>(B)); + + EXPECT_STREQ("hello", llvm::any_cast<const char *>(D)); + EXPECT_STREQ("hello", llvm::any_cast<const char *>(D)); + + EXPECT_EQ('x', llvm::any_cast<char>(E)); + EXPECT_EQ('x', llvm::any_cast<char>(E)); + + llvm::Any F(B); + EXPECT_EQ(7, llvm::any_cast<int>(F)); + EXPECT_EQ(7, llvm::any_cast<int>(F)); + + llvm::Any G(std::move(C)); + EXPECT_EQ(8, llvm::any_cast<int>(G)); + EXPECT_EQ(8, llvm::any_cast<int>(G)); + + A = F; + EXPECT_EQ(7, llvm::any_cast<int>(A)); + EXPECT_EQ(7, llvm::any_cast<int>(A)); + + E = std::move(G); + EXPECT_EQ(8, llvm::any_cast<int>(E)); + EXPECT_EQ(8, llvm::any_cast<int>(E)); + + // Make sure we can any_cast from an rvalue and that it's properly destroyed + // in the process. + EXPECT_EQ(8, llvm::any_cast<int>(std::move(E))); + EXPECT_TRUE(E.hasValue()); + + // Make sure moving from pointers gives back pointers, and that we can modify + // the underlying value through those pointers. + EXPECT_EQ(7, *llvm::any_cast<int>(&A)); + int *N = llvm::any_cast<int>(&A); + *N = 42; + EXPECT_EQ(42, llvm::any_cast<int>(A)); + + // Make sure that we can any_cast to a reference and this is considered a good + // cast, resulting in an lvalue which can be modified. + llvm::any_cast<int &>(A) = 43; + EXPECT_EQ(43, llvm::any_cast<int>(A)); +} + +TEST(AnyTest, CopiesAndMoves) { + struct TestType { + TestType() = default; + TestType(const TestType &Other) + : Copies(Other.Copies + 1), Moves(Other.Moves) {} + TestType(TestType &&Other) : Copies(Other.Copies), Moves(Other.Moves + 1) {} + int Copies = 0; + int Moves = 0; + }; + + // One move to get TestType into the Any, and one move on the cast. + TestType T1 = llvm::any_cast<TestType>(Any{TestType()}); + EXPECT_EQ(0, T1.Copies); + EXPECT_EQ(2, T1.Moves); + + // One move to get TestType into the Any, and one copy on the cast. + Any A{TestType()}; + TestType T2 = llvm::any_cast<TestType>(A); + EXPECT_EQ(1, T2.Copies); + EXPECT_EQ(1, T2.Moves); + + // One move to get TestType into the Any, and one move on the cast. + TestType T3 = llvm::any_cast<TestType>(std::move(A)); + EXPECT_EQ(0, T3.Copies); + EXPECT_EQ(2, T3.Moves); +} + +TEST(AnyTest, BadAnyCast) { + llvm::Any A; + llvm::Any B{7}; + llvm::Any C{"hello"}; + llvm::Any D{'x'}; + +#if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST + EXPECT_DEATH(llvm::any_cast<int>(A), ""); + + EXPECT_DEATH(llvm::any_cast<float>(B), ""); + EXPECT_DEATH(llvm::any_cast<int *>(B), ""); + + EXPECT_DEATH(llvm::any_cast<std::string>(C), ""); + + EXPECT_DEATH(llvm::any_cast<unsigned char>(D), ""); +#endif +} + +} // anonymous namespace diff --git a/unittests/ADT/ArrayRefTest.cpp b/unittests/ADT/ArrayRefTest.cpp index 4694ff112cb5..e01d212f218b 100644 --- a/unittests/ADT/ArrayRefTest.cpp +++ b/unittests/ADT/ArrayRefTest.cpp @@ -39,16 +39,16 @@ static_assert( // std::is_assignable and actually writing such an assignment. #if !defined(_MSC_VER) static_assert( - !std::is_assignable<ArrayRef<int *>, int *>::value, + !std::is_assignable<ArrayRef<int *>&, int *>::value, "Assigning from single prvalue element"); static_assert( - !std::is_assignable<ArrayRef<int *>, int * &&>::value, + !std::is_assignable<ArrayRef<int *>&, int * &&>::value, "Assigning from single xvalue element"); static_assert( - std::is_assignable<ArrayRef<int *>, int * &>::value, + std::is_assignable<ArrayRef<int *>&, int * &>::value, "Assigning from single lvalue element"); static_assert( - !std::is_assignable<ArrayRef<int *>, std::initializer_list<int *>>::value, + !std::is_assignable<ArrayRef<int *>&, std::initializer_list<int *>>::value, "Assigning from an initializer list"); #endif diff --git a/unittests/ADT/CMakeLists.txt b/unittests/ADT/CMakeLists.txt index c0d511000f61..19657da8bcee 100644 --- a/unittests/ADT/CMakeLists.txt +++ b/unittests/ADT/CMakeLists.txt @@ -2,7 +2,8 @@ set(LLVM_LINK_COMPONENTS Support ) -set(ADTSources +add_llvm_unittest(ADTTests + AnyTest.cpp APFloatTest.cpp APIntTest.cpp APSIntTest.cpp @@ -18,6 +19,7 @@ set(ADTSources DepthFirstIteratorTest.cpp EquivalenceClassesTest.cpp FoldingSet.cpp + FunctionExtrasTest.cpp FunctionRefTest.cpp HashingTest.cpp IListBaseTest.cpp @@ -51,11 +53,13 @@ set(ADTSources SetVectorTest.cpp SimpleIListTest.cpp SmallPtrSetTest.cpp + SmallSetTest.cpp SmallStringTest.cpp SmallVectorTest.cpp SparseBitVectorTest.cpp SparseMultiSetTest.cpp SparseSetTest.cpp + StatisticTest.cpp StringExtrasTest.cpp StringMapTest.cpp StringRefTest.cpp @@ -64,10 +68,6 @@ set(ADTSources TripleTest.cpp TwineTest.cpp VariadicFunctionTest.cpp - ) - -add_llvm_unittest(ADTTests - ${ADTSources} ) add_dependencies(ADTTests intrinsics_gen) diff --git a/unittests/ADT/DenseMapTest.cpp b/unittests/ADT/DenseMapTest.cpp index cb5ba6875eaa..87f22f6f403e 100644 --- a/unittests/ADT/DenseMapTest.cpp +++ b/unittests/ADT/DenseMapTest.cpp @@ -30,7 +30,7 @@ uint32_t *getTestValue(int i, uint32_t **) { return &dummy_arr1[i]; } -/// \brief A test class that tries to check that construction and destruction +/// A test class that tries to check that construction and destruction /// occur correctly. class CtorTester { static std::set<CtorTester *> Constructed; @@ -247,7 +247,7 @@ TYPED_TEST(DenseMapTest, AssignmentTest) { EXPECT_EQ(this->getValue(), copyMap[this->getKey()]); // test self-assignment. - copyMap = copyMap; + copyMap = static_cast<TypeParam &>(copyMap); EXPECT_EQ(1u, copyMap.size()); EXPECT_EQ(this->getValue(), copyMap[this->getKey()]); } @@ -262,7 +262,7 @@ TYPED_TEST(DenseMapTest, AssignmentTestNotSmall) { EXPECT_EQ(this->getValue(Key), copyMap[this->getKey(Key)]); // test self-assignment. - copyMap = copyMap; + copyMap = static_cast<TypeParam &>(copyMap); EXPECT_EQ(5u, copyMap.size()); for (int Key = 0; Key < 5; ++Key) EXPECT_EQ(this->getValue(Key), copyMap[this->getKey(Key)]); @@ -384,7 +384,7 @@ TEST(DenseMapCustomTest, DefaultMinReservedSizeTest) { EXPECT_EQ(MemorySize, Map.getMemorySize()); // Check that move was called the expected number of times EXPECT_EQ(ExpectedMaxInitialEntries, CountCopyAndMove::Move); - // Check that no copy occured + // Check that no copy occurred EXPECT_EQ(0, CountCopyAndMove::Copy); // Adding one extra element should grow the map @@ -397,7 +397,7 @@ TEST(DenseMapCustomTest, DefaultMinReservedSizeTest) { // Check that move was called the expected number of times // This relies on move-construction elision, and cannot be reliably tested. // EXPECT_EQ(ExpectedMaxInitialEntries + 2, CountCopyAndMove::Move); - // Check that no copy occured + // Check that no copy occurred EXPECT_EQ(0, CountCopyAndMove::Copy); } @@ -422,7 +422,7 @@ TEST(DenseMapCustomTest, InitialSizeTest) { EXPECT_EQ(MemorySize, Map.getMemorySize()); // Check that move was called the expected number of times EXPECT_EQ(Size, CountCopyAndMove::Move); - // Check that no copy occured + // Check that no copy occurred EXPECT_EQ(0, CountCopyAndMove::Copy); } } @@ -438,7 +438,7 @@ TEST(DenseMapCustomTest, InitFromIterator) { CountCopyAndMove::Move = 0; CountCopyAndMove::Copy = 0; DenseMap<int, CountCopyAndMove> Map(Values.begin(), Values.end()); - // Check that no move occured + // Check that no move occurred EXPECT_EQ(0, CountCopyAndMove::Move); // Check that copy was called the expected number of times EXPECT_EQ(Count, CountCopyAndMove::Copy); @@ -466,7 +466,7 @@ TEST(DenseMapCustomTest, ReserveTest) { EXPECT_EQ(MemorySize, Map.getMemorySize()); // Check that move was called the expected number of times EXPECT_EQ(Size, CountCopyAndMove::Move); - // Check that no copy occured + // Check that no copy occurred EXPECT_EQ(0, CountCopyAndMove::Copy); } } diff --git a/unittests/ADT/DenseSetTest.cpp b/unittests/ADT/DenseSetTest.cpp index a09537a3e990..03738e46be4a 100644 --- a/unittests/ADT/DenseSetTest.cpp +++ b/unittests/ADT/DenseSetTest.cpp @@ -181,7 +181,7 @@ TEST(DenseSetCustomTest, ReserveTest) { EXPECT_EQ(MemorySize, Set.getMemorySize()); // Check that move was called the expected number of times EXPECT_EQ(Size, CountCopyAndMove::Move); - // Check that no copy occured + // Check that no copy occurred EXPECT_EQ(0, CountCopyAndMove::Copy); } } diff --git a/unittests/ADT/FunctionExtrasTest.cpp b/unittests/ADT/FunctionExtrasTest.cpp new file mode 100644 index 000000000000..d85962e0f773 --- /dev/null +++ b/unittests/ADT/FunctionExtrasTest.cpp @@ -0,0 +1,228 @@ +//===- FunctionExtrasTest.cpp - Unit tests for function type erasure ------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/FunctionExtras.h" +#include "gtest/gtest.h" + +#include <memory> + +using namespace llvm; + +namespace { + +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{new 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); + + struct LargeCallable { + std::unique_ptr<int> A{new int(1)}; + std::unique_ptr<int> B{new int(2)}; + std::unique_ptr<int> C{new int(3)}; + std::unique_ptr<int> D{new int(4)}; + std::unique_ptr<int> E{new int(5)}; + + int operator()() { return *A + *B + *C + *D + *E; } + }; + unique_function<int()> Large = LargeCallable(); + EXPECT_EQ(Large(), 15); + unique_function<int()> Large2 = std::move(Large); + EXPECT_EQ(Large2(), 15); +} + +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, CountForwardingMoves) { + struct MoveCounter { + int &MoveCount; + + MoveCounter(int &MoveCount) : MoveCount(MoveCount) {} + MoveCounter(MoveCounter &&Arg) : MoveCount(Arg.MoveCount) { ++MoveCount; } + }; + + unique_function<void(MoveCounter)> ByValF = [](MoveCounter) {}; + int MoveCount = 0; + ByValF(MoveCounter(MoveCount)); + EXPECT_EQ(1, MoveCount); + + MoveCount = 0; + { + MoveCounter Counter{MoveCount}; + ByValF(std::move(Counter)); + } + EXPECT_EQ(2, MoveCount); + + // Check that when we use an r-value reference we get no spurious copies. + unique_function<void(MoveCounter &&)> ByRefF = [](MoveCounter &&) {}; + MoveCount = 0; + ByRefF(MoveCounter(MoveCount)); + EXPECT_EQ(0, MoveCount); + + MoveCount = 0; + { + MoveCounter Counter{MoveCount}; + ByRefF(std::move(Counter)); + } + EXPECT_EQ(0, MoveCount); + + // If we use an r-value reference we can in fact make a stronger guarantee + // with an unmovable type. + struct Unmovable { + Unmovable() = default; + Unmovable(Unmovable &&) = delete; + }; + unique_function<void(const Unmovable &)> UnmovableF = [](const Unmovable &) { + }; + UnmovableF(Unmovable()); + Unmovable X; + UnmovableF(X); +} + +} // anonymous namespace diff --git a/unittests/ADT/IteratorTest.cpp b/unittests/ADT/IteratorTest.cpp index c95ce8061847..50c3b01bbc74 100644 --- a/unittests/ADT/IteratorTest.cpp +++ b/unittests/ADT/IteratorTest.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "llvm/ADT/iterator.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallVector.h" #include "gtest/gtest.h" @@ -127,6 +128,20 @@ TEST(PointeeIteratorTest, Range) { EXPECT_EQ(A[I++], II); } +TEST(PointeeIteratorTest, PointeeType) { + struct S { + int X; + bool operator==(const S &RHS) const { return X == RHS.X; }; + }; + S A[] = {S{0}, S{1}}; + SmallVector<S *, 2> V{&A[0], &A[1]}; + + pointee_iterator<SmallVectorImpl<S *>::const_iterator, const S> I = V.begin(); + for (int j = 0; j < 2; ++j, ++I) { + EXPECT_EQ(*V[j], *I); + } +} + TEST(FilterIteratorTest, Lambda) { auto IsOdd = [](int N) { return N % 2 == 1; }; int A[] = {0, 1, 2, 3, 4, 5, 6}; @@ -196,6 +211,33 @@ TEST(FilterIteratorTest, InputIterator) { EXPECT_EQ((SmallVector<int, 3>{1, 3, 5}), Actual); } +TEST(FilterIteratorTest, ReverseFilterRange) { + auto IsOdd = [](int N) { return N % 2 == 1; }; + int A[] = {0, 1, 2, 3, 4, 5, 6}; + + // Check basic reversal. + auto Range = reverse(make_filter_range(A, IsOdd)); + SmallVector<int, 3> Actual(Range.begin(), Range.end()); + EXPECT_EQ((SmallVector<int, 3>{5, 3, 1}), Actual); + + // Check that the reverse of the reverse is the original. + auto Range2 = reverse(reverse(make_filter_range(A, IsOdd))); + SmallVector<int, 3> Actual2(Range2.begin(), Range2.end()); + EXPECT_EQ((SmallVector<int, 3>{1, 3, 5}), Actual2); + + // Check empty ranges. + auto Range3 = reverse(make_filter_range(ArrayRef<int>(), IsOdd)); + SmallVector<int, 0> Actual3(Range3.begin(), Range3.end()); + EXPECT_EQ((SmallVector<int, 0>{}), Actual3); + + // Check that we don't skip the first element, provided it isn't filtered + // away. + auto IsEven = [](int N) { return N % 2 == 0; }; + auto Range4 = reverse(make_filter_range(A, IsEven)); + SmallVector<int, 4> Actual4(Range4.begin(), Range4.end()); + EXPECT_EQ((SmallVector<int, 4>{6, 4, 2, 0}), Actual4); +} + TEST(PointerIterator, Basic) { int A[] = {1, 2, 3, 4}; pointer_iterator<int *> Begin(std::begin(A)), End(std::end(A)); @@ -337,4 +379,25 @@ TEST(ZipIteratorTest, Reverse) { EXPECT_TRUE(all_of(ascending, [](unsigned n) { return (n & 0x01) == 0; })); } +TEST(RangeTest, Distance) { + std::vector<int> v1; + std::vector<int> v2{1, 2, 3}; + + EXPECT_EQ(std::distance(v1.begin(), v1.end()), size(v1)); + EXPECT_EQ(std::distance(v2.begin(), v2.end()), size(v2)); +} + +TEST(IteratorRangeTest, DropBegin) { + SmallVector<int, 5> vec{0, 1, 2, 3, 4}; + + for (int n = 0; n < 5; ++n) { + int i = n; + for (auto &v : drop_begin(vec, n)) { + EXPECT_EQ(v, i); + i += 1; + } + EXPECT_EQ(i, 5); + } +} + } // anonymous namespace diff --git a/unittests/ADT/MapVectorTest.cpp b/unittests/ADT/MapVectorTest.cpp index bd6602b030f6..16e9b5a74f42 100644 --- a/unittests/ADT/MapVectorTest.cpp +++ b/unittests/ADT/MapVectorTest.cpp @@ -157,6 +157,45 @@ TEST(MapVectorTest, NonCopyable) { ASSERT_EQ(*MV.find(2)->second, 2); } +template <class IntType> struct MapVectorMappedTypeTest : ::testing::Test { + using int_type = IntType; +}; + +using MapIntTypes = ::testing::Types<int, long, long long, unsigned, + unsigned long, unsigned long long>; +TYPED_TEST_CASE(MapVectorMappedTypeTest, MapIntTypes); + +TYPED_TEST(MapVectorMappedTypeTest, DifferentDenseMap) { + // Test that using a map with a mapped type other than 'unsigned' compiles + // and works. + using IntType = typename TestFixture::int_type; + using MapVectorType = MapVector<int, int, DenseMap<int, IntType>>; + + MapVectorType MV; + std::pair<typename MapVectorType::iterator, bool> R; + + R = MV.insert(std::make_pair(1, 2)); + ASSERT_EQ(R.first, MV.begin()); + EXPECT_EQ(R.first->first, 1); + EXPECT_EQ(R.first->second, 2); + EXPECT_TRUE(R.second); + + const std::pair<int, int> Elem(1, 3); + R = MV.insert(Elem); + ASSERT_EQ(R.first, MV.begin()); + EXPECT_EQ(R.first->first, 1); + EXPECT_EQ(R.first->second, 2); + EXPECT_FALSE(R.second); + + int& value = MV[4]; + EXPECT_EQ(value, 0); + value = 5; + + EXPECT_EQ(MV.size(), 2u); + EXPECT_EQ(MV[1], 2); + EXPECT_EQ(MV[4], 5); +} + TEST(SmallMapVectorSmallTest, insert_pop) { SmallMapVector<int, int, 32> MV; std::pair<SmallMapVector<int, int, 32>::iterator, bool> R; diff --git a/unittests/ADT/OptionalTest.cpp b/unittests/ADT/OptionalTest.cpp index 46d4fe0780c3..2e09c5340fa3 100644 --- a/unittests/ADT/OptionalTest.cpp +++ b/unittests/ADT/OptionalTest.cpp @@ -268,12 +268,12 @@ TEST_F(OptionalTest, MoveOnlyMoveConstruction) { Optional<MoveOnly> A(MoveOnly(3)); MoveOnly::ResetCounts(); Optional<MoveOnly> B(std::move(A)); - EXPECT_FALSE((bool)A); + EXPECT_TRUE((bool)A); EXPECT_TRUE((bool)B); EXPECT_EQ(3, B->val); EXPECT_EQ(1u, MoveOnly::MoveConstructions); EXPECT_EQ(0u, MoveOnly::MoveAssignments); - EXPECT_EQ(1u, MoveOnly::Destructions); + EXPECT_EQ(0u, MoveOnly::Destructions); } TEST_F(OptionalTest, MoveOnlyAssignment) { @@ -292,12 +292,12 @@ TEST_F(OptionalTest, MoveOnlyInitializingAssignment) { Optional<MoveOnly> B; MoveOnly::ResetCounts(); B = std::move(A); - EXPECT_FALSE((bool)A); + EXPECT_TRUE((bool)A); EXPECT_TRUE((bool)B); EXPECT_EQ(3, B->val); EXPECT_EQ(1u, MoveOnly::MoveConstructions); EXPECT_EQ(0u, MoveOnly::MoveAssignments); - EXPECT_EQ(1u, MoveOnly::Destructions); + EXPECT_EQ(0u, MoveOnly::Destructions); } TEST_F(OptionalTest, MoveOnlyNullingAssignment) { @@ -317,12 +317,12 @@ TEST_F(OptionalTest, MoveOnlyAssigningAssignment) { Optional<MoveOnly> B(MoveOnly(4)); MoveOnly::ResetCounts(); B = std::move(A); - EXPECT_FALSE((bool)A); + EXPECT_TRUE((bool)A); EXPECT_TRUE((bool)B); EXPECT_EQ(3, B->val); EXPECT_EQ(0u, MoveOnly::MoveConstructions); EXPECT_EQ(1u, MoveOnly::MoveAssignments); - EXPECT_EQ(1u, MoveOnly::Destructions); + EXPECT_EQ(0u, MoveOnly::Destructions); } struct Immovable { @@ -518,5 +518,13 @@ TEST_F(OptionalTest, OperatorGreaterEqual) { CheckRelation<GreaterEqual>(InequalityLhs, InequalityRhs, !IsLess); } +#if __has_feature(is_trivially_copyable) && defined(_LIBCPP_VERSION) +static_assert(std::is_trivially_copyable<Optional<int>>::value, + "Should be trivially copyable"); +static_assert( + !std::is_trivially_copyable<Optional<NonDefaultConstructible>>::value, + "Shouldn't be trivially copyable"); +#endif + } // end anonymous namespace diff --git a/unittests/ADT/STLExtrasTest.cpp b/unittests/ADT/STLExtrasTest.cpp index 89e876eb4de9..d744a3cd521a 100644 --- a/unittests/ADT/STLExtrasTest.cpp +++ b/unittests/ADT/STLExtrasTest.cpp @@ -302,8 +302,8 @@ TEST(STLExtrasTest, PartitionAdaptor) { ASSERT_EQ(V.begin() + 4, I); // Sort the two halves as partition may have messed with the order. - std::sort(V.begin(), I); - std::sort(I, V.end()); + llvm::sort(V.begin(), I); + llvm::sort(I, V.end()); EXPECT_EQ(2, V[0]); EXPECT_EQ(4, V[1]); diff --git a/unittests/ADT/ScopeExitTest.cpp b/unittests/ADT/ScopeExitTest.cpp index 301942c30bbc..ab11dff8ce48 100644 --- a/unittests/ADT/ScopeExitTest.cpp +++ b/unittests/ADT/ScopeExitTest.cpp @@ -29,4 +29,21 @@ TEST(ScopeExitTest, Basic) { EXPECT_TRUE(Called); } +TEST(ScopeExitTest, Release) { + int Count = 0; + auto Increment = [&] { ++Count; }; + { + auto G = make_scope_exit(Increment); + auto H = std::move(G); + auto I = std::move(G); + EXPECT_EQ(0, Count); + } + EXPECT_EQ(1, Count); + { + auto G = make_scope_exit(Increment); + G.release(); + } + EXPECT_EQ(1, Count); +} + } // end anonymous namespace diff --git a/unittests/ADT/SmallPtrSetTest.cpp b/unittests/ADT/SmallPtrSetTest.cpp index 0070d1cbae1b..76f9cf7fb9af 100644 --- a/unittests/ADT/SmallPtrSetTest.cpp +++ b/unittests/ADT/SmallPtrSetTest.cpp @@ -28,7 +28,7 @@ TEST(SmallPtrSetTest, Assignment) { (s2 = s1).insert(&buf[2]); // Self assign as well. - (s2 = s2).insert(&buf[3]); + (s2 = static_cast<SmallPtrSet<int *, 4> &>(s2)).insert(&buf[3]); s1 = s2; EXPECT_EQ(4U, s1.size()); @@ -56,7 +56,7 @@ TEST(SmallPtrSetTest, GrowthTest) { SmallPtrSet<int *, 4> s; typedef SmallPtrSet<int *, 4>::iterator iter; - + s.insert(&buf[0]); s.insert(&buf[1]); s.insert(&buf[2]); @@ -299,7 +299,7 @@ TEST(SmallPtrSetTest, dereferenceAndIterate) { // Sort. We should hit the first element just once and the final element N // times. - std::sort(std::begin(Found), std::end(Found)); + llvm::sort(std::begin(Found), std::end(Found)); for (auto F = std::begin(Found), E = std::end(Found); F != E; ++F) EXPECT_EQ(F - Found + 1, *F); } diff --git a/unittests/ADT/SmallSetTest.cpp b/unittests/ADT/SmallSetTest.cpp new file mode 100644 index 000000000000..d78a72b38f8b --- /dev/null +++ b/unittests/ADT/SmallSetTest.cpp @@ -0,0 +1,149 @@ +//===- llvm/unittest/ADT/SmallSetTest.cpp ------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// SmallSet unit tests. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/SmallSet.h" +#include "gtest/gtest.h" +#include <string> + +using namespace llvm; + +TEST(SmallSetTest, Insert) { + + SmallSet<int, 4> s1; + + for (int i = 0; i < 4; i++) + s1.insert(i); + + for (int i = 0; i < 4; i++) + s1.insert(i); + + EXPECT_EQ(4u, s1.size()); + + for (int i = 0; i < 4; i++) + EXPECT_EQ(1u, s1.count(i)); + + EXPECT_EQ(0u, s1.count(4)); +} + +TEST(SmallSetTest, Grow) { + SmallSet<int, 4> s1; + + for (int i = 0; i < 8; i++) + s1.insert(i); + + EXPECT_EQ(8u, s1.size()); + + for (int i = 0; i < 8; i++) + EXPECT_EQ(1u, s1.count(i)); + + EXPECT_EQ(0u, s1.count(8)); +} + +TEST(SmallSetTest, Erase) { + SmallSet<int, 4> s1; + + for (int i = 0; i < 8; i++) + s1.insert(i); + + EXPECT_EQ(8u, s1.size()); + + // Remove elements one by one and check if all other elements are still there. + for (int i = 0; i < 8; i++) { + EXPECT_EQ(1u, s1.count(i)); + EXPECT_TRUE(s1.erase(i)); + EXPECT_EQ(0u, s1.count(i)); + EXPECT_EQ(8u - i - 1, s1.size()); + for (int j = i + 1; j < 8; j++) + EXPECT_EQ(1u, s1.count(j)); + } + + EXPECT_EQ(0u, s1.count(8)); +} + +TEST(SmallSetTest, IteratorInt) { + SmallSet<int, 4> s1; + + // Test the 'small' case. + for (int i = 0; i < 3; i++) + s1.insert(i); + + std::vector<int> V(s1.begin(), s1.end()); + // Make sure the elements are in the expected order. + std::sort(V.begin(), V.end()); + for (int i = 0; i < 3; i++) + EXPECT_EQ(i, V[i]); + + // Test the 'big' case by adding a few more elements to switch to std::set + // internally. + for (int i = 3; i < 6; i++) + s1.insert(i); + + V.assign(s1.begin(), s1.end()); + // Make sure the elements are in the expected order. + std::sort(V.begin(), V.end()); + for (int i = 0; i < 6; i++) + EXPECT_EQ(i, V[i]); +} + +TEST(SmallSetTest, IteratorString) { + // Test SmallSetIterator for SmallSet with a type with non-trivial + // ctors/dtors. + SmallSet<std::string, 2> s1; + + s1.insert("str 1"); + s1.insert("str 2"); + s1.insert("str 1"); + + std::vector<std::string> V(s1.begin(), s1.end()); + std::sort(V.begin(), V.end()); + EXPECT_EQ(2u, s1.size()); + EXPECT_EQ("str 1", V[0]); + EXPECT_EQ("str 2", V[1]); + + s1.insert("str 4"); + s1.insert("str 0"); + s1.insert("str 4"); + + V.assign(s1.begin(), s1.end()); + // Make sure the elements are in the expected order. + std::sort(V.begin(), V.end()); + EXPECT_EQ(4u, s1.size()); + EXPECT_EQ("str 0", V[0]); + EXPECT_EQ("str 1", V[1]); + EXPECT_EQ("str 2", V[2]); + EXPECT_EQ("str 4", V[3]); +} + +TEST(SmallSetTest, IteratorIncMoveCopy) { + // Test SmallSetIterator for SmallSet with a type with non-trivial + // ctors/dtors. + SmallSet<std::string, 2> s1; + + s1.insert("str 1"); + s1.insert("str 2"); + + auto Iter = s1.begin(); + EXPECT_EQ("str 1", *Iter); + ++Iter; + EXPECT_EQ("str 2", *Iter); + + s1.insert("str 4"); + s1.insert("str 0"); + auto Iter2 = s1.begin(); + Iter = std::move(Iter2); + EXPECT_EQ("str 0", *Iter); + + auto Iter3 = s1.end(); + Iter3 = Iter2; + EXPECT_EQ(Iter3, Iter2); +} diff --git a/unittests/ADT/SparseBitVectorTest.cpp b/unittests/ADT/SparseBitVectorTest.cpp index 6cd4de35bca7..9d6f4f1665d5 100644 --- a/unittests/ADT/SparseBitVectorTest.cpp +++ b/unittests/ADT/SparseBitVectorTest.cpp @@ -68,7 +68,7 @@ TEST(SparseBitVectorTest, SelfAssignment) { Vec.set(23); Vec.set(234); - Vec = Vec; + Vec = static_cast<SparseBitVector<> &>(Vec); EXPECT_TRUE(Vec.test(23)); EXPECT_TRUE(Vec.test(234)); diff --git a/unittests/ADT/StatisticTest.cpp b/unittests/ADT/StatisticTest.cpp new file mode 100644 index 000000000000..17a5c7fc204a --- /dev/null +++ b/unittests/ADT/StatisticTest.cpp @@ -0,0 +1,160 @@ +//===- llvm/unittest/ADT/StatisticTest.cpp - Statistic unit tests ---------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/ADT/Statistic.h" +#include "llvm/Support/raw_ostream.h" +#include "gtest/gtest.h" +using namespace llvm; + +using OptionalStatistic = Optional<std::pair<StringRef, unsigned>>; + +namespace { +#define DEBUG_TYPE "unittest" +STATISTIC(Counter, "Counts things"); +STATISTIC(Counter2, "Counts other things"); + +#if LLVM_ENABLE_STATS +static void +extractCounters(const std::vector<std::pair<StringRef, unsigned>> &Range, + OptionalStatistic &S1, OptionalStatistic &S2) { + for (const auto &S : Range) { + if (S.first == "Counter") + S1 = S; + if (S.first == "Counter2") + S2 = S; + } +} +#endif + +TEST(StatisticTest, Count) { + EnableStatistics(); + + Counter = 0; + EXPECT_EQ(Counter, 0u); + Counter++; + Counter++; +#if LLVM_ENABLE_STATS + EXPECT_EQ(Counter, 2u); +#else + EXPECT_EQ(Counter, 0u); +#endif +} + +TEST(StatisticTest, Assign) { + EnableStatistics(); + + Counter = 2; +#if LLVM_ENABLE_STATS + EXPECT_EQ(Counter, 2u); +#else + EXPECT_EQ(Counter, 0u); +#endif +} + +TEST(StatisticTest, API) { + EnableStatistics(); + + Counter = 0; + EXPECT_EQ(Counter, 0u); + Counter++; + Counter++; +#if LLVM_ENABLE_STATS + EXPECT_EQ(Counter, 2u); +#else + EXPECT_EQ(Counter, 0u); +#endif + +#if LLVM_ENABLE_STATS + { + const auto Range1 = GetStatistics(); + EXPECT_NE(Range1.begin(), Range1.end()); + EXPECT_EQ(Range1.begin() + 1, Range1.end()); + + OptionalStatistic S1; + OptionalStatistic S2; + extractCounters(Range1, S1, S2); + + EXPECT_EQ(S1.hasValue(), true); + EXPECT_EQ(S2.hasValue(), false); + } + + // Counter2 will be registered when it's first touched. + Counter2++; + + { + const auto Range = GetStatistics(); + EXPECT_NE(Range.begin(), Range.end()); + EXPECT_EQ(Range.begin() + 2, Range.end()); + + OptionalStatistic S1; + OptionalStatistic S2; + extractCounters(Range, S1, S2); + + EXPECT_EQ(S1.hasValue(), true); + EXPECT_EQ(S2.hasValue(), true); + + EXPECT_EQ(S1->first, "Counter"); + EXPECT_EQ(S1->second, 2u); + + EXPECT_EQ(S2->first, "Counter2"); + EXPECT_EQ(S2->second, 1u); + } +#else + Counter2++; + auto &Range = GetStatistics(); + EXPECT_EQ(Range.begin(), Range.end()); +#endif + +#if LLVM_ENABLE_STATS + // Check that resetting the statistics works correctly. + // It should empty the list and zero the counters. + ResetStatistics(); + { + auto &Range = GetStatistics(); + EXPECT_EQ(Range.begin(), Range.end()); + EXPECT_EQ(Counter, 0u); + EXPECT_EQ(Counter2, 0u); + OptionalStatistic S1; + OptionalStatistic S2; + extractCounters(Range, S1, S2); + EXPECT_EQ(S1.hasValue(), false); + EXPECT_EQ(S2.hasValue(), false); + } + + // Now check that they successfully re-register and count. + Counter++; + Counter2++; + + { + auto &Range = GetStatistics(); + EXPECT_EQ(Range.begin() + 2, Range.end()); + EXPECT_EQ(Counter, 1u); + EXPECT_EQ(Counter2, 1u); + + OptionalStatistic S1; + OptionalStatistic S2; + extractCounters(Range, S1, S2); + + EXPECT_EQ(S1.hasValue(), true); + EXPECT_EQ(S2.hasValue(), true); + + EXPECT_EQ(S1->first, "Counter"); + EXPECT_EQ(S1->second, 1u); + + EXPECT_EQ(S2->first, "Counter2"); + EXPECT_EQ(S2->second, 1u); + } +#else + // No need to test the output ResetStatistics(), there's nothing to reset so + // we can't tell if it failed anyway. + ResetStatistics(); +#endif +} + +} // end anonymous namespace diff --git a/unittests/ADT/StringExtrasTest.cpp b/unittests/ADT/StringExtrasTest.cpp index 7bcdaf62fe5a..1df200553a61 100644 --- a/unittests/ADT/StringExtrasTest.cpp +++ b/unittests/ADT/StringExtrasTest.cpp @@ -13,6 +13,17 @@ using namespace llvm; +TEST(StringExtrasTest, isPrint) { + EXPECT_FALSE(isPrint('\0')); + EXPECT_FALSE(isPrint('\t')); + EXPECT_TRUE(isPrint('0')); + EXPECT_TRUE(isPrint('a')); + EXPECT_TRUE(isPrint('A')); + EXPECT_TRUE(isPrint(' ')); + EXPECT_TRUE(isPrint('~')); + EXPECT_TRUE(isPrint('?')); +} + TEST(StringExtrasTest, Join) { std::vector<std::string> Items; EXPECT_EQ("", join(Items.begin(), Items.end(), " <sep> ")); @@ -92,3 +103,17 @@ TEST(StringExtrasTest, printLowerCase) { printLowerCase("ABCdefg01234.,&!~`'}\"", OS); EXPECT_EQ("abcdefg01234.,&!~`'}\"", OS.str()); } + +TEST(StringExtrasTest, printEscapedString) { + std::string str; + raw_string_ostream OS(str); + printEscapedString("ABCdef123&<>\\\"'\t", OS); + EXPECT_EQ("ABCdef123&<>\\5C\\22'\\09", OS.str()); +} + +TEST(StringExtrasTest, printHTMLEscaped) { + std::string str; + raw_string_ostream OS(str); + printHTMLEscaped("ABCdef123&<>\"'", OS); + EXPECT_EQ("ABCdef123&<>"'", OS.str()); +} diff --git a/unittests/ADT/StringMapTest.cpp b/unittests/ADT/StringMapTest.cpp index b5c63695ff35..1f5c4f031642 100644 --- a/unittests/ADT/StringMapTest.cpp +++ b/unittests/ADT/StringMapTest.cpp @@ -12,6 +12,7 @@ #include "llvm/ADT/Twine.h" #include "llvm/Support/DataTypes.h" #include "gtest/gtest.h" +#include <limits> #include <tuple> using namespace llvm; @@ -278,7 +279,7 @@ TEST_F(StringMapTest, IterMapKeys) { Map["D"] = 3; auto Keys = to_vector<4>(Map.keys()); - std::sort(Keys.begin(), Keys.end()); + llvm::sort(Keys.begin(), Keys.end()); SmallVector<StringRef, 4> Expected = {"A", "B", "C", "D"}; EXPECT_EQ(Expected, Keys); @@ -292,7 +293,7 @@ TEST_F(StringMapTest, IterSetKeys) { Set.insert("D"); auto Keys = to_vector<4>(Set.keys()); - std::sort(Keys.begin(), Keys.end()); + llvm::sort(Keys.begin(), Keys.end()); SmallVector<StringRef, 4> Expected = {"A", "B", "C", "D"}; EXPECT_EQ(Expected, Keys); @@ -492,4 +493,43 @@ TEST(StringMapCustomTest, EmplaceTest) { EXPECT_EQ(42, Map["abcd"].Data); } +// Test that StringMapEntryBase can handle size_t wide sizes. +TEST(StringMapCustomTest, StringMapEntryBaseSize) { + size_t LargeValue; + + // Test that the entry can represent max-unsigned. + if (sizeof(size_t) <= sizeof(unsigned)) + LargeValue = std::numeric_limits<unsigned>::max(); + else + LargeValue = std::numeric_limits<unsigned>::max() + 1ULL; + StringMapEntryBase LargeBase(LargeValue); + EXPECT_EQ(LargeValue, LargeBase.getKeyLength()); + + // Test that the entry can hold at least max size_t. + LargeValue = std::numeric_limits<size_t>::max(); + StringMapEntryBase LargerBase(LargeValue); + LargeValue = std::numeric_limits<size_t>::max(); + EXPECT_EQ(LargeValue, LargerBase.getKeyLength()); +} + +// Test that StringMapEntry can handle size_t wide sizes. +TEST(StringMapCustomTest, StringMapEntrySize) { + size_t LargeValue; + + // Test that the entry can represent max-unsigned. + if (sizeof(size_t) <= sizeof(unsigned)) + LargeValue = std::numeric_limits<unsigned>::max(); + else + LargeValue = std::numeric_limits<unsigned>::max() + 1ULL; + StringMapEntry<int> LargeEntry(LargeValue); + StringRef Key = LargeEntry.getKey(); + EXPECT_EQ(LargeValue, Key.size()); + + // Test that the entry can hold at least max size_t. + LargeValue = std::numeric_limits<size_t>::max(); + StringMapEntry<int> LargerEntry(LargeValue); + Key = LargerEntry.getKey(); + EXPECT_EQ(LargeValue, Key.size()); +} + } // end anonymous namespace diff --git a/unittests/ADT/StringRefTest.cpp b/unittests/ADT/StringRefTest.cpp index 0e0b5957f025..4087d6c99a93 100644 --- a/unittests/ADT/StringRefTest.cpp +++ b/unittests/ADT/StringRefTest.cpp @@ -40,22 +40,22 @@ std::ostream &operator<<(std::ostream &OS, // std::is_assignable and actually writing such an assignment. #if !defined(_MSC_VER) static_assert( - !std::is_assignable<StringRef, std::string>::value, + !std::is_assignable<StringRef&, std::string>::value, "Assigning from prvalue std::string"); static_assert( - !std::is_assignable<StringRef, std::string &&>::value, + !std::is_assignable<StringRef&, std::string &&>::value, "Assigning from xvalue std::string"); static_assert( - std::is_assignable<StringRef, std::string &>::value, + std::is_assignable<StringRef&, std::string &>::value, "Assigning from lvalue std::string"); static_assert( - std::is_assignable<StringRef, const char *>::value, + std::is_assignable<StringRef&, const char *>::value, "Assigning from prvalue C string"); static_assert( - std::is_assignable<StringRef, const char * &&>::value, + std::is_assignable<StringRef&, const char * &&>::value, "Assigning from xvalue C string"); static_assert( - std::is_assignable<StringRef, const char * &>::value, + std::is_assignable<StringRef&, const char * &>::value, "Assigning from lvalue C string"); #endif @@ -181,6 +181,17 @@ TEST(StringRefTest, Split) { Str.rsplit('l')); EXPECT_EQ(std::make_pair(StringRef("hell"), StringRef("")), Str.rsplit('o')); + + EXPECT_EQ(std::make_pair(StringRef("he"), StringRef("o")), + Str.rsplit("ll")); + EXPECT_EQ(std::make_pair(StringRef(""), StringRef("ello")), + Str.rsplit("h")); + EXPECT_EQ(std::make_pair(StringRef("hell"), StringRef("")), + Str.rsplit("o")); + EXPECT_EQ(std::make_pair(StringRef("hello"), StringRef("")), + Str.rsplit("::")); + EXPECT_EQ(std::make_pair(StringRef("hel"), StringRef("o")), + Str.rsplit("l")); } TEST(StringRefTest, Split2) { diff --git a/unittests/ADT/StringSwitchTest.cpp b/unittests/ADT/StringSwitchTest.cpp index 1a51629ca574..62d3a319e7a4 100644 --- a/unittests/ADT/StringSwitchTest.cpp +++ b/unittests/ADT/StringSwitchTest.cpp @@ -158,7 +158,8 @@ TEST(StringSwitchTest, Cases) { auto Translate = [](StringRef S) { return llvm::StringSwitch<OSType>(S) - .Cases("wind\0ws", "win32", "winnt", OSType::Windows) + .Cases(StringLiteral::withInnerNUL("wind\0ws"), "win32", "winnt", + OSType::Windows) .Cases("linux", "unix", "*nix", "posix", OSType::Linux) .Default(OSType::Unknown); }; @@ -184,7 +185,8 @@ TEST(StringSwitchTest, CasesLower) { auto Translate = [](StringRef S) { return llvm::StringSwitch<OSType>(S) - .CasesLower("wind\0ws", "win32", "winnt", OSType::Windows) + .CasesLower(StringLiteral::withInnerNUL("wind\0ws"), "win32", "winnt", + OSType::Windows) .CasesLower("linux", "unix", "*nix", "posix", OSType::Linux) .Default(OSType::Unknown); }; diff --git a/unittests/ADT/TripleTest.cpp b/unittests/ADT/TripleTest.cpp index ed4a88067b15..b6b470d34e2b 100644 --- a/unittests/ADT/TripleTest.cpp +++ b/unittests/ADT/TripleTest.cpp @@ -117,12 +117,29 @@ TEST(TripleTest, ParsedIDs) { EXPECT_EQ(Triple::CNK, T.getOS()); EXPECT_EQ(Triple::UnknownEnvironment, T.getEnvironment()); + T = Triple("ppc-bgp-linux"); + EXPECT_EQ(Triple::ppc, T.getArch()); + EXPECT_EQ(Triple::BGP, T.getVendor()); + EXPECT_EQ(Triple::Linux, T.getOS()); + EXPECT_EQ(Triple::UnknownEnvironment, T.getEnvironment()); + + T = Triple("ppc32-bgp-linux"); + EXPECT_EQ(Triple::ppc, T.getArch()); + EXPECT_EQ(Triple::BGP, T.getVendor()); + EXPECT_EQ(Triple::Linux, T.getOS()); + EXPECT_EQ(Triple::UnknownEnvironment, T.getEnvironment()); + T = Triple("powerpc64-bgq-linux"); EXPECT_EQ(Triple::ppc64, T.getArch()); EXPECT_EQ(Triple::BGQ, T.getVendor()); EXPECT_EQ(Triple::Linux, T.getOS()); EXPECT_EQ(Triple::UnknownEnvironment, T.getEnvironment()); + T = Triple("ppc64-bgq-linux"); + EXPECT_EQ(Triple::ppc64, T.getArch()); + EXPECT_EQ(Triple::BGQ, T.getVendor()); + EXPECT_EQ(Triple::Linux, T.getOS()); + T = Triple("powerpc-ibm-aix"); EXPECT_EQ(Triple::ppc, T.getArch()); EXPECT_EQ(Triple::IBM, T.getVendor()); @@ -266,12 +283,6 @@ TEST(TripleTest, ParsedIDs) { EXPECT_EQ(Triple::AMDHSA, T.getOS()); EXPECT_EQ(Triple::UnknownEnvironment, T.getEnvironment()); - T = Triple("amdgcn-amd-amdhsa-opencl"); - EXPECT_EQ(Triple::amdgcn, T.getArch()); - EXPECT_EQ(Triple::AMD, T.getVendor()); - EXPECT_EQ(Triple::AMDHSA, T.getOS()); - EXPECT_EQ(Triple::OpenCL, T.getEnvironment()); - T = Triple("amdgcn-amd-amdpal"); EXPECT_EQ(Triple::amdgcn, T.getArch()); EXPECT_EQ(Triple::AMD, T.getVendor()); @@ -350,6 +361,18 @@ TEST(TripleTest, ParsedIDs) { EXPECT_EQ(Triple::Linux, T.getOS()); EXPECT_EQ(Triple::GNUABI64, T.getEnvironment()); + T = Triple("arm-oe-linux-gnueabi"); + EXPECT_EQ(Triple::arm, T.getArch()); + EXPECT_EQ(Triple::OpenEmbedded, T.getVendor()); + EXPECT_EQ(Triple::Linux, T.getOS()); + EXPECT_EQ(Triple::GNUEABI, T.getEnvironment()); + + T = Triple("aarch64-oe-linux"); + EXPECT_EQ(Triple::aarch64, T.getArch()); + EXPECT_EQ(Triple::OpenEmbedded, T.getVendor()); + EXPECT_EQ(Triple::Linux, T.getOS()); + EXPECT_EQ(Triple::UnknownEnvironment, T.getEnvironment()); + T = Triple("huh"); EXPECT_EQ(Triple::UnknownArch, T.getArch()); } @@ -523,9 +546,6 @@ TEST(TripleTest, MutateName) { EXPECT_EQ(Triple::PC, T.getVendor()); EXPECT_EQ(Triple::Darwin, T.getOS()); EXPECT_EQ("i386-pc-darwin", T.getTriple()); - - T.setEnvironmentName("amdopencl"); - EXPECT_EQ(Triple::AMDOpenCL, T.getEnvironment()); } TEST(TripleTest, BitWidthPredicates) { @@ -1027,8 +1047,13 @@ TEST(TripleTest, FileFormat) { EXPECT_EQ(Triple::ELF, Triple("i686-pc-windows-msvc-elf").getObjectFormat()); EXPECT_EQ(Triple::ELF, Triple("i686-pc-cygwin-elf").getObjectFormat()); - EXPECT_EQ(Triple::Wasm, Triple("wasm32-unknown-unknown-wasm").getObjectFormat()); - EXPECT_EQ(Triple::Wasm, Triple("wasm64-unknown-unknown-wasm").getObjectFormat()); + EXPECT_EQ(Triple::Wasm, Triple("wasm32-unknown-unknown").getObjectFormat()); + EXPECT_EQ(Triple::Wasm, Triple("wasm64-unknown-unknown").getObjectFormat()); + + EXPECT_EQ(Triple::Wasm, + Triple("wasm32-unknown-unknown-wasm").getObjectFormat()); + EXPECT_EQ(Triple::Wasm, + Triple("wasm64-unknown-unknown-wasm").getObjectFormat()); Triple MSVCNormalized(Triple::normalize("i686-pc-windows-msvc-elf")); EXPECT_EQ(Triple::ELF, MSVCNormalized.getObjectFormat()); diff --git a/unittests/Analysis/AliasAnalysisTest.cpp b/unittests/Analysis/AliasAnalysisTest.cpp index f1294eb5b7e6..0f0d44f6c780 100644 --- a/unittests/Analysis/AliasAnalysisTest.cpp +++ b/unittests/Analysis/AliasAnalysisTest.cpp @@ -156,7 +156,7 @@ protected: // Build the various AA results and register them. AC.reset(new AssumptionCache(F)); - BAR.reset(new BasicAAResult(M.getDataLayout(), TLI, *AC)); + BAR.reset(new BasicAAResult(M.getDataLayout(), F, TLI, *AC)); AAR->addAAResult(*BAR); return *AAR; diff --git a/unittests/Analysis/CMakeLists.txt b/unittests/Analysis/CMakeLists.txt index 65f2aeda4180..f6ff29064be3 100644 --- a/unittests/Analysis/CMakeLists.txt +++ b/unittests/Analysis/CMakeLists.txt @@ -20,6 +20,7 @@ add_llvm_unittest(AnalysisTests MemoryBuiltinsTest.cpp MemorySSA.cpp OrderedBasicBlockTest.cpp + PhiValuesTest.cpp ProfileSummaryInfoTest.cpp ScalarEvolutionTest.cpp SparsePropagation.cpp diff --git a/unittests/Analysis/LazyCallGraphTest.cpp b/unittests/Analysis/LazyCallGraphTest.cpp index cb8eb3756059..6279ebb9b5ae 100644 --- a/unittests/Analysis/LazyCallGraphTest.cpp +++ b/unittests/Analysis/LazyCallGraphTest.cpp @@ -264,7 +264,7 @@ TEST(LazyCallGraphTest, BasicGraphFormation) { for (LazyCallGraph::Edge &E : A1.populate()) Nodes.push_back(E.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ("a2", Nodes[0]); EXPECT_EQ("b2", Nodes[1]); EXPECT_EQ("c3", Nodes[2]); @@ -279,7 +279,7 @@ TEST(LazyCallGraphTest, BasicGraphFormation) { for (LazyCallGraph::Edge &E : B1.populate()) Nodes.push_back(E.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ("b2", Nodes[0]); EXPECT_EQ("d3", Nodes[1]); Nodes.clear(); @@ -293,7 +293,7 @@ TEST(LazyCallGraphTest, BasicGraphFormation) { for (LazyCallGraph::Edge &E : C1.populate()) Nodes.push_back(E.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ("c2", Nodes[0]); EXPECT_EQ("d2", Nodes[1]); Nodes.clear(); @@ -323,7 +323,7 @@ TEST(LazyCallGraphTest, BasicGraphFormation) { ASSERT_EQ(1, D.size()); for (LazyCallGraph::Node &N : *D.begin()) Nodes.push_back(N.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ(3u, Nodes.size()); EXPECT_EQ("d1", Nodes[0]); EXPECT_EQ("d2", Nodes[1]); @@ -339,7 +339,7 @@ TEST(LazyCallGraphTest, BasicGraphFormation) { ASSERT_EQ(1, C.size()); for (LazyCallGraph::Node &N : *C.begin()) Nodes.push_back(N.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ(3u, Nodes.size()); EXPECT_EQ("c1", Nodes[0]); EXPECT_EQ("c2", Nodes[1]); @@ -355,7 +355,7 @@ TEST(LazyCallGraphTest, BasicGraphFormation) { ASSERT_EQ(1, B.size()); for (LazyCallGraph::Node &N : *B.begin()) Nodes.push_back(N.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ(3u, Nodes.size()); EXPECT_EQ("b1", Nodes[0]); EXPECT_EQ("b2", Nodes[1]); @@ -373,7 +373,7 @@ TEST(LazyCallGraphTest, BasicGraphFormation) { ASSERT_EQ(1, A.size()); for (LazyCallGraph::Node &N : *A.begin()) Nodes.push_back(N.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ(3u, Nodes.size()); EXPECT_EQ("a1", Nodes[0]); EXPECT_EQ("a2", Nodes[1]); @@ -477,7 +477,7 @@ TEST(LazyCallGraphTest, InnerSCCFormation) { LazyCallGraph::SCC &D = *J++; for (LazyCallGraph::Node &N : D) Nodes.push_back(N.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ(3u, Nodes.size()); EXPECT_EQ("d1", Nodes[0]); EXPECT_EQ("d2", Nodes[1]); @@ -487,7 +487,7 @@ TEST(LazyCallGraphTest, InnerSCCFormation) { LazyCallGraph::SCC &B = *J++; for (LazyCallGraph::Node &N : B) Nodes.push_back(N.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ(3u, Nodes.size()); EXPECT_EQ("b1", Nodes[0]); EXPECT_EQ("b2", Nodes[1]); @@ -497,7 +497,7 @@ TEST(LazyCallGraphTest, InnerSCCFormation) { LazyCallGraph::SCC &C = *J++; for (LazyCallGraph::Node &N : C) Nodes.push_back(N.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ(3u, Nodes.size()); EXPECT_EQ("c1", Nodes[0]); EXPECT_EQ("c2", Nodes[1]); @@ -507,7 +507,7 @@ TEST(LazyCallGraphTest, InnerSCCFormation) { LazyCallGraph::SCC &A = *J++; for (LazyCallGraph::Node &N : A) Nodes.push_back(N.getFunction().getName()); - std::sort(Nodes.begin(), Nodes.end()); + llvm::sort(Nodes.begin(), Nodes.end()); EXPECT_EQ(3u, Nodes.size()); EXPECT_EQ("a1", Nodes[0]); EXPECT_EQ("a2", Nodes[1]); diff --git a/unittests/Analysis/MemorySSA.cpp b/unittests/Analysis/MemorySSA.cpp index affa0e71820c..1a1675faca16 100644 --- a/unittests/Analysis/MemorySSA.cpp +++ b/unittests/Analysis/MemorySSA.cpp @@ -50,7 +50,7 @@ protected: TestAnalyses(MemorySSATest &Test) : DT(*Test.F), AC(*Test.F), AA(Test.TLI), - BAA(Test.DL, Test.TLI, AC, &DT) { + BAA(Test.DL, *Test.F, Test.TLI, AC, &DT) { AA.addAAResult(BAA); MSSA = make_unique<MemorySSA>(*Test.F, &AA, &DT); Walker = MSSA->getWalker(); @@ -909,3 +909,293 @@ TEST_F(MemorySSATest, Irreducible) { Updater.insertUse(LoadAccess); MSSA.verifyMemorySSA(); } + +TEST_F(MemorySSATest, MoveToBeforeLiveOnEntryInvalidatesCache) { + // Create: + // %1 = alloca i8 + // ; 1 = MemoryDef(liveOnEntry) + // store i8 0, i8* %1 + // ; 2 = MemoryDef(1) + // store i8 0, i8* %1 + // + // ...And be sure that MSSA's caching doesn't give us `1` for the clobber of + // `2` after `1` is removed. + IRBuilder<> B(C); + F = Function::Create( + FunctionType::get(B.getVoidTy(), {B.getInt8PtrTy()}, false), + GlobalValue::ExternalLinkage, "F", &M); + + BasicBlock *Entry = BasicBlock::Create(C, "if", F); + B.SetInsertPoint(Entry); + + Value *A = B.CreateAlloca(B.getInt8Ty()); + StoreInst *StoreA = B.CreateStore(B.getInt8(0), A); + StoreInst *StoreB = B.CreateStore(B.getInt8(0), A); + + setupAnalyses(); + + MemorySSA &MSSA = *Analyses->MSSA; + + auto *DefA = cast<MemoryDef>(MSSA.getMemoryAccess(StoreA)); + auto *DefB = cast<MemoryDef>(MSSA.getMemoryAccess(StoreB)); + + MemoryAccess *BClobber = MSSA.getWalker()->getClobberingMemoryAccess(DefB); + ASSERT_EQ(DefA, BClobber); + + MemorySSAUpdater(&MSSA).removeMemoryAccess(DefA); + StoreA->eraseFromParent(); + + EXPECT_EQ(DefB->getDefiningAccess(), MSSA.getLiveOnEntryDef()); + + EXPECT_EQ(MSSA.getWalker()->getClobberingMemoryAccess(DefB), + MSSA.getLiveOnEntryDef()) + << "(DefA = " << DefA << ")"; +} + +TEST_F(MemorySSATest, RemovingDefInvalidatesCache) { + // Create: + // %x = alloca i8 + // %y = alloca i8 + // ; 1 = MemoryDef(liveOnEntry) + // store i8 0, i8* %x + // ; 2 = MemoryDef(1) + // store i8 0, i8* %y + // ; 3 = MemoryDef(2) + // store i8 0, i8* %x + // + // And be sure that MSSA's caching handles the removal of def `1` + // appropriately. + IRBuilder<> B(C); + F = Function::Create( + FunctionType::get(B.getVoidTy(), {B.getInt8PtrTy()}, false), + GlobalValue::ExternalLinkage, "F", &M); + + BasicBlock *Entry = BasicBlock::Create(C, "if", F); + B.SetInsertPoint(Entry); + + Value *X = B.CreateAlloca(B.getInt8Ty()); + Value *Y = B.CreateAlloca(B.getInt8Ty()); + StoreInst *StoreX1 = B.CreateStore(B.getInt8(0), X); + StoreInst *StoreY = B.CreateStore(B.getInt8(0), Y); + StoreInst *StoreX2 = B.CreateStore(B.getInt8(0), X); + + setupAnalyses(); + + MemorySSA &MSSA = *Analyses->MSSA; + + auto *DefX1 = cast<MemoryDef>(MSSA.getMemoryAccess(StoreX1)); + auto *DefY = cast<MemoryDef>(MSSA.getMemoryAccess(StoreY)); + auto *DefX2 = cast<MemoryDef>(MSSA.getMemoryAccess(StoreX2)); + + EXPECT_EQ(DefX2->getDefiningAccess(), DefY); + MemoryAccess *X2Clobber = MSSA.getWalker()->getClobberingMemoryAccess(DefX2); + ASSERT_EQ(DefX1, X2Clobber); + + MemorySSAUpdater(&MSSA).removeMemoryAccess(DefX1); + StoreX1->eraseFromParent(); + + EXPECT_EQ(DefX2->getDefiningAccess(), DefY); + EXPECT_EQ(MSSA.getWalker()->getClobberingMemoryAccess(DefX2), + MSSA.getLiveOnEntryDef()) + << "(DefX1 = " << DefX1 << ")"; +} + +// Test Must alias for optimized uses +TEST_F(MemorySSATest, TestLoadMustAlias) { + F = Function::Create(FunctionType::get(B.getVoidTy(), {}, false), + GlobalValue::ExternalLinkage, "F", &M); + B.SetInsertPoint(BasicBlock::Create(C, "", F)); + Type *Int8 = Type::getInt8Ty(C); + Value *AllocaA = B.CreateAlloca(Int8, ConstantInt::get(Int8, 1), "A"); + Value *AllocaB = B.CreateAlloca(Int8, ConstantInt::get(Int8, 1), "B"); + + B.CreateStore(ConstantInt::get(Int8, 1), AllocaB); + // Check load from LOE + LoadInst *LA1 = B.CreateLoad(AllocaA, ""); + // Check load alias cached for second load + LoadInst *LA2 = B.CreateLoad(AllocaA, ""); + + B.CreateStore(ConstantInt::get(Int8, 1), AllocaA); + // Check load from store/def + LoadInst *LA3 = B.CreateLoad(AllocaA, ""); + // Check load alias cached for second load + LoadInst *LA4 = B.CreateLoad(AllocaA, ""); + + setupAnalyses(); + MemorySSA &MSSA = *Analyses->MSSA; + + unsigned I = 0; + for (LoadInst *V : {LA1, LA2}) { + MemoryUse *MemUse = dyn_cast_or_null<MemoryUse>(MSSA.getMemoryAccess(V)); + EXPECT_EQ(MemUse->getOptimizedAccessType(), None) + << "Load " << I << " doesn't have the correct alias information"; + // EXPECT_EQ expands such that if we increment I above, it won't get + // incremented except when we try to print the error message. + ++I; + } + for (LoadInst *V : {LA3, LA4}) { + MemoryUse *MemUse = dyn_cast_or_null<MemoryUse>(MSSA.getMemoryAccess(V)); + EXPECT_EQ(MemUse->getOptimizedAccessType(), MustAlias) + << "Load " << I << " doesn't have the correct alias information"; + // EXPECT_EQ expands such that if we increment I above, it won't get + // incremented except when we try to print the error message. + ++I; + } +} + +// Test Must alias for optimized defs. +TEST_F(MemorySSATest, TestStoreMustAlias) { + F = Function::Create(FunctionType::get(B.getVoidTy(), {}, false), + GlobalValue::ExternalLinkage, "F", &M); + B.SetInsertPoint(BasicBlock::Create(C, "", F)); + Type *Int8 = Type::getInt8Ty(C); + Value *AllocaA = B.CreateAlloca(Int8, ConstantInt::get(Int8, 1), "A"); + Value *AllocaB = B.CreateAlloca(Int8, ConstantInt::get(Int8, 1), "B"); + StoreInst *SA1 = B.CreateStore(ConstantInt::get(Int8, 1), AllocaA); + StoreInst *SB1 = B.CreateStore(ConstantInt::get(Int8, 1), AllocaB); + StoreInst *SA2 = B.CreateStore(ConstantInt::get(Int8, 2), AllocaA); + StoreInst *SB2 = B.CreateStore(ConstantInt::get(Int8, 2), AllocaB); + StoreInst *SA3 = B.CreateStore(ConstantInt::get(Int8, 3), AllocaA); + StoreInst *SB3 = B.CreateStore(ConstantInt::get(Int8, 3), AllocaB); + + setupAnalyses(); + MemorySSA &MSSA = *Analyses->MSSA; + MemorySSAWalker *Walker = Analyses->Walker; + + unsigned I = 0; + for (StoreInst *V : {SA1, SB1, SA2, SB2, SA3, SB3}) { + MemoryDef *MemDef = dyn_cast_or_null<MemoryDef>(MSSA.getMemoryAccess(V)); + EXPECT_EQ(MemDef->isOptimized(), false) + << "Store " << I << " is optimized from the start?"; + EXPECT_EQ(MemDef->getOptimizedAccessType(), MayAlias) + << "Store " << I + << " has correct alias information before being optimized?"; + if (V == SA1) + Walker->getClobberingMemoryAccess(V); + else { + MemoryAccess *Def = MemDef->getDefiningAccess(); + MemoryAccess *Clob = Walker->getClobberingMemoryAccess(V); + EXPECT_NE(Def, Clob) << "Store " << I + << " has Defining Access equal to Clobbering Access"; + } + EXPECT_EQ(MemDef->isOptimized(), true) + << "Store " << I << " was not optimized"; + if (I == 0 || I == 1) + EXPECT_EQ(MemDef->getOptimizedAccessType(), None) + << "Store " << I << " doesn't have the correct alias information"; + else + EXPECT_EQ(MemDef->getOptimizedAccessType(), MustAlias) + << "Store " << I << " doesn't have the correct alias information"; + // EXPECT_EQ expands such that if we increment I above, it won't get + // incremented except when we try to print the error message. + ++I; + } +} + +// Test May alias for optimized uses. +TEST_F(MemorySSATest, TestLoadMayAlias) { + F = Function::Create(FunctionType::get(B.getVoidTy(), + {B.getInt8PtrTy(), B.getInt8PtrTy()}, + false), + GlobalValue::ExternalLinkage, "F", &M); + B.SetInsertPoint(BasicBlock::Create(C, "", F)); + Type *Int8 = Type::getInt8Ty(C); + auto *ArgIt = F->arg_begin(); + Argument *PointerA = &*ArgIt; + Argument *PointerB = &*(++ArgIt); + B.CreateStore(ConstantInt::get(Int8, 1), PointerB); + LoadInst *LA1 = B.CreateLoad(PointerA, ""); + B.CreateStore(ConstantInt::get(Int8, 0), PointerA); + LoadInst *LB1 = B.CreateLoad(PointerB, ""); + B.CreateStore(ConstantInt::get(Int8, 0), PointerA); + LoadInst *LA2 = B.CreateLoad(PointerA, ""); + B.CreateStore(ConstantInt::get(Int8, 0), PointerB); + LoadInst *LB2 = B.CreateLoad(PointerB, ""); + + setupAnalyses(); + MemorySSA &MSSA = *Analyses->MSSA; + + unsigned I = 0; + for (LoadInst *V : {LA1, LB1}) { + MemoryUse *MemUse = dyn_cast_or_null<MemoryUse>(MSSA.getMemoryAccess(V)); + EXPECT_EQ(MemUse->getOptimizedAccessType(), MayAlias) + << "Load " << I << " doesn't have the correct alias information"; + // EXPECT_EQ expands such that if we increment I above, it won't get + // incremented except when we try to print the error message. + ++I; + } + for (LoadInst *V : {LA2, LB2}) { + MemoryUse *MemUse = dyn_cast_or_null<MemoryUse>(MSSA.getMemoryAccess(V)); + EXPECT_EQ(MemUse->getOptimizedAccessType(), MustAlias) + << "Load " << I << " doesn't have the correct alias information"; + // EXPECT_EQ expands such that if we increment I above, it won't get + // incremented except when we try to print the error message. + ++I; + } +} + +// Test May alias for optimized defs. +TEST_F(MemorySSATest, TestStoreMayAlias) { + F = Function::Create(FunctionType::get(B.getVoidTy(), + {B.getInt8PtrTy(), B.getInt8PtrTy()}, + false), + GlobalValue::ExternalLinkage, "F", &M); + B.SetInsertPoint(BasicBlock::Create(C, "", F)); + Type *Int8 = Type::getInt8Ty(C); + auto *ArgIt = F->arg_begin(); + Argument *PointerA = &*ArgIt; + Argument *PointerB = &*(++ArgIt); + Value *AllocaC = B.CreateAlloca(Int8, ConstantInt::get(Int8, 1), "C"); + // Store into arg1, must alias because it's LOE => must + StoreInst *SA1 = B.CreateStore(ConstantInt::get(Int8, 0), PointerA); + // Store into arg2, may alias store to arg1 => may + StoreInst *SB1 = B.CreateStore(ConstantInt::get(Int8, 1), PointerB); + // Store into aloca, no alias with args, so must alias LOE => must + StoreInst *SC1 = B.CreateStore(ConstantInt::get(Int8, 2), AllocaC); + // Store into arg1, may alias store to arg2 => may + StoreInst *SA2 = B.CreateStore(ConstantInt::get(Int8, 3), PointerA); + // Store into arg2, may alias store to arg1 => may + StoreInst *SB2 = B.CreateStore(ConstantInt::get(Int8, 4), PointerB); + // Store into aloca, no alias with args, so must alias SC1 => must + StoreInst *SC2 = B.CreateStore(ConstantInt::get(Int8, 5), AllocaC); + // Store into arg2, must alias store to arg2 => must + StoreInst *SB3 = B.CreateStore(ConstantInt::get(Int8, 6), PointerB); + std::initializer_list<StoreInst *> Sts = {SA1, SB1, SC1, SA2, SB2, SC2, SB3}; + + setupAnalyses(); + MemorySSA &MSSA = *Analyses->MSSA; + MemorySSAWalker *Walker = Analyses->Walker; + + unsigned I = 0; + for (StoreInst *V : Sts) { + MemoryDef *MemDef = dyn_cast_or_null<MemoryDef>(MSSA.getMemoryAccess(V)); + EXPECT_EQ(MemDef->isOptimized(), false) + << "Store " << I << " is optimized from the start?"; + EXPECT_EQ(MemDef->getOptimizedAccessType(), MayAlias) + << "Store " << I + << " has correct alias information before being optimized?"; + ++I; + } + + for (StoreInst *V : Sts) + Walker->getClobberingMemoryAccess(V); + + I = 0; + for (StoreInst *V : Sts) { + MemoryDef *MemDef = dyn_cast_or_null<MemoryDef>(MSSA.getMemoryAccess(V)); + EXPECT_EQ(MemDef->isOptimized(), true) + << "Store " << I << " was not optimized"; + if (I == 1 || I == 3 || I == 4) + EXPECT_EQ(MemDef->getOptimizedAccessType(), MayAlias) + << "Store " << I << " doesn't have the correct alias information"; + else if (I == 0 || I == 2) + EXPECT_EQ(MemDef->getOptimizedAccessType(), None) + << "Store " << I << " doesn't have the correct alias information"; + else + EXPECT_EQ(MemDef->getOptimizedAccessType(), MustAlias) + << "Store " << I << " doesn't have the correct alias information"; + // EXPECT_EQ expands such that if we increment I above, it won't get + // incremented except when we try to print the error message. + ++I; + } +} diff --git a/unittests/Analysis/PhiValuesTest.cpp b/unittests/Analysis/PhiValuesTest.cpp new file mode 100644 index 000000000000..303b0df21f52 --- /dev/null +++ b/unittests/Analysis/PhiValuesTest.cpp @@ -0,0 +1,208 @@ +//===- PhiValuesTest.cpp - PhiValues unit tests ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Analysis/PhiValues.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Function.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/Module.h" +#include "llvm/IR/Type.h" +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(PhiValuesTest, SimplePhi) { + LLVMContext C; + Module M("PhiValuesTest", C); + + Type *VoidTy = Type::getVoidTy(C); + Type *I1Ty = Type::getInt1Ty(C); + Type *I32Ty = Type::getInt32Ty(C); + Type *I32PtrTy = Type::getInt32PtrTy(C); + + // Create a function with phis that do not have other phis as incoming values + Function *F = cast<Function>(M.getOrInsertFunction("f", FunctionType::get(VoidTy, false))); + + BasicBlock *Entry = BasicBlock::Create(C, "entry", F); + BasicBlock *If = BasicBlock::Create(C, "if", F); + BasicBlock *Else = BasicBlock::Create(C, "else", F); + BasicBlock *Then = BasicBlock::Create(C, "then", F); + BranchInst::Create(If, Else, UndefValue::get(I1Ty), Entry); + BranchInst::Create(Then, If); + BranchInst::Create(Then, Else); + + Value *Val1 = new LoadInst(UndefValue::get(I32PtrTy), "val1", Entry); + Value *Val2 = new LoadInst(UndefValue::get(I32PtrTy), "val2", Entry); + Value *Val3 = new LoadInst(UndefValue::get(I32PtrTy), "val3", Entry); + Value *Val4 = new LoadInst(UndefValue::get(I32PtrTy), "val4", Entry); + + PHINode *Phi1 = PHINode::Create(I32Ty, 2, "phi1", Then); + Phi1->addIncoming(Val1, If); + Phi1->addIncoming(Val2, Else); + PHINode *Phi2 = PHINode::Create(I32Ty, 2, "phi2", Then); + Phi2->addIncoming(Val1, If); + Phi2->addIncoming(Val3, Else); + + PhiValues PV(*F); + PhiValues::ValueSet Vals; + + // Check that simple usage works + Vals = PV.getValuesForPhi(Phi1); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val1)); + EXPECT_TRUE(Vals.count(Val2)); + Vals = PV.getValuesForPhi(Phi2); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val1)); + EXPECT_TRUE(Vals.count(Val3)); + + // Check that values are updated when one value is replaced with another + Val1->replaceAllUsesWith(Val4); + PV.invalidateValue(Val1); + Vals = PV.getValuesForPhi(Phi1); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val4)); + EXPECT_TRUE(Vals.count(Val2)); + Vals = PV.getValuesForPhi(Phi2); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val4)); + EXPECT_TRUE(Vals.count(Val3)); + + // Check that setting in incoming value directly updates the values + Phi1->setIncomingValue(0, Val1); + PV.invalidateValue(Phi1); + Vals = PV.getValuesForPhi(Phi1); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val1)); + EXPECT_TRUE(Vals.count(Val2)); +} + +TEST(PhiValuesTest, DependentPhi) { + LLVMContext C; + Module M("PhiValuesTest", C); + + Type *VoidTy = Type::getVoidTy(C); + Type *I1Ty = Type::getInt1Ty(C); + Type *I32Ty = Type::getInt32Ty(C); + Type *I32PtrTy = Type::getInt32PtrTy(C); + + // Create a function with a phi that has another phi as an incoming value + Function *F = cast<Function>(M.getOrInsertFunction("f", FunctionType::get(VoidTy, false))); + + BasicBlock *Entry = BasicBlock::Create(C, "entry", F); + BasicBlock *If1 = BasicBlock::Create(C, "if1", F); + BasicBlock *Else1 = BasicBlock::Create(C, "else1", F); + BasicBlock *Then = BasicBlock::Create(C, "then", F); + BasicBlock *If2 = BasicBlock::Create(C, "if2", F); + BasicBlock *Else2 = BasicBlock::Create(C, "else2", F); + BasicBlock *End = BasicBlock::Create(C, "then", F); + BranchInst::Create(If1, Else1, UndefValue::get(I1Ty), Entry); + BranchInst::Create(Then, If1); + BranchInst::Create(Then, Else1); + BranchInst::Create(If2, Else2, UndefValue::get(I1Ty), Then); + BranchInst::Create(End, If2); + BranchInst::Create(End, Else2); + + Value *Val1 = new LoadInst(UndefValue::get(I32PtrTy), "val1", Entry); + Value *Val2 = new LoadInst(UndefValue::get(I32PtrTy), "val2", Entry); + Value *Val3 = new LoadInst(UndefValue::get(I32PtrTy), "val3", Entry); + Value *Val4 = new LoadInst(UndefValue::get(I32PtrTy), "val4", Entry); + + PHINode *Phi1 = PHINode::Create(I32Ty, 2, "phi1", Then); + Phi1->addIncoming(Val1, If1); + Phi1->addIncoming(Val2, Else1); + PHINode *Phi2 = PHINode::Create(I32Ty, 2, "phi2", Then); + Phi2->addIncoming(Val2, If1); + Phi2->addIncoming(Val3, Else1); + PHINode *Phi3 = PHINode::Create(I32Ty, 2, "phi3", End); + Phi3->addIncoming(Phi1, If2); + Phi3->addIncoming(Val3, Else2); + + PhiValues PV(*F); + PhiValues::ValueSet Vals; + + // Check that simple usage works + Vals = PV.getValuesForPhi(Phi1); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val1)); + EXPECT_TRUE(Vals.count(Val2)); + Vals = PV.getValuesForPhi(Phi2); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val2)); + EXPECT_TRUE(Vals.count(Val3)); + Vals = PV.getValuesForPhi(Phi3); + EXPECT_EQ(Vals.size(), 3u); + EXPECT_TRUE(Vals.count(Val1)); + EXPECT_TRUE(Vals.count(Val2)); + EXPECT_TRUE(Vals.count(Val3)); + + // Check that changing an incoming value in the dependent phi changes the depending phi + Phi1->setIncomingValue(0, Val4); + PV.invalidateValue(Phi1); + Vals = PV.getValuesForPhi(Phi1); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val4)); + EXPECT_TRUE(Vals.count(Val2)); + Vals = PV.getValuesForPhi(Phi2); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val2)); + EXPECT_TRUE(Vals.count(Val3)); + Vals = PV.getValuesForPhi(Phi3); + EXPECT_EQ(Vals.size(), 3u); + EXPECT_TRUE(Vals.count(Val4)); + EXPECT_TRUE(Vals.count(Val2)); + EXPECT_TRUE(Vals.count(Val3)); + + // Check that replacing an incoming phi with a value works + Phi3->setIncomingValue(0, Val1); + PV.invalidateValue(Phi3); + Vals = PV.getValuesForPhi(Phi1); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val4)); + EXPECT_TRUE(Vals.count(Val2)); + Vals = PV.getValuesForPhi(Phi2); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val2)); + EXPECT_TRUE(Vals.count(Val3)); + Vals = PV.getValuesForPhi(Phi3); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val1)); + EXPECT_TRUE(Vals.count(Val3)); + + // Check that adding a phi as an incoming value works + Phi3->setIncomingValue(1, Phi2); + PV.invalidateValue(Phi3); + Vals = PV.getValuesForPhi(Phi1); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val4)); + EXPECT_TRUE(Vals.count(Val2)); + Vals = PV.getValuesForPhi(Phi2); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val2)); + EXPECT_TRUE(Vals.count(Val3)); + Vals = PV.getValuesForPhi(Phi3); + EXPECT_EQ(Vals.size(), 3u); + EXPECT_TRUE(Vals.count(Val1)); + EXPECT_TRUE(Vals.count(Val2)); + EXPECT_TRUE(Vals.count(Val3)); + + // Check that replacing an incoming phi then deleting it works + Phi3->setIncomingValue(1, Val2); + PV.invalidateValue(Phi2); + Phi2->eraseFromParent(); + PV.invalidateValue(Phi3); + Vals = PV.getValuesForPhi(Phi1); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val4)); + EXPECT_TRUE(Vals.count(Val2)); + Vals = PV.getValuesForPhi(Phi3); + EXPECT_EQ(Vals.size(), 2u); + EXPECT_TRUE(Vals.count(Val1)); + EXPECT_TRUE(Vals.count(Val2)); +} diff --git a/unittests/Analysis/ScalarEvolutionTest.cpp b/unittests/Analysis/ScalarEvolutionTest.cpp index dffb68ac94f4..98fc44e4923d 100644 --- a/unittests/Analysis/ScalarEvolutionTest.cpp +++ b/unittests/Analysis/ScalarEvolutionTest.cpp @@ -1288,5 +1288,107 @@ TEST_F(ScalarEvolutionsTest, SCEVExpanderNSW) { EXPECT_FALSE(I->hasNoSignedWrap()); } +// Check that SCEV does not save the SCEV -> V +// mapping of SCEV differ from V in NUW flag. +TEST_F(ScalarEvolutionsTest, SCEVCacheNUW) { + /* + * Create the following code: + * func(i64 %a) + * entry: + * %s1 = add i64 %a, -1 + * %s2 = add nuw i64 %a, -1 + * br label %exit + * exit: + * ret %s + */ + + // Create a module. + Module M("SCEVCacheNUW", Context); + + Type *T_int64 = Type::getInt64Ty(Context); + + FunctionType *FTy = + FunctionType::get(Type::getVoidTy(Context), { T_int64 }, false); + Function *F = cast<Function>(M.getOrInsertFunction("func", FTy)); + Argument *Arg = &*F->arg_begin(); + ConstantInt *C = ConstantInt::get(Context, APInt(64, -1)); + + BasicBlock *Entry = BasicBlock::Create(Context, "entry", F); + BasicBlock *Exit = BasicBlock::Create(Context, "exit", F); + + IRBuilder<> Builder(Entry); + auto *S1 = cast<Instruction>(Builder.CreateAdd(Arg, C, "add")); + auto *S2 = cast<Instruction>(Builder.CreateAdd(Arg, C, "add")); + S2->setHasNoUnsignedWrap(true); + Builder.CreateBr(Exit); + + Builder.SetInsertPoint(Exit); + auto *R = cast<Instruction>(Builder.CreateRetVoid()); + + ScalarEvolution SE = buildSE(*F); + // Get S2 first to move it to cache. + const SCEV *SC2 = SE.getSCEV(S2); + EXPECT_TRUE(isa<SCEVAddExpr>(SC2)); + // Now get S1. + const SCEV *SC1 = SE.getSCEV(S1); + EXPECT_TRUE(isa<SCEVAddExpr>(SC1)); + // Expand for S1, it should use S1 not S2 in spite S2 + // first in the cache. + SCEVExpander Exp(SE, M.getDataLayout(), "expander"); + auto *I = cast<Instruction>(Exp.expandCodeFor(SC1, nullptr, R)); + EXPECT_FALSE(I->hasNoUnsignedWrap()); +} + +// Check that SCEV does not save the SCEV -> V +// mapping of SCEV differ from V in NSW flag. +TEST_F(ScalarEvolutionsTest, SCEVCacheNSW) { + /* + * Create the following code: + * func(i64 %a) + * entry: + * %s1 = add i64 %a, -1 + * %s2 = add nsw i64 %a, -1 + * br label %exit + * exit: + * ret %s + */ + + // Create a module. + Module M("SCEVCacheNUW", Context); + + Type *T_int64 = Type::getInt64Ty(Context); + + FunctionType *FTy = + FunctionType::get(Type::getVoidTy(Context), { T_int64 }, false); + Function *F = cast<Function>(M.getOrInsertFunction("func", FTy)); + Argument *Arg = &*F->arg_begin(); + ConstantInt *C = ConstantInt::get(Context, APInt(64, -1)); + + BasicBlock *Entry = BasicBlock::Create(Context, "entry", F); + BasicBlock *Exit = BasicBlock::Create(Context, "exit", F); + + IRBuilder<> Builder(Entry); + auto *S1 = cast<Instruction>(Builder.CreateAdd(Arg, C, "add")); + auto *S2 = cast<Instruction>(Builder.CreateAdd(Arg, C, "add")); + S2->setHasNoSignedWrap(true); + Builder.CreateBr(Exit); + + Builder.SetInsertPoint(Exit); + auto *R = cast<Instruction>(Builder.CreateRetVoid()); + + ScalarEvolution SE = buildSE(*F); + // Get S2 first to move it to cache. + const SCEV *SC2 = SE.getSCEV(S2); + EXPECT_TRUE(isa<SCEVAddExpr>(SC2)); + // Now get S1. + const SCEV *SC1 = SE.getSCEV(S1); + EXPECT_TRUE(isa<SCEVAddExpr>(SC1)); + // Expand for S1, it should use S1 not S2 in spite S2 + // first in the cache. + SCEVExpander Exp(SE, M.getDataLayout(), "expander"); + auto *I = cast<Instruction>(Exp.expandCodeFor(SC1, nullptr, R)); + EXPECT_FALSE(I->hasNoSignedWrap()); +} + } // end anonymous namespace } // end namespace llvm diff --git a/unittests/Analysis/TargetLibraryInfoTest.cpp b/unittests/Analysis/TargetLibraryInfoTest.cpp index ef558a434c7d..ec0f89a7e500 100644 --- a/unittests/Analysis/TargetLibraryInfoTest.cpp +++ b/unittests/Analysis/TargetLibraryInfoTest.cpp @@ -161,8 +161,10 @@ TEST_F(TargetLibraryInfoTest, ValidProto) { "declare i32 @ffsl(i64)\n" "declare i32 @ffsll(i64)\n" "declare i32 @fgetc(%struct*)\n" + "declare i32 @fgetc_unlocked(%struct*)\n" "declare i32 @fgetpos(%struct*, i64*)\n" "declare i8* @fgets(i8*, i32, %struct*)\n" + "declare i8* @fgets_unlocked(i8*, i32, %struct*)\n" "declare i32 @fileno(%struct*)\n" "declare void @flockfile(%struct*)\n" "declare double @floor(double)\n" @@ -182,7 +184,9 @@ TEST_F(TargetLibraryInfoTest, ValidProto) { "declare x86_fp80 @fmodl(x86_fp80, x86_fp80)\n" "declare i32 @fprintf(%struct*, i8*, ...)\n" "declare i32 @fputc(i32, %struct*)\n" + "declare i32 @fputc_unlocked(i32, %struct*)\n" "declare i64 @fread(i8*, i64, i64, %struct*)\n" + "declare i64 @fread_unlocked(i8*, i64, i64, %struct*)\n" "declare void @free(i8*)\n" "declare double @frexp(double, i32*)\n" "declare float @frexpf(float, i32*)\n" @@ -199,6 +203,7 @@ TEST_F(TargetLibraryInfoTest, ValidProto) { "declare i32 @getc(%struct*)\n" "declare i32 @getc_unlocked(%struct*)\n" "declare i32 @getchar()\n" + "declare i32 @getchar_unlocked()\n" "declare i8* @getenv(i8*)\n" "declare i32 @getitimer(i32, %struct*)\n" "declare i32 @getlogin_r(i8*, i64)\n" @@ -250,7 +255,9 @@ TEST_F(TargetLibraryInfoTest, ValidProto) { "declare x86_fp80 @powl(x86_fp80, x86_fp80)\n" "declare i32 @printf(i8*, ...)\n" "declare i32 @putc(i32, %struct*)\n" + "declare i32 @putc_unlocked(i32, %struct*)\n" "declare i32 @putchar(i32)\n" + "declare i32 @putchar_unlocked(i32)\n" "declare i32 @puts(i8*)\n" "declare void @qsort(i8*, i64, i64, i32 (i8*, i8*)*)\n" "declare i64 @readlink(i8*, i8*, i64)\n" @@ -347,8 +354,10 @@ TEST_F(TargetLibraryInfoTest, ValidProto) { "declare %struct* @fdopen(i32, i8*)\n" "declare %struct* @fopen(i8*, i8*)\n" "declare i32 @fputs(i8*, %struct*)\n" + "declare i32 @fputs_unlocked(i8*, %struct*)\n" "declare i32 @fstat(i32, %struct*)\n" "declare i64 @fwrite(i8*, i64, i64, %struct*)\n" + "declare i64 @fwrite_unlocked(i8*, i64, i64, %struct*)\n" "declare i32 @lchown(i8*, i32, i32)\n" "declare i32 @lstat(i8*, %struct*)\n" "declare i64 @mktime(%struct*)\n" @@ -386,20 +395,32 @@ TEST_F(TargetLibraryInfoTest, ValidProto) { "declare void @_ZdaPv(i8*)\n" "declare void @_ZdaPvRKSt9nothrow_t(i8*, %struct*)\n" + "declare void @_ZdaPvSt11align_val_t(i8*, i64)\n" + "declare void @_ZdaPvSt11align_val_tRKSt9nothrow_t(i8*, i64, %struct*)\n" "declare void @_ZdaPvj(i8*, i32)\n" "declare void @_ZdaPvm(i8*, i64)\n" "declare void @_ZdlPv(i8*)\n" "declare void @_ZdlPvRKSt9nothrow_t(i8*, %struct*)\n" + "declare void @_ZdlPvSt11align_val_t(i8*, i64)\n" + "declare void @_ZdlPvSt11align_val_tRKSt9nothrow_t(i8*, i64, %struct*)\n" "declare void @_ZdlPvj(i8*, i32)\n" "declare void @_ZdlPvm(i8*, i64)\n" "declare i8* @_Znaj(i32)\n" "declare i8* @_ZnajRKSt9nothrow_t(i32, %struct*)\n" + "declare i8* @_ZnajSt11align_val_t(i32, i32)\n" + "declare i8* @_ZnajSt11align_val_tRKSt9nothrow_t(i32, i32, %struct*)\n" "declare i8* @_Znam(i64)\n" "declare i8* @_ZnamRKSt9nothrow_t(i64, %struct*)\n" + "declare i8* @_ZnamSt11align_val_t(i64, i64)\n" + "declare i8* @_ZnamSt11align_val_tRKSt9nothrow_t(i64, i64, %struct*)\n" "declare i8* @_Znwj(i32)\n" "declare i8* @_ZnwjRKSt9nothrow_t(i32, %struct*)\n" + "declare i8* @_ZnwjSt11align_val_t(i32, i32)\n" + "declare i8* @_ZnwjSt11align_val_tRKSt9nothrow_t(i32, i32, %struct*)\n" "declare i8* @_Znwm(i64)\n" "declare i8* @_ZnwmRKSt9nothrow_t(i64, %struct*)\n" + "declare i8* @_ZnwmSt11align_val_t(i64, i64)\n" + "declare i8* @_ZnwmSt11align_val_tRKSt9nothrow_t(i64, i64, %struct*)\n" "declare void @\"??3@YAXPEAX@Z\"(i8*)\n" "declare void @\"??3@YAXPEAXAEBUnothrow_t@std@@@Z\"(i8*, %struct*)\n" diff --git a/unittests/Analysis/ValueLatticeTest.cpp b/unittests/Analysis/ValueLatticeTest.cpp index 3e4102564144..b0b379760a53 100644 --- a/unittests/Analysis/ValueLatticeTest.cpp +++ b/unittests/Analysis/ValueLatticeTest.cpp @@ -50,20 +50,26 @@ TEST_F(ValueLatticeTest, MergeIn) { // Merge to lattice values with equal integer constant. auto LV1 = ValueLatticeElement::get(C1); - LV1.mergeIn(ValueLatticeElement::get(C1), M.getDataLayout()); + EXPECT_FALSE(LV1.mergeIn(ValueLatticeElement::get(C1), M.getDataLayout())); EXPECT_TRUE(LV1.isConstantRange()); EXPECT_EQ(LV1.asConstantInteger().getValue().getLimitedValue(), 1U); // Merge LV1 with different integer constant. - LV1.mergeIn(ValueLatticeElement::get(ConstantInt::get(I32Ty, 99)), - M.getDataLayout()); + EXPECT_TRUE(LV1.mergeIn(ValueLatticeElement::get(ConstantInt::get(I32Ty, 99)), + M.getDataLayout())); + EXPECT_TRUE(LV1.isConstantRange()); + EXPECT_EQ(LV1.getConstantRange().getLower().getLimitedValue(), 1U); + EXPECT_EQ(LV1.getConstantRange().getUpper().getLimitedValue(), 100U); + + // Merge constant range with same constant range. + EXPECT_FALSE(LV1.mergeIn(LV1, M.getDataLayout())); EXPECT_TRUE(LV1.isConstantRange()); EXPECT_EQ(LV1.getConstantRange().getLower().getLimitedValue(), 1U); EXPECT_EQ(LV1.getConstantRange().getUpper().getLimitedValue(), 100U); // Merge LV1 in undefined value. ValueLatticeElement LV2; - LV2.mergeIn(LV1, M.getDataLayout()); + EXPECT_TRUE(LV2.mergeIn(LV1, M.getDataLayout())); EXPECT_TRUE(LV1.isConstantRange()); EXPECT_EQ(LV1.getConstantRange().getLower().getLimitedValue(), 1U); EXPECT_EQ(LV1.getConstantRange().getUpper().getLimitedValue(), 100U); @@ -71,77 +77,110 @@ TEST_F(ValueLatticeTest, MergeIn) { EXPECT_EQ(LV2.getConstantRange().getLower().getLimitedValue(), 1U); EXPECT_EQ(LV2.getConstantRange().getUpper().getLimitedValue(), 100U); - // Merge with overdefined. - LV1.mergeIn(ValueLatticeElement::getOverdefined(), M.getDataLayout()); + // Merge LV1 with overdefined. + EXPECT_TRUE( + LV1.mergeIn(ValueLatticeElement::getOverdefined(), M.getDataLayout())); + EXPECT_TRUE(LV1.isOverdefined()); + + // Merge overdefined with overdefined. + EXPECT_FALSE( + LV1.mergeIn(ValueLatticeElement::getOverdefined(), M.getDataLayout())); EXPECT_TRUE(LV1.isOverdefined()); } -TEST_F(ValueLatticeTest, satisfiesPredicateIntegers) { - auto I32Ty = IntegerType::get(Context, 32); +TEST_F(ValueLatticeTest, getCompareIntegers) { + auto *I32Ty = IntegerType::get(Context, 32); + auto *I1Ty = IntegerType::get(Context, 1); auto *C1 = ConstantInt::get(I32Ty, 1); auto LV1 = ValueLatticeElement::get(C1); - // Check satisfiesPredicate for equal integer constants. - EXPECT_TRUE(LV1.satisfiesPredicate(CmpInst::ICMP_EQ, LV1)); - EXPECT_TRUE(LV1.satisfiesPredicate(CmpInst::ICMP_SGE, LV1)); - EXPECT_TRUE(LV1.satisfiesPredicate(CmpInst::ICMP_SLE, LV1)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::ICMP_NE, LV1)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::ICMP_SLT, LV1)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::ICMP_SGT, LV1)); + // Check getCompare for equal integer constants. + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_EQ, I1Ty, LV1)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_SGE, I1Ty, LV1)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_SLE, I1Ty, LV1)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_NE, I1Ty, LV1)->isZeroValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_SLT, I1Ty, LV1)->isZeroValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_SGT, I1Ty, LV1)->isZeroValue()); auto LV2 = ValueLatticeElement::getRange({APInt(32, 10, true), APInt(32, 20, true)}); - // Check satisfiesPredicate with distinct integer ranges. - EXPECT_TRUE(LV1.satisfiesPredicate(CmpInst::ICMP_SLT, LV2)); - EXPECT_TRUE(LV1.satisfiesPredicate(CmpInst::ICMP_SLE, LV2)); - EXPECT_TRUE(LV1.satisfiesPredicate(CmpInst::ICMP_NE, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::ICMP_EQ, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::ICMP_SGE, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::ICMP_SGT, LV2)); + // Check getCompare with distinct integer ranges. + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_SLT, I1Ty, LV2)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_SLE, I1Ty, LV2)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_NE, I1Ty, LV2)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_EQ, I1Ty, LV2)->isZeroValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_SGE, I1Ty, LV2)->isZeroValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::ICMP_SGT, I1Ty, LV2)->isZeroValue()); auto LV3 = ValueLatticeElement::getRange({APInt(32, 15, true), APInt(32, 19, true)}); - // Check satisfiesPredicate with a subset integer ranges. - EXPECT_FALSE(LV2.satisfiesPredicate(CmpInst::ICMP_SLT, LV3)); - EXPECT_FALSE(LV2.satisfiesPredicate(CmpInst::ICMP_SLE, LV3)); - EXPECT_FALSE(LV2.satisfiesPredicate(CmpInst::ICMP_NE, LV3)); - EXPECT_FALSE(LV2.satisfiesPredicate(CmpInst::ICMP_EQ, LV3)); - EXPECT_FALSE(LV2.satisfiesPredicate(CmpInst::ICMP_SGE, LV3)); - EXPECT_FALSE(LV2.satisfiesPredicate(CmpInst::ICMP_SGT, LV3)); + // Check getCompare with a subset integer ranges. + EXPECT_EQ(LV2.getCompare(CmpInst::ICMP_SLT, I1Ty, LV3), nullptr); + EXPECT_EQ(LV2.getCompare(CmpInst::ICMP_SLE, I1Ty, LV3), nullptr); + EXPECT_EQ(LV2.getCompare(CmpInst::ICMP_NE, I1Ty, LV3), nullptr); + EXPECT_EQ(LV2.getCompare(CmpInst::ICMP_EQ, I1Ty, LV3), nullptr); + EXPECT_EQ(LV2.getCompare(CmpInst::ICMP_SGE, I1Ty, LV3), nullptr); + EXPECT_EQ(LV2.getCompare(CmpInst::ICMP_SGT, I1Ty, LV3), nullptr); auto LV4 = ValueLatticeElement::getRange({APInt(32, 15, true), APInt(32, 25, true)}); - // Check satisfiesPredicate with overlapping integer ranges. - EXPECT_FALSE(LV3.satisfiesPredicate(CmpInst::ICMP_SLT, LV4)); - EXPECT_FALSE(LV3.satisfiesPredicate(CmpInst::ICMP_SLE, LV4)); - EXPECT_FALSE(LV3.satisfiesPredicate(CmpInst::ICMP_NE, LV4)); - EXPECT_FALSE(LV3.satisfiesPredicate(CmpInst::ICMP_EQ, LV4)); - EXPECT_FALSE(LV3.satisfiesPredicate(CmpInst::ICMP_SGE, LV4)); - EXPECT_FALSE(LV3.satisfiesPredicate(CmpInst::ICMP_SGT, LV4)); + // Check getCompare with overlapping integer ranges. + EXPECT_EQ(LV3.getCompare(CmpInst::ICMP_SLT, I1Ty, LV4), nullptr); + EXPECT_EQ(LV3.getCompare(CmpInst::ICMP_SLE, I1Ty, LV4), nullptr); + EXPECT_EQ(LV3.getCompare(CmpInst::ICMP_NE, I1Ty, LV4), nullptr); + EXPECT_EQ(LV3.getCompare(CmpInst::ICMP_EQ, I1Ty, LV4), nullptr); + EXPECT_EQ(LV3.getCompare(CmpInst::ICMP_SGE, I1Ty, LV4), nullptr); + EXPECT_EQ(LV3.getCompare(CmpInst::ICMP_SGT, I1Ty, LV4), nullptr); } -TEST_F(ValueLatticeTest, satisfiesPredicateFloat) { - auto FloatTy = IntegerType::getFloatTy(Context); +TEST_F(ValueLatticeTest, getCompareFloat) { + auto *FloatTy = IntegerType::getFloatTy(Context); + auto *I1Ty = IntegerType::get(Context, 1); auto *C1 = ConstantFP::get(FloatTy, 1.0); auto LV1 = ValueLatticeElement::get(C1); auto LV2 = ValueLatticeElement::get(C1); - // Check satisfiesPredicate for equal floating point constants. - EXPECT_TRUE(LV1.satisfiesPredicate(CmpInst::FCMP_OEQ, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OGE, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OLE, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_ONE, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OLT, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OGT, LV2)); - - LV1.mergeIn(ValueLatticeElement::get(ConstantFP::get(FloatTy, 2.2)), - M.getDataLayout()); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OEQ, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OGE, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OLE, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_ONE, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OLT, LV2)); - EXPECT_FALSE(LV1.satisfiesPredicate(CmpInst::FCMP_OGT, LV2)); + // Check getCompare for equal floating point constants. + EXPECT_TRUE(LV1.getCompare(CmpInst::FCMP_OEQ, I1Ty, LV2)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::FCMP_OGE, I1Ty, LV2)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::FCMP_OLE, I1Ty, LV2)->isOneValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::FCMP_ONE, I1Ty, LV2)->isZeroValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::FCMP_OLT, I1Ty, LV2)->isZeroValue()); + EXPECT_TRUE(LV1.getCompare(CmpInst::FCMP_OGT, I1Ty, LV2)->isZeroValue()); + + EXPECT_TRUE( + LV1.mergeIn(ValueLatticeElement::get(ConstantFP::get(FloatTy, 2.2)), + M.getDataLayout())); + EXPECT_EQ(LV1.getCompare(CmpInst::FCMP_OEQ, I1Ty, LV2), nullptr); + EXPECT_EQ(LV1.getCompare(CmpInst::FCMP_OGE, I1Ty, LV2), nullptr); + EXPECT_EQ(LV1.getCompare(CmpInst::FCMP_OLE, I1Ty, LV2), nullptr); + EXPECT_EQ(LV1.getCompare(CmpInst::FCMP_ONE, I1Ty, LV2), nullptr); + EXPECT_EQ(LV1.getCompare(CmpInst::FCMP_OLT, I1Ty, LV2), nullptr); + EXPECT_EQ(LV1.getCompare(CmpInst::FCMP_OGT, I1Ty, LV2), nullptr); +} + +TEST_F(ValueLatticeTest, getCompareUndef) { + auto *I32Ty = IntegerType::get(Context, 32); + auto *I1Ty = IntegerType::get(Context, 1); + + auto LV1 = ValueLatticeElement::get(UndefValue::get(I32Ty)); + auto LV2 = + ValueLatticeElement::getRange({APInt(32, 10, true), APInt(32, 20, true)}); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::ICMP_SLT, I1Ty, LV2))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::ICMP_SLE, I1Ty, LV2))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::ICMP_NE, I1Ty, LV2))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::ICMP_EQ, I1Ty, LV2))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::ICMP_SGE, I1Ty, LV2))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::ICMP_SGT, I1Ty, LV2))); + + auto *FloatTy = IntegerType::getFloatTy(Context); + auto LV3 = ValueLatticeElement::get(ConstantFP::get(FloatTy, 1.0)); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::FCMP_OEQ, I1Ty, LV3))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::FCMP_OGE, I1Ty, LV3))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::FCMP_OLE, I1Ty, LV3))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::FCMP_ONE, I1Ty, LV3))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::FCMP_OLT, I1Ty, LV3))); + EXPECT_TRUE(isa<UndefValue>(LV1.getCompare(CmpInst::FCMP_OGT, I1Ty, LV3))); } } // end anonymous namespace diff --git a/unittests/BinaryFormat/CMakeLists.txt b/unittests/BinaryFormat/CMakeLists.txt index 631936795b6c..95c672e35be8 100644 --- a/unittests/BinaryFormat/CMakeLists.txt +++ b/unittests/BinaryFormat/CMakeLists.txt @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS add_llvm_unittest(BinaryFormatTests DwarfTest.cpp + MachOTest.cpp TestFileMagic.cpp ) diff --git a/unittests/BinaryFormat/DwarfTest.cpp b/unittests/BinaryFormat/DwarfTest.cpp index f24e029beef2..08a731f6174d 100644 --- a/unittests/BinaryFormat/DwarfTest.cpp +++ b/unittests/BinaryFormat/DwarfTest.cpp @@ -9,6 +9,7 @@ #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/FormatVariadic.h" #include "gtest/gtest.h" using namespace llvm; @@ -139,4 +140,67 @@ TEST(DwarfTest, getVirtuality) { EXPECT_EQ(DW_VIRTUALITY_invalid, getVirtuality("something else")); } +TEST(DwarfTest, FixedFormSizes) { + Optional<uint8_t> RefSize; + Optional<uint8_t> AddrSize; + + // Test 32 bit DWARF version 2 with 4 byte addresses. + FormParams Params_2_4_32 = {2, 4, DWARF32}; + RefSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_2_4_32); + AddrSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_2_4_32); + EXPECT_TRUE(RefSize.hasValue()); + EXPECT_TRUE(AddrSize.hasValue()); + EXPECT_EQ(*RefSize, *AddrSize); + + // Test 32 bit DWARF version 2 with 8 byte addresses. + FormParams Params_2_8_32 = {2, 8, DWARF32}; + RefSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_2_8_32); + AddrSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_2_8_32); + EXPECT_TRUE(RefSize.hasValue()); + EXPECT_TRUE(AddrSize.hasValue()); + EXPECT_EQ(*RefSize, *AddrSize); + + // DW_FORM_ref_addr is 4 bytes in DWARF 32 in DWARF version 3 and beyond. + FormParams Params_3_4_32 = {3, 4, DWARF32}; + RefSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_3_4_32); + EXPECT_TRUE(RefSize.hasValue()); + EXPECT_EQ(*RefSize, 4); + + FormParams Params_4_4_32 = {4, 4, DWARF32}; + RefSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_4_4_32); + EXPECT_TRUE(RefSize.hasValue()); + EXPECT_EQ(*RefSize, 4); + + FormParams Params_5_4_32 = {5, 4, DWARF32}; + RefSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_5_4_32); + EXPECT_TRUE(RefSize.hasValue()); + EXPECT_EQ(*RefSize, 4); + + // DW_FORM_ref_addr is 8 bytes in DWARF 64 in DWARF version 3 and beyond. + FormParams Params_3_8_64 = {3, 8, DWARF64}; + RefSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_3_8_64); + EXPECT_TRUE(RefSize.hasValue()); + EXPECT_EQ(*RefSize, 8); + + FormParams Params_4_8_64 = {4, 8, DWARF64}; + RefSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_4_8_64); + EXPECT_TRUE(RefSize.hasValue()); + EXPECT_EQ(*RefSize, 8); + + FormParams Params_5_8_64 = {5, 8, DWARF64}; + RefSize = getFixedFormByteSize(DW_FORM_ref_addr, Params_5_8_64); + EXPECT_TRUE(RefSize.hasValue()); + EXPECT_EQ(*RefSize, 8); +} + +TEST(DwarfTest, format_provider) { + EXPECT_EQ("DW_AT_name", formatv("{0}", DW_AT_name).str()); + EXPECT_EQ("DW_AT_unknown_3fff", formatv("{0}", DW_AT_hi_user).str()); + EXPECT_EQ("DW_FORM_addr", formatv("{0}", DW_FORM_addr).str()); + EXPECT_EQ("DW_FORM_unknown_1f00", formatv("{0}", DW_FORM_lo_user).str()); + EXPECT_EQ("DW_IDX_compile_unit", formatv("{0}", DW_IDX_compile_unit).str()); + EXPECT_EQ("DW_IDX_unknown_3fff", formatv("{0}", DW_IDX_hi_user).str()); + EXPECT_EQ("DW_TAG_compile_unit", formatv("{0}", DW_TAG_compile_unit).str()); + EXPECT_EQ("DW_TAG_unknown_ffff", formatv("{0}", DW_TAG_hi_user).str()); +} } // end namespace diff --git a/unittests/BinaryFormat/MachOTest.cpp b/unittests/BinaryFormat/MachOTest.cpp new file mode 100644 index 000000000000..da8e9e8c6901 --- /dev/null +++ b/unittests/BinaryFormat/MachOTest.cpp @@ -0,0 +1,47 @@ +//===- unittest/BinaryFormat/MachOTest.cpp - MachO support tests ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/BinaryFormat/MachO.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::MachO; + +TEST(MachOTest, UnalignedLC) { + unsigned char Valid32BitMachO[] = { + 0xCE, 0xFA, 0xED, 0xFE, 0x07, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, + 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, + 0x85, 0x80, 0x21, 0x01, 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x5F, 0x5F, 0x50, 0x41, 0x47, 0x45, 0x5A, 0x45, 0x52, 0x4F, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, 0x5F, 0x5F, 0x4C, 0x49, + 0x4E, 0x4B, 0x45, 0x44, 0x49, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x40, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, + 0x8C, 0x0B, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; + + mach_header *Header = + reinterpret_cast<mach_header *>(Valid32BitMachO); + if (!sys::IsLittleEndianHost) + swapStruct(*Header); + ASSERT_EQ(Header->magic, MH_MAGIC); + unsigned char *Current = Valid32BitMachO + sizeof(mach_header); + unsigned char *BufferEnd = + Valid32BitMachO + sizeof(mach_header) + Header->sizeofcmds; + while (Current < BufferEnd) { + macho_load_command *LC = + reinterpret_cast<macho_load_command *>(Current); + if (!sys::IsLittleEndianHost) + swapStruct(LC->load_command_data); + ASSERT_EQ(LC->load_command_data.cmd, LC_SEGMENT); + Current += LC->load_command_data.cmdsize; + } +} diff --git a/unittests/BinaryFormat/TestFileMagic.cpp b/unittests/BinaryFormat/TestFileMagic.cpp index ca4ca9a27281..5f132ba4144d 100644 --- a/unittests/BinaryFormat/TestFileMagic.cpp +++ b/unittests/BinaryFormat/TestFileMagic.cpp @@ -81,6 +81,8 @@ const char windows_resource[] = const char macho_dynamically_linked_shared_lib_stub[] = "\xfe\xed\xfa\xce........\x00\x00\x00\x09............"; const char ms_dos_stub_broken[] = "\x4d\x5a\x20\x20"; +const char pdb[] = "Microsoft C/C++ MSF 7.00\r\n\x1a" + "DS\x00\x00\x00"; TEST_F(MagicTest, Magic) { struct type { @@ -110,6 +112,7 @@ TEST_F(MagicTest, Magic) { DEFINE(macho_dsym_companion), DEFINE(macho_kext_bundle), DEFINE(windows_resource), + DEFINE(pdb), {"ms_dos_stub_broken", ms_dos_stub_broken, sizeof(ms_dos_stub_broken), file_magic::unknown}, #undef DEFINE diff --git a/unittests/Bitcode/BitReaderTest.cpp b/unittests/Bitcode/BitReaderTest.cpp index 5efb7faf508d..3b4600718b53 100644 --- a/unittests/Bitcode/BitReaderTest.cpp +++ b/unittests/Bitcode/BitReaderTest.cpp @@ -44,7 +44,7 @@ std::unique_ptr<Module> parseAssembly(LLVMContext &Context, static void writeModuleToBuffer(std::unique_ptr<Module> Mod, SmallVectorImpl<char> &Buffer) { raw_svector_ostream OS(Buffer); - WriteBitcodeToFile(Mod.get(), OS); + WriteBitcodeToFile(*Mod, OS); } static std::unique_ptr<Module> getLazyModuleFromAssembly(LLVMContext &Context, diff --git a/unittests/CMakeLists.txt b/unittests/CMakeLists.txt index 94aca0566256..f9166509b4ef 100644 --- a/unittests/CMakeLists.txt +++ b/unittests/CMakeLists.txt @@ -8,9 +8,11 @@ endfunction() add_subdirectory(ADT) add_subdirectory(Analysis) add_subdirectory(AsmParser) +add_subdirectory(BinaryFormat) add_subdirectory(Bitcode) add_subdirectory(CodeGen) add_subdirectory(DebugInfo) +add_subdirectory(Demangle) add_subdirectory(ExecutionEngine) add_subdirectory(FuzzMutate) add_subdirectory(IR) @@ -19,9 +21,9 @@ add_subdirectory(Linker) add_subdirectory(MC) add_subdirectory(MI) add_subdirectory(Object) -add_subdirectory(BinaryFormat) add_subdirectory(ObjectYAML) add_subdirectory(Option) +add_subdirectory(Passes) add_subdirectory(ProfileData) add_subdirectory(Support) add_subdirectory(Target) diff --git a/unittests/CodeGen/CMakeLists.txt b/unittests/CodeGen/CMakeLists.txt index 05b72ce05b7b..3a9112740e0a 100644 --- a/unittests/CodeGen/CMakeLists.txt +++ b/unittests/CodeGen/CMakeLists.txt @@ -8,7 +8,7 @@ set(LLVM_LINK_COMPONENTS Target ) -set(CodeGenSources +add_llvm_unittest(CodeGenTests DIEHashTest.cpp LowLevelTypeTest.cpp MachineInstrBundleIteratorTest.cpp @@ -17,8 +17,4 @@ set(CodeGenSources ScalableVectorMVTsTest.cpp ) -add_llvm_unittest(CodeGenTests - ${CodeGenSources} - ) - add_subdirectory(GlobalISel) diff --git a/unittests/CodeGen/GlobalISel/CMakeLists.txt b/unittests/CodeGen/GlobalISel/CMakeLists.txt index 075bb44bc330..85f5dcd6d040 100644 --- a/unittests/CodeGen/GlobalISel/CMakeLists.txt +++ b/unittests/CodeGen/GlobalISel/CMakeLists.txt @@ -1,8 +1,15 @@ set(LLVM_LINK_COMPONENTS - GlobalISel + ${LLVM_TARGETS_TO_BUILD} CodeGen + Core + GlobalISel + MC + MIRParser + Support + Target ) add_llvm_unittest(GlobalISelTests LegalizerInfoTest.cpp + PatternMatchTest.cpp ) diff --git a/unittests/CodeGen/GlobalISel/LegalizerInfoTest.cpp b/unittests/CodeGen/GlobalISel/LegalizerInfoTest.cpp index ca7b47e87eda..42371e63da9d 100644 --- a/unittests/CodeGen/GlobalISel/LegalizerInfoTest.cpp +++ b/unittests/CodeGen/GlobalISel/LegalizerInfoTest.cpp @@ -12,22 +12,24 @@ #include "gtest/gtest.h" using namespace llvm; +using namespace LegalizeActions; // Define a couple of pretty printers to help debugging when things go wrong. namespace llvm { std::ostream & -operator<<(std::ostream &OS, const llvm::LegalizerInfo::LegalizeAction Act) { +operator<<(std::ostream &OS, const LegalizeAction Act) { switch (Act) { - case LegalizerInfo::Lower: OS << "Lower"; break; - case LegalizerInfo::Legal: OS << "Legal"; break; - case LegalizerInfo::NarrowScalar: OS << "NarrowScalar"; break; - case LegalizerInfo::WidenScalar: OS << "WidenScalar"; break; - case LegalizerInfo::FewerElements: OS << "FewerElements"; break; - case LegalizerInfo::MoreElements: OS << "MoreElements"; break; - case LegalizerInfo::Libcall: OS << "Libcall"; break; - case LegalizerInfo::Custom: OS << "Custom"; break; - case LegalizerInfo::Unsupported: OS << "Unsupported"; break; - case LegalizerInfo::NotFound: OS << "NotFound"; + case Lower: OS << "Lower"; break; + case Legal: OS << "Legal"; break; + case NarrowScalar: OS << "NarrowScalar"; break; + case WidenScalar: OS << "WidenScalar"; break; + case FewerElements: OS << "FewerElements"; break; + case MoreElements: OS << "MoreElements"; break; + case Libcall: OS << "Libcall"; break; + case Custom: OS << "Custom"; break; + case Unsupported: OS << "Unsupported"; break; + case NotFound: OS << "NotFound"; break; + case UseLegacyRules: OS << "UseLegacyRules"; break; } return OS; } @@ -51,7 +53,7 @@ TEST(LegalizerInfoTest, ScalarRISC) { // Typical RISCy set of operations based on AArch64. for (unsigned Op : {G_ADD, G_SUB}) { for (unsigned Size : {32, 64}) - L.setAction({Op, 0, LLT::scalar(Size)}, LegalizerInfo::Legal); + L.setAction({Op, 0, LLT::scalar(Size)}, Legal); L.setLegalizeScalarToDifferentSizeStrategy( Op, 0, LegalizerInfo::widenToLargerTypesAndNarrowToLargest); } @@ -60,29 +62,29 @@ TEST(LegalizerInfoTest, ScalarRISC) { for (unsigned opcode : {G_ADD, G_SUB}) { // Check we infer the correct types and actually do what we're told. - ASSERT_EQ(L.getAction({opcode, LLT::scalar(8)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(32))); - ASSERT_EQ(L.getAction({opcode, LLT::scalar(16)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(32))); - ASSERT_EQ(L.getAction({opcode, LLT::scalar(32)}), - std::make_pair(LegalizerInfo::Legal, LLT::scalar(32))); - ASSERT_EQ(L.getAction({opcode, LLT::scalar(64)}), - std::make_pair(LegalizerInfo::Legal, LLT::scalar(64))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(8)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(32))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(16)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(32))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(32)}}), + LegalizeActionStep(Legal, 0, LLT{})); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(64)}}), + LegalizeActionStep(Legal, 0, LLT{})); // Make sure the default for over-sized types applies. - ASSERT_EQ(L.getAction({opcode, LLT::scalar(128)}), - std::make_pair(LegalizerInfo::NarrowScalar, LLT::scalar(64))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(128)}}), + LegalizeActionStep(NarrowScalar, 0, LLT::scalar(64))); // Make sure we also handle unusual sizes - ASSERT_EQ(L.getAction({opcode, LLT::scalar(1)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(32))); - ASSERT_EQ(L.getAction({opcode, LLT::scalar(31)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(32))); - ASSERT_EQ(L.getAction({opcode, LLT::scalar(33)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(64))); - ASSERT_EQ(L.getAction({opcode, LLT::scalar(63)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(64))); - ASSERT_EQ(L.getAction({opcode, LLT::scalar(65)}), - std::make_pair(LegalizerInfo::NarrowScalar, LLT::scalar(64))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(1)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(32))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(31)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(32))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(33)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(64))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(63)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(64))); + ASSERT_EQ(L.getAction({opcode, {LLT::scalar(65)}}), + LegalizeActionStep(NarrowScalar, 0, LLT::scalar(64))); } } @@ -90,35 +92,35 @@ TEST(LegalizerInfoTest, VectorRISC) { using namespace TargetOpcode; LegalizerInfo L; // Typical RISCy set of operations based on ARM. - L.setAction({G_ADD, LLT::vector(8, 8)}, LegalizerInfo::Legal); - L.setAction({G_ADD, LLT::vector(16, 8)}, LegalizerInfo::Legal); - L.setAction({G_ADD, LLT::vector(4, 16)}, LegalizerInfo::Legal); - L.setAction({G_ADD, LLT::vector(8, 16)}, LegalizerInfo::Legal); - L.setAction({G_ADD, LLT::vector(2, 32)}, LegalizerInfo::Legal); - L.setAction({G_ADD, LLT::vector(4, 32)}, LegalizerInfo::Legal); + L.setAction({G_ADD, LLT::vector(8, 8)}, Legal); + L.setAction({G_ADD, LLT::vector(16, 8)}, Legal); + L.setAction({G_ADD, LLT::vector(4, 16)}, Legal); + L.setAction({G_ADD, LLT::vector(8, 16)}, Legal); + L.setAction({G_ADD, LLT::vector(2, 32)}, Legal); + L.setAction({G_ADD, LLT::vector(4, 32)}, Legal); L.setLegalizeVectorElementToDifferentSizeStrategy( G_ADD, 0, LegalizerInfo::widenToLargerTypesUnsupportedOtherwise); - L.setAction({G_ADD, 0, LLT::scalar(32)}, LegalizerInfo::Legal); + L.setAction({G_ADD, 0, LLT::scalar(32)}, Legal); L.computeTables(); // Check we infer the correct types and actually do what we're told for some // simple cases. - ASSERT_EQ(L.getAction({G_ADD, LLT::vector(8, 8)}), - std::make_pair(LegalizerInfo::Legal, LLT::vector(8, 8))); - ASSERT_EQ(L.getAction({G_ADD, LLT::vector(8, 7)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::vector(8, 8))); - ASSERT_EQ(L.getAction({G_ADD, LLT::vector(2, 8)}), - std::make_pair(LegalizerInfo::MoreElements, LLT::vector(8, 8))); - ASSERT_EQ(L.getAction({G_ADD, LLT::vector(8, 32)}), - std::make_pair(LegalizerInfo::FewerElements, LLT::vector(4, 32))); + ASSERT_EQ(L.getAction({G_ADD, {LLT::vector(8, 8)}}), + LegalizeActionStep(Legal, 0, LLT{})); + ASSERT_EQ(L.getAction({G_ADD, {LLT::vector(8, 7)}}), + LegalizeActionStep(WidenScalar, 0, LLT::vector(8, 8))); + ASSERT_EQ(L.getAction({G_ADD, {LLT::vector(2, 8)}}), + LegalizeActionStep(MoreElements, 0, LLT::vector(8, 8))); + ASSERT_EQ(L.getAction({G_ADD, {LLT::vector(8, 32)}}), + LegalizeActionStep(FewerElements, 0, LLT::vector(4, 32))); // Check a few non-power-of-2 sizes: - ASSERT_EQ(L.getAction({G_ADD, LLT::vector(3, 3)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::vector(3, 8))); - ASSERT_EQ(L.getAction({G_ADD, LLT::vector(3, 8)}), - std::make_pair(LegalizerInfo::MoreElements, LLT::vector(8, 8))); + ASSERT_EQ(L.getAction({G_ADD, {LLT::vector(3, 3)}}), + LegalizeActionStep(WidenScalar, 0, LLT::vector(3, 8))); + ASSERT_EQ(L.getAction({G_ADD, {LLT::vector(3, 8)}}), + LegalizeActionStep(MoreElements, 0, LLT::vector(8, 8))); } TEST(LegalizerInfoTest, MultipleTypes) { @@ -128,8 +130,8 @@ TEST(LegalizerInfoTest, MultipleTypes) { LLT s64 = LLT::scalar(64); // Typical RISCy set of operations based on AArch64. - L.setAction({G_PTRTOINT, 0, s64}, LegalizerInfo::Legal); - L.setAction({G_PTRTOINT, 1, p0}, LegalizerInfo::Legal); + L.setAction({G_PTRTOINT, 0, s64}, Legal); + L.setAction({G_PTRTOINT, 1, p0}, Legal); L.setLegalizeScalarToDifferentSizeStrategy( G_PTRTOINT, 0, LegalizerInfo::widenToLargerTypesAndNarrowToLargest); @@ -137,15 +139,16 @@ TEST(LegalizerInfoTest, MultipleTypes) { L.computeTables(); // Check we infer the correct types and actually do what we're told. - ASSERT_EQ(L.getAction({G_PTRTOINT, 0, s64}), - std::make_pair(LegalizerInfo::Legal, s64)); - ASSERT_EQ(L.getAction({G_PTRTOINT, 1, p0}), - std::make_pair(LegalizerInfo::Legal, p0)); + ASSERT_EQ(L.getAction({G_PTRTOINT, {s64, p0}}), + LegalizeActionStep(Legal, 0, LLT{})); + // Make sure we also handle unusual sizes - ASSERT_EQ(L.getAction({G_PTRTOINT, 0, LLT::scalar(65)}), - std::make_pair(LegalizerInfo::NarrowScalar, s64)); - ASSERT_EQ(L.getAction({G_PTRTOINT, 1, LLT::pointer(0, 32)}), - std::make_pair(LegalizerInfo::Unsupported, LLT::pointer(0, 32))); + ASSERT_EQ( + L.getAction({G_PTRTOINT, {LLT::scalar(65), s64}}), + LegalizeActionStep(NarrowScalar, 0, s64)); + ASSERT_EQ( + L.getAction({G_PTRTOINT, {s64, LLT::pointer(0, 32)}}), + LegalizeActionStep(Unsupported, 1, LLT::pointer(0, 32))); } TEST(LegalizerInfoTest, MultipleSteps) { @@ -156,22 +159,22 @@ TEST(LegalizerInfoTest, MultipleSteps) { L.setLegalizeScalarToDifferentSizeStrategy( G_UREM, 0, LegalizerInfo::widenToLargerTypesUnsupportedOtherwise); - L.setAction({G_UREM, 0, s32}, LegalizerInfo::Lower); - L.setAction({G_UREM, 0, s64}, LegalizerInfo::Lower); + L.setAction({G_UREM, 0, s32}, Lower); + L.setAction({G_UREM, 0, s64}, Lower); L.computeTables(); - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(16)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(32))); - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(32)}), - std::make_pair(LegalizerInfo::Lower, LLT::scalar(32))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(16)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(32))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(32)}}), + LegalizeActionStep(Lower, 0, LLT::scalar(32))); } TEST(LegalizerInfoTest, SizeChangeStrategy) { using namespace TargetOpcode; LegalizerInfo L; for (unsigned Size : {1, 8, 16, 32}) - L.setAction({G_UREM, 0, LLT::scalar(Size)}, LegalizerInfo::Legal); + L.setAction({G_UREM, 0, LLT::scalar(Size)}, Legal); L.setLegalizeScalarToDifferentSizeStrategy( G_UREM, 0, LegalizerInfo::widenToLargerTypesUnsupportedOtherwise); @@ -179,20 +182,20 @@ TEST(LegalizerInfoTest, SizeChangeStrategy) { // Check we infer the correct types and actually do what we're told. for (unsigned Size : {1, 8, 16, 32}) { - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(Size)}), - std::make_pair(LegalizerInfo::Legal, LLT::scalar(Size))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(Size)}}), + LegalizeActionStep(Legal, 0, LLT{})); } - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(2)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(8))); - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(7)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(8))); - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(9)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(16))); - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(17)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(32))); - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(31)}), - std::make_pair(LegalizerInfo::WidenScalar, LLT::scalar(32))); - ASSERT_EQ(L.getAction({G_UREM, LLT::scalar(33)}), - std::make_pair(LegalizerInfo::Unsupported, LLT::scalar(33))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(2)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(8))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(7)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(8))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(9)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(16))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(17)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(32))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(31)}}), + LegalizeActionStep(WidenScalar, 0, LLT::scalar(32))); + ASSERT_EQ(L.getAction({G_UREM, {LLT::scalar(33)}}), + LegalizeActionStep(Unsupported, 0, LLT::scalar(33))); } } diff --git a/unittests/CodeGen/GlobalISel/PatternMatchTest.cpp b/unittests/CodeGen/GlobalISel/PatternMatchTest.cpp new file mode 100644 index 000000000000..8f17b1991df5 --- /dev/null +++ b/unittests/CodeGen/GlobalISel/PatternMatchTest.cpp @@ -0,0 +1,493 @@ +//===- PatternMatchTest.cpp -----------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/CodeGen/GlobalISel/ConstantFoldingMIRBuilder.h" +#include "llvm/CodeGen/GlobalISel/MIPatternMatch.h" +#include "llvm/CodeGen/GlobalISel/MachineIRBuilder.h" +#include "llvm/CodeGen/GlobalISel/Utils.h" +#include "llvm/CodeGen/MIRParser/MIRParser.h" +#include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/CodeGen/TargetFrameLowering.h" +#include "llvm/CodeGen/TargetInstrInfo.h" +#include "llvm/CodeGen/TargetLowering.h" +#include "llvm/CodeGen/TargetSubtargetInfo.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "llvm/Target/TargetOptions.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace MIPatternMatch; + +namespace { + +void initLLVM() { + InitializeAllTargets(); + InitializeAllTargetMCs(); + InitializeAllAsmPrinters(); + InitializeAllAsmParsers(); + + PassRegistry *Registry = PassRegistry::getPassRegistry(); + initializeCore(*Registry); + initializeCodeGen(*Registry); +} + +/// Create a TargetMachine. As we lack a dedicated always available target for +/// unittests, we go for "AArch64". +std::unique_ptr<TargetMachine> createTargetMachine() { + Triple TargetTriple("aarch64--"); + std::string Error; + const Target *T = TargetRegistry::lookupTarget("", TargetTriple, Error); + if (!T) + return nullptr; + + TargetOptions Options; + return std::unique_ptr<TargetMachine>(T->createTargetMachine( + "AArch64", "", "", Options, None, None, CodeGenOpt::Aggressive)); +} + +std::unique_ptr<Module> parseMIR(LLVMContext &Context, + std::unique_ptr<MIRParser> &MIR, + const TargetMachine &TM, StringRef MIRCode, + const char *FuncName, MachineModuleInfo &MMI) { + SMDiagnostic Diagnostic; + std::unique_ptr<MemoryBuffer> MBuffer = MemoryBuffer::getMemBuffer(MIRCode); + MIR = createMIRParser(std::move(MBuffer), Context); + if (!MIR) + return nullptr; + + std::unique_ptr<Module> M = MIR->parseIRModule(); + if (!M) + return nullptr; + + M->setDataLayout(TM.createDataLayout()); + + if (MIR->parseMachineFunctions(*M, MMI)) + return nullptr; + + return M; +} + +std::pair<std::unique_ptr<Module>, std::unique_ptr<MachineModuleInfo>> +createDummyModule(LLVMContext &Context, const TargetMachine &TM, + StringRef MIRFunc) { + SmallString<512> S; + StringRef MIRString = (Twine(R"MIR( +--- +... +name: func +registers: + - { id: 0, class: _ } + - { id: 1, class: _ } + - { id: 2, class: _ } + - { id: 3, class: _ } +body: | + bb.1: + %0(s64) = COPY $x0 + %1(s64) = COPY $x1 + %2(s64) = COPY $x2 +)MIR") + Twine(MIRFunc) + Twine("...\n")) + .toNullTerminatedStringRef(S); + std::unique_ptr<MIRParser> MIR; + auto MMI = make_unique<MachineModuleInfo>(&TM); + std::unique_ptr<Module> M = + parseMIR(Context, MIR, TM, MIRString, "func", *MMI); + return make_pair(std::move(M), std::move(MMI)); +} + +static MachineFunction *getMFFromMMI(const Module *M, + const MachineModuleInfo *MMI) { + Function *F = M->getFunction("func"); + auto *MF = MMI->getMachineFunction(*F); + return MF; +} + +static void collectCopies(SmallVectorImpl<unsigned> &Copies, + MachineFunction *MF) { + for (auto &MBB : *MF) + for (MachineInstr &MI : MBB) { + if (MI.getOpcode() == TargetOpcode::COPY) + Copies.push_back(MI.getOperand(0).getReg()); + } +} + +TEST(PatternMatchInstr, MatchIntConstant) { + LLVMContext Context; + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + if (!TM) + return; + auto ModuleMMIPair = createDummyModule(Context, *TM, ""); + MachineFunction *MF = + getMFFromMMI(ModuleMMIPair.first.get(), ModuleMMIPair.second.get()); + SmallVector<unsigned, 4> Copies; + collectCopies(Copies, MF); + MachineBasicBlock *EntryMBB = &*MF->begin(); + MachineIRBuilder B(*MF); + MachineRegisterInfo &MRI = MF->getRegInfo(); + B.setInsertPt(*EntryMBB, EntryMBB->end()); + auto MIBCst = B.buildConstant(LLT::scalar(64), 42); + int64_t Cst; + bool match = mi_match(MIBCst->getOperand(0).getReg(), MRI, m_ICst(Cst)); + ASSERT_TRUE(match); + ASSERT_EQ(Cst, 42); +} + +TEST(PatternMatchInstr, MatchBinaryOp) { + LLVMContext Context; + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + if (!TM) + return; + auto ModuleMMIPair = createDummyModule(Context, *TM, ""); + MachineFunction *MF = + getMFFromMMI(ModuleMMIPair.first.get(), ModuleMMIPair.second.get()); + SmallVector<unsigned, 4> Copies; + collectCopies(Copies, MF); + MachineBasicBlock *EntryMBB = &*MF->begin(); + MachineIRBuilder B(*MF); + MachineRegisterInfo &MRI = MF->getRegInfo(); + B.setInsertPt(*EntryMBB, EntryMBB->end()); + LLT s64 = LLT::scalar(64); + auto MIBAdd = B.buildAdd(s64, Copies[0], Copies[1]); + // Test case for no bind. + bool match = + mi_match(MIBAdd->getOperand(0).getReg(), MRI, m_GAdd(m_Reg(), m_Reg())); + ASSERT_TRUE(match); + unsigned Src0, Src1, Src2; + match = mi_match(MIBAdd->getOperand(0).getReg(), MRI, + m_GAdd(m_Reg(Src0), m_Reg(Src1))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + ASSERT_EQ(Src1, Copies[1]); + + // Build MUL(ADD %0, %1), %2 + auto MIBMul = B.buildMul(s64, MIBAdd, Copies[2]); + + // Try to match MUL. + match = mi_match(MIBMul->getOperand(0).getReg(), MRI, + m_GMul(m_Reg(Src0), m_Reg(Src1))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, MIBAdd->getOperand(0).getReg()); + ASSERT_EQ(Src1, Copies[2]); + + // Try to match MUL(ADD) + match = mi_match(MIBMul->getOperand(0).getReg(), MRI, + m_GMul(m_GAdd(m_Reg(Src0), m_Reg(Src1)), m_Reg(Src2))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + ASSERT_EQ(Src1, Copies[1]); + ASSERT_EQ(Src2, Copies[2]); + + // Test Commutativity. + auto MIBMul2 = B.buildMul(s64, Copies[0], B.buildConstant(s64, 42)); + // Try to match MUL(Cst, Reg) on src of MUL(Reg, Cst) to validate + // commutativity. + int64_t Cst; + match = mi_match(MIBMul2->getOperand(0).getReg(), MRI, + m_GMul(m_ICst(Cst), m_Reg(Src0))); + ASSERT_TRUE(match); + ASSERT_EQ(Cst, 42); + ASSERT_EQ(Src0, Copies[0]); + + // Make sure commutative doesn't work with something like SUB. + auto MIBSub = B.buildSub(s64, Copies[0], B.buildConstant(s64, 42)); + match = mi_match(MIBSub->getOperand(0).getReg(), MRI, + m_GSub(m_ICst(Cst), m_Reg(Src0))); + ASSERT_FALSE(match); + + auto MIBFMul = B.buildInstr(TargetOpcode::G_FMUL, s64, Copies[0], + B.buildConstant(s64, 42)); + // Match and test commutativity for FMUL. + match = mi_match(MIBFMul->getOperand(0).getReg(), MRI, + m_GFMul(m_ICst(Cst), m_Reg(Src0))); + ASSERT_TRUE(match); + ASSERT_EQ(Cst, 42); + ASSERT_EQ(Src0, Copies[0]); + + // FSUB + auto MIBFSub = B.buildInstr(TargetOpcode::G_FSUB, s64, Copies[0], + B.buildConstant(s64, 42)); + match = mi_match(MIBFSub->getOperand(0).getReg(), MRI, + m_GFSub(m_Reg(Src0), m_Reg())); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + + // Build AND %0, %1 + auto MIBAnd = B.buildAnd(s64, Copies[0], Copies[1]); + // Try to match AND. + match = mi_match(MIBAnd->getOperand(0).getReg(), MRI, + m_GAnd(m_Reg(Src0), m_Reg(Src1))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + ASSERT_EQ(Src1, Copies[1]); + + // Build OR %0, %1 + auto MIBOr = B.buildOr(s64, Copies[0], Copies[1]); + // Try to match OR. + match = mi_match(MIBOr->getOperand(0).getReg(), MRI, + m_GOr(m_Reg(Src0), m_Reg(Src1))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + ASSERT_EQ(Src1, Copies[1]); + + // Try to use the FoldableInstructionsBuilder to build binary ops. + ConstantFoldingMIRBuilder CFB(B.getState()); + LLT s32 = LLT::scalar(32); + auto MIBCAdd = + CFB.buildAdd(s32, CFB.buildConstant(s32, 0), CFB.buildConstant(s32, 1)); + // This should be a constant now. + match = mi_match(MIBCAdd->getOperand(0).getReg(), MRI, m_ICst(Cst)); + ASSERT_TRUE(match); + ASSERT_EQ(Cst, 1); + auto MIBCAdd1 = + CFB.buildInstr(TargetOpcode::G_ADD, s32, CFB.buildConstant(s32, 0), + CFB.buildConstant(s32, 1)); + // This should be a constant now. + match = mi_match(MIBCAdd1->getOperand(0).getReg(), MRI, m_ICst(Cst)); + ASSERT_TRUE(match); + ASSERT_EQ(Cst, 1); + + // Try one of the other constructors of MachineIRBuilder to make sure it's + // compatible. + ConstantFoldingMIRBuilder CFB1(*MF); + CFB1.setInsertPt(*EntryMBB, EntryMBB->end()); + auto MIBCSub = + CFB1.buildInstr(TargetOpcode::G_SUB, s32, CFB1.buildConstant(s32, 1), + CFB1.buildConstant(s32, 1)); + // This should be a constant now. + match = mi_match(MIBCSub->getOperand(0).getReg(), MRI, m_ICst(Cst)); + ASSERT_TRUE(match); + ASSERT_EQ(Cst, 0); +} + +TEST(PatternMatchInstr, MatchFPUnaryOp) { + LLVMContext Context; + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + if (!TM) + return; + auto ModuleMMIPair = createDummyModule(Context, *TM, ""); + MachineFunction *MF = + getMFFromMMI(ModuleMMIPair.first.get(), ModuleMMIPair.second.get()); + SmallVector<unsigned, 4> Copies; + collectCopies(Copies, MF); + MachineBasicBlock *EntryMBB = &*MF->begin(); + MachineIRBuilder B(*MF); + MachineRegisterInfo &MRI = MF->getRegInfo(); + B.setInsertPt(*EntryMBB, EntryMBB->end()); + + // Truncate s64 to s32. + LLT s32 = LLT::scalar(32); + auto Copy0s32 = B.buildFPTrunc(s32, Copies[0]); + + // Match G_FABS. + auto MIBFabs = B.buildInstr(TargetOpcode::G_FABS, s32, Copy0s32); + bool match = mi_match(MIBFabs->getOperand(0).getReg(), MRI, m_GFabs(m_Reg())); + ASSERT_TRUE(match); + + unsigned Src; + auto MIBFNeg = B.buildInstr(TargetOpcode::G_FNEG, s32, Copy0s32); + match = mi_match(MIBFNeg->getOperand(0).getReg(), MRI, m_GFNeg(m_Reg(Src))); + ASSERT_TRUE(match); + ASSERT_EQ(Src, Copy0s32->getOperand(0).getReg()); + + match = mi_match(MIBFabs->getOperand(0).getReg(), MRI, m_GFabs(m_Reg(Src))); + ASSERT_TRUE(match); + ASSERT_EQ(Src, Copy0s32->getOperand(0).getReg()); + + // Build and match FConstant. + auto MIBFCst = B.buildFConstant(s32, .5); + const ConstantFP *TmpFP{}; + match = mi_match(MIBFCst->getOperand(0).getReg(), MRI, m_GFCst(TmpFP)); + ASSERT_TRUE(match); + ASSERT_TRUE(TmpFP); + APFloat APF((float).5); + auto *CFP = ConstantFP::get(Context, APF); + ASSERT_EQ(CFP, TmpFP); + + // Build double float. + LLT s64 = LLT::scalar(64); + auto MIBFCst64 = B.buildFConstant(s64, .5); + const ConstantFP *TmpFP64{}; + match = mi_match(MIBFCst64->getOperand(0).getReg(), MRI, m_GFCst(TmpFP64)); + ASSERT_TRUE(match); + ASSERT_TRUE(TmpFP64); + APFloat APF64(.5); + auto CFP64 = ConstantFP::get(Context, APF64); + ASSERT_EQ(CFP64, TmpFP64); + ASSERT_NE(TmpFP64, TmpFP); + + // Build half float. + LLT s16 = LLT::scalar(16); + auto MIBFCst16 = B.buildFConstant(s16, .5); + const ConstantFP *TmpFP16{}; + match = mi_match(MIBFCst16->getOperand(0).getReg(), MRI, m_GFCst(TmpFP16)); + ASSERT_TRUE(match); + ASSERT_TRUE(TmpFP16); + bool Ignored; + APFloat APF16(.5); + APF16.convert(APFloat::IEEEhalf(), APFloat::rmNearestTiesToEven, &Ignored); + auto CFP16 = ConstantFP::get(Context, APF16); + ASSERT_EQ(TmpFP16, CFP16); + ASSERT_NE(TmpFP16, TmpFP); +} + +TEST(PatternMatchInstr, MatchExtendsTrunc) { + LLVMContext Context; + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + if (!TM) + return; + auto ModuleMMIPair = createDummyModule(Context, *TM, ""); + MachineFunction *MF = + getMFFromMMI(ModuleMMIPair.first.get(), ModuleMMIPair.second.get()); + SmallVector<unsigned, 4> Copies; + collectCopies(Copies, MF); + MachineBasicBlock *EntryMBB = &*MF->begin(); + MachineIRBuilder B(*MF); + MachineRegisterInfo &MRI = MF->getRegInfo(); + B.setInsertPt(*EntryMBB, EntryMBB->end()); + LLT s64 = LLT::scalar(64); + LLT s32 = LLT::scalar(32); + + auto MIBTrunc = B.buildTrunc(s32, Copies[0]); + auto MIBAExt = B.buildAnyExt(s64, MIBTrunc); + auto MIBZExt = B.buildZExt(s64, MIBTrunc); + auto MIBSExt = B.buildSExt(s64, MIBTrunc); + unsigned Src0; + bool match = + mi_match(MIBTrunc->getOperand(0).getReg(), MRI, m_GTrunc(m_Reg(Src0))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + match = + mi_match(MIBAExt->getOperand(0).getReg(), MRI, m_GAnyExt(m_Reg(Src0))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, MIBTrunc->getOperand(0).getReg()); + + match = mi_match(MIBSExt->getOperand(0).getReg(), MRI, m_GSExt(m_Reg(Src0))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, MIBTrunc->getOperand(0).getReg()); + + match = mi_match(MIBZExt->getOperand(0).getReg(), MRI, m_GZExt(m_Reg(Src0))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, MIBTrunc->getOperand(0).getReg()); + + // Match ext(trunc src) + match = mi_match(MIBAExt->getOperand(0).getReg(), MRI, + m_GAnyExt(m_GTrunc(m_Reg(Src0)))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + + match = mi_match(MIBSExt->getOperand(0).getReg(), MRI, + m_GSExt(m_GTrunc(m_Reg(Src0)))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + + match = mi_match(MIBZExt->getOperand(0).getReg(), MRI, + m_GZExt(m_GTrunc(m_Reg(Src0)))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); +} + +TEST(PatternMatchInstr, MatchSpecificType) { + LLVMContext Context; + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + if (!TM) + return; + auto ModuleMMIPair = createDummyModule(Context, *TM, ""); + MachineFunction *MF = + getMFFromMMI(ModuleMMIPair.first.get(), ModuleMMIPair.second.get()); + SmallVector<unsigned, 4> Copies; + collectCopies(Copies, MF); + MachineBasicBlock *EntryMBB = &*MF->begin(); + MachineIRBuilder B(*MF); + MachineRegisterInfo &MRI = MF->getRegInfo(); + B.setInsertPt(*EntryMBB, EntryMBB->end()); + + // Try to match a 64bit add. + LLT s64 = LLT::scalar(64); + LLT s32 = LLT::scalar(32); + auto MIBAdd = B.buildAdd(s64, Copies[0], Copies[1]); + ASSERT_FALSE(mi_match(MIBAdd->getOperand(0).getReg(), MRI, + m_GAdd(m_SpecificType(s32), m_Reg()))); + ASSERT_TRUE(mi_match(MIBAdd->getOperand(0).getReg(), MRI, + m_GAdd(m_SpecificType(s64), m_Reg()))); + + // Try to match the destination type of a bitcast. + LLT v2s32 = LLT::vector(2, 32); + auto MIBCast = B.buildCast(v2s32, Copies[0]); + ASSERT_TRUE( + mi_match(MIBCast->getOperand(0).getReg(), MRI, m_GBitcast(m_Reg()))); + ASSERT_TRUE( + mi_match(MIBCast->getOperand(0).getReg(), MRI, m_SpecificType(v2s32))); + ASSERT_TRUE( + mi_match(MIBCast->getOperand(1).getReg(), MRI, m_SpecificType(s64))); + + // Build a PTRToInt and INTTOPTR and match and test them. + LLT PtrTy = LLT::pointer(0, 64); + auto MIBIntToPtr = B.buildCast(PtrTy, Copies[0]); + auto MIBPtrToInt = B.buildCast(s64, MIBIntToPtr); + unsigned Src0; + + // match the ptrtoint(inttoptr reg) + bool match = mi_match(MIBPtrToInt->getOperand(0).getReg(), MRI, + m_GPtrToInt(m_GIntToPtr(m_Reg(Src0)))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); +} + +TEST(PatternMatchInstr, MatchCombinators) { + LLVMContext Context; + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + if (!TM) + return; + auto ModuleMMIPair = createDummyModule(Context, *TM, ""); + MachineFunction *MF = + getMFFromMMI(ModuleMMIPair.first.get(), ModuleMMIPair.second.get()); + SmallVector<unsigned, 4> Copies; + collectCopies(Copies, MF); + MachineBasicBlock *EntryMBB = &*MF->begin(); + MachineIRBuilder B(*MF); + MachineRegisterInfo &MRI = MF->getRegInfo(); + B.setInsertPt(*EntryMBB, EntryMBB->end()); + LLT s64 = LLT::scalar(64); + LLT s32 = LLT::scalar(32); + auto MIBAdd = B.buildAdd(s64, Copies[0], Copies[1]); + unsigned Src0, Src1; + bool match = + mi_match(MIBAdd->getOperand(0).getReg(), MRI, + m_all_of(m_SpecificType(s64), m_GAdd(m_Reg(Src0), m_Reg(Src1)))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + ASSERT_EQ(Src1, Copies[1]); + // Check for s32 (which should fail). + match = + mi_match(MIBAdd->getOperand(0).getReg(), MRI, + m_all_of(m_SpecificType(s32), m_GAdd(m_Reg(Src0), m_Reg(Src1)))); + ASSERT_FALSE(match); + match = + mi_match(MIBAdd->getOperand(0).getReg(), MRI, + m_any_of(m_SpecificType(s32), m_GAdd(m_Reg(Src0), m_Reg(Src1)))); + ASSERT_TRUE(match); + ASSERT_EQ(Src0, Copies[0]); + ASSERT_EQ(Src1, Copies[1]); + + // Match a case where none of the predicates hold true. + match = mi_match( + MIBAdd->getOperand(0).getReg(), MRI, + m_any_of(m_SpecificType(LLT::scalar(16)), m_GSub(m_Reg(), m_Reg()))); + ASSERT_FALSE(match); +} +} // namespace + +int main(int argc, char **argv) { + ::testing::InitGoogleTest(&argc, argv); + initLLVM(); + return RUN_ALL_TESTS(); +} diff --git a/unittests/CodeGen/MachineInstrTest.cpp b/unittests/CodeGen/MachineInstrTest.cpp index aca640ebcf35..6883d3ed63e9 100644 --- a/unittests/CodeGen/MachineInstrTest.cpp +++ b/unittests/CodeGen/MachineInstrTest.cpp @@ -14,6 +14,8 @@ #include "llvm/CodeGen/TargetInstrInfo.h" #include "llvm/CodeGen/TargetLowering.h" #include "llvm/CodeGen/TargetSubtargetInfo.h" +#include "llvm/IR/DebugInfoMetadata.h" +#include "llvm/IR/ModuleSlotTracker.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" #include "llvm/Target/TargetMachine.h" @@ -244,4 +246,31 @@ TEST(MachineInstrExpressionTraitTest, IsEqualAgreesWithGetHashValue) { checkHashAndIsEqualMatch(VD2PU, VD2PD); } + +TEST(MachineInstrPrintingTest, DebugLocPrinting) { + auto MF = createMachineFunction(); + + MCOperandInfo OpInfo{0, 0, MCOI::OPERAND_REGISTER, 0}; + MCInstrDesc MCID = {0, 1, 1, 0, 0, 0, + 0, nullptr, nullptr, &OpInfo, 0, nullptr}; + + LLVMContext Ctx; + DIFile *DIF = DIFile::getDistinct(Ctx, "filename", ""); + DISubprogram *DIS = DISubprogram::getDistinct( + Ctx, nullptr, "", "", DIF, 0, nullptr, false, false, 0, nullptr, 0, 0, 0, + DINode::FlagZero, false, nullptr); + DILocation *DIL = DILocation::get(Ctx, 1, 5, DIS); + DebugLoc DL(DIL); + MachineInstr *MI = MF->CreateMachineInstr(MCID, DL); + MI->addOperand(*MF, MachineOperand::CreateReg(0, /*isDef*/ true)); + + std::string str; + raw_string_ostream OS(str); + MI->print(OS); + ASSERT_TRUE( + StringRef(OS.str()).startswith("$noreg = UNKNOWN debug-location ")); + ASSERT_TRUE( + StringRef(OS.str()).endswith("filename:1:5")); +} + } // end namespace diff --git a/unittests/CodeGen/MachineOperandTest.cpp b/unittests/CodeGen/MachineOperandTest.cpp index 78a20b836486..53ec5aed8189 100644 --- a/unittests/CodeGen/MachineOperandTest.cpp +++ b/unittests/CodeGen/MachineOperandTest.cpp @@ -80,7 +80,7 @@ TEST(MachineOperandTest, PrintSubReg) { std::string str; raw_string_ostream OS(str); MO.print(OS, /*TRI=*/nullptr, /*IntrinsicInfo=*/nullptr); - ASSERT_TRUE(OS.str() == "%physreg1.subreg5"); + ASSERT_TRUE(OS.str() == "$physreg1.subreg5"); } TEST(MachineOperandTest, PrintCImm) { @@ -118,8 +118,7 @@ TEST(MachineOperandTest, PrintSubRegIndex) { // TRI and IntrinsicInfo we can print the operand as a subreg index. std::string str; raw_string_ostream OS(str); - ModuleSlotTracker DummyMST(nullptr); - MachineOperand::printSubregIdx(OS, MO.getImm(), nullptr); + MachineOperand::printSubRegIdx(OS, MO.getImm(), nullptr); ASSERT_TRUE(OS.str() == "%subreg.3"); } @@ -215,7 +214,7 @@ TEST(MachineOperandTest, PrintExternalSymbol) { { raw_string_ostream OS(str); MO.print(OS, /*TRI=*/nullptr, /*IntrinsicInfo=*/nullptr); - ASSERT_TRUE(OS.str() == "$foo"); + ASSERT_TRUE(OS.str() == "&foo"); } str.clear(); @@ -225,7 +224,7 @@ TEST(MachineOperandTest, PrintExternalSymbol) { { raw_string_ostream OS(str); MO.print(OS, /*TRI=*/nullptr, /*IntrinsicInfo=*/nullptr); - ASSERT_TRUE(OS.str() == "$foo + 12"); + ASSERT_TRUE(OS.str() == "&foo + 12"); } str.clear(); @@ -235,7 +234,7 @@ TEST(MachineOperandTest, PrintExternalSymbol) { { raw_string_ostream OS(str); MO.print(OS, /*TRI=*/nullptr, /*IntrinsicInfo=*/nullptr); - ASSERT_TRUE(OS.str() == "$foo - 12"); + ASSERT_TRUE(OS.str() == "&foo - 12"); } } @@ -296,7 +295,7 @@ TEST(MachineOperandTest, PrintMetadata) { LLVMContext Ctx; Module M("MachineOperandMDNodeTest", Ctx); NamedMDNode *MD = M.getOrInsertNamedMetadata("namedmd"); - ModuleSlotTracker DummyMST(&M); + ModuleSlotTracker MST(&M); Metadata *MDS = MDString::get(Ctx, "foo"); MDNode *Node = MDNode::get(Ctx, MDS); MD->addOperand(Node); @@ -312,7 +311,8 @@ TEST(MachineOperandTest, PrintMetadata) { std::string str; // Print a MachineOperand containing a metadata node. raw_string_ostream OS(str); - MO.print(OS, DummyMST, LLT{}, false, false, 0, /*TRI=*/nullptr, + MO.print(OS, MST, LLT{}, /*PrintDef=*/false, /*IsStandalone=*/false, + /*ShouldPrintRegisterTies=*/false, 0, /*TRI=*/nullptr, /*IntrinsicInfo=*/nullptr); ASSERT_TRUE(OS.str() == "!0"); } diff --git a/unittests/CodeGen/ScalableVectorMVTsTest.cpp b/unittests/CodeGen/ScalableVectorMVTsTest.cpp index 0071823f2cc9..20b3fb1b9f48 100644 --- a/unittests/CodeGen/ScalableVectorMVTsTest.cpp +++ b/unittests/CodeGen/ScalableVectorMVTsTest.cpp @@ -7,9 +7,9 @@ // //===----------------------------------------------------------------------===// -#include "llvm/CodeGen/MachineValueType.h" #include "llvm/CodeGen/ValueTypes.h" #include "llvm/IR/LLVMContext.h" +#include "llvm/Support/MachineValueType.h" #include "gtest/gtest.h" using namespace llvm; diff --git a/unittests/DebugInfo/CodeView/CMakeLists.txt b/unittests/DebugInfo/CodeView/CMakeLists.txt index d06ccfaba72a..70a7b8af1447 100644 --- a/unittests/DebugInfo/CodeView/CMakeLists.txt +++ b/unittests/DebugInfo/CodeView/CMakeLists.txt @@ -2,14 +2,10 @@ set(LLVM_LINK_COMPONENTS DebugInfoCodeView ) -set(DebugInfoCodeViewSources +add_llvm_unittest(DebugInfoCodeViewTests RandomAccessVisitorTest.cpp TypeHashingTest.cpp TypeIndexDiscoveryTest.cpp ) -add_llvm_unittest(DebugInfoCodeViewTests - ${DebugInfoCodeViewSources} - ) - target_link_libraries(DebugInfoCodeViewTests PRIVATE LLVMTestingSupport) diff --git a/unittests/DebugInfo/CodeView/TypeIndexDiscoveryTest.cpp b/unittests/DebugInfo/CodeView/TypeIndexDiscoveryTest.cpp index c51b9e723f04..60ca56b0d143 100644 --- a/unittests/DebugInfo/CodeView/TypeIndexDiscoveryTest.cpp +++ b/unittests/DebugInfo/CodeView/TypeIndexDiscoveryTest.cpp @@ -555,7 +555,7 @@ TEST_F(TypeIndexIteratorTest, DataSym) { TEST_F(TypeIndexIteratorTest, RegisterSym) { RegisterSym Reg(SymbolRecordKind::RegisterSym); Reg.Index = TypeIndex::UInt32(); - Reg.Register = RegisterId::EAX; + Reg.Register = RegisterId::CVRegEAX; Reg.Name = "Target"; writeSymbolRecords(Reg); checkTypeReferences(0, Reg.Index); @@ -580,3 +580,24 @@ TEST_F(TypeIndexIteratorTest, CallerSym) { checkTypeReferences(2, TypeIndex(7), TypeIndex(8), TypeIndex(9)); } +TEST_F(TypeIndexIteratorTest, Precomp) { + PrecompRecord P(TypeRecordKind::Precomp); + P.StartTypeIndex = TypeIndex::FirstNonSimpleIndex; + P.TypesCount = 100; + P.Signature = 0x12345678; + P.PrecompFilePath = "C:/precomp.obj"; + + EndPrecompRecord EP(TypeRecordKind::EndPrecomp); + EP.Signature = P.Signature; + + writeTypeRecords(P, EP); + checkTypeReferences(0); +} + +// This is a test for getEncodedIntegerLength() +TEST_F(TypeIndexIteratorTest, VariableSizeIntegers) { + BaseClassRecord BaseClass1(MemberAccess::Public, TypeIndex(47), (uint64_t)-1); + BaseClassRecord BaseClass2(MemberAccess::Public, TypeIndex(48), 1); + writeFieldList(BaseClass1, BaseClass2); + checkTypeReferences(0, TypeIndex(47), TypeIndex(48)); +}
\ No newline at end of file diff --git a/unittests/DebugInfo/DWARF/CMakeLists.txt b/unittests/DebugInfo/DWARF/CMakeLists.txt index f490097a21a7..d97e573ea38f 100644 --- a/unittests/DebugInfo/DWARF/CMakeLists.txt +++ b/unittests/DebugInfo/DWARF/CMakeLists.txt @@ -8,14 +8,12 @@ set(LLVM_LINK_COMPONENTS Support ) -set(DebugInfoSources +add_llvm_unittest(DebugInfoDWARFTests DwarfGenerator.cpp + DwarfUtils.cpp DWARFDebugInfoTest.cpp + DWARFDebugLineTest.cpp DWARFFormValueTest.cpp ) -add_llvm_unittest(DebugInfoDWARFTests - ${DebugInfoSources} - ) - target_link_libraries(DebugInfoDWARFTests PRIVATE LLVMTestingSupport) diff --git a/unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp b/unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp index cb7bf82d86f6..442dea3c52f7 100644 --- a/unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp +++ b/unittests/DebugInfo/DWARF/DWARFDebugInfoTest.cpp @@ -1,4 +1,4 @@ -//===- llvm/unittest/DebugInfo/DWARFFormValueTest.cpp ---------------------===// +//===- llvm/unittest/DebugInfo/DWARFDebugInfoTest.cpp ---------------------===// // // The LLVM Compiler Infrastructure // @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "DwarfGenerator.h" +#include "DwarfUtils.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/Optional.h" #include "llvm/ADT/SmallString.h" @@ -15,7 +16,6 @@ #include "llvm/ADT/Triple.h" #include "llvm/BinaryFormat/Dwarf.h" #include "llvm/CodeGen/AsmPrinter.h" -#include "llvm/Config/llvm-config.h" #include "llvm/DebugInfo/DWARF/DWARFCompileUnit.h" #include "llvm/DebugInfo/DWARF/DWARFContext.h" #include "llvm/DebugInfo/DWARF/DWARFDie.h" @@ -36,36 +36,10 @@ using namespace llvm; using namespace dwarf; +using namespace utils; namespace { -void initLLVMIfNeeded() { - static bool gInitialized = false; - if (!gInitialized) { - gInitialized = true; - InitializeAllTargets(); - InitializeAllTargetMCs(); - InitializeAllAsmPrinters(); - InitializeAllAsmParsers(); - } -} - -Triple getHostTripleForAddrSize(uint8_t AddrSize) { - Triple PT(Triple::normalize(LLVM_HOST_TRIPLE)); - - if (AddrSize == 8 && PT.isArch32Bit()) - return PT.get64BitArchVariant(); - if (AddrSize == 4 && PT.isArch64Bit()) - return PT.get32BitArchVariant(); - return PT; -} - -static bool isConfigurationSupported(Triple &T) { - initLLVMIfNeeded(); - std::string Err; - return TargetRegistry::lookupTarget(T.getTriple(), Err); -} - template <uint16_t Version, class AddrType, class RefAddrType> void TestAllForms() { Triple Triple = getHostTripleForAddrSize(sizeof(AddrType)); @@ -93,12 +67,21 @@ void TestAllForms() { const uint32_t Dwarf32Values[] = {1, 2, 3, 4, 5, 6, 7, 8}; const char *StringValue = "Hello"; const char *StrpValue = "World"; + const char *StrxValue = "Indexed"; + const char *Strx1Value = "Indexed1"; + const char *Strx2Value = "Indexed2"; + const char *Strx3Value = "Indexed3"; + const char *Strx4Value = "Indexed4"; auto ExpectedDG = dwarfgen::Generator::create(Triple, Version); ASSERT_THAT_EXPECTED(ExpectedDG, Succeeded()); dwarfgen::Generator *DG = ExpectedDG.get().get(); dwarfgen::CompileUnit &CU = DG->addCompileUnit(); dwarfgen::DIE CUDie = CU.getUnitDIE(); + + if (Version >= 5) + CUDie.addStrOffsetsBaseAttribute(); + uint16_t Attr = DW_AT_lo_user; //---------------------------------------------------------------------- @@ -148,6 +131,19 @@ void TestAllForms() { const auto Attr_DW_FORM_string = static_cast<dwarf::Attribute>(Attr++); CUDie.addAttribute(Attr_DW_FORM_string, DW_FORM_string, StringValue); + const auto Attr_DW_FORM_strx = static_cast<dwarf::Attribute>(Attr++); + const auto Attr_DW_FORM_strx1 = static_cast<dwarf::Attribute>(Attr++); + const auto Attr_DW_FORM_strx2 = static_cast<dwarf::Attribute>(Attr++); + const auto Attr_DW_FORM_strx3 = static_cast<dwarf::Attribute>(Attr++); + const auto Attr_DW_FORM_strx4 = static_cast<dwarf::Attribute>(Attr++); + if (Version >= 5) { + CUDie.addAttribute(Attr_DW_FORM_strx, DW_FORM_strx, StrxValue); + CUDie.addAttribute(Attr_DW_FORM_strx1, DW_FORM_strx1, Strx1Value); + CUDie.addAttribute(Attr_DW_FORM_strx2, DW_FORM_strx2, Strx2Value); + CUDie.addAttribute(Attr_DW_FORM_strx3, DW_FORM_strx3, Strx3Value); + CUDie.addAttribute(Attr_DW_FORM_strx4, DW_FORM_strx4, Strx4Value); + } + const auto Attr_DW_FORM_strp = static_cast<dwarf::Attribute>(Attr++); CUDie.addAttribute(Attr_DW_FORM_strp, DW_FORM_strp, StrpValue); @@ -307,11 +303,33 @@ void TestAllForms() { //---------------------------------------------------------------------- auto ExtractedStringValue = toString(DieDG.find(Attr_DW_FORM_string)); EXPECT_TRUE((bool)ExtractedStringValue); - EXPECT_TRUE(strcmp(StringValue, *ExtractedStringValue) == 0); + EXPECT_STREQ(StringValue, *ExtractedStringValue); + + if (Version >= 5) { + auto ExtractedStrxValue = toString(DieDG.find(Attr_DW_FORM_strx)); + EXPECT_TRUE((bool)ExtractedStrxValue); + EXPECT_STREQ(StrxValue, *ExtractedStrxValue); + + auto ExtractedStrx1Value = toString(DieDG.find(Attr_DW_FORM_strx1)); + EXPECT_TRUE((bool)ExtractedStrx1Value); + EXPECT_STREQ(Strx1Value, *ExtractedStrx1Value); + + auto ExtractedStrx2Value = toString(DieDG.find(Attr_DW_FORM_strx2)); + EXPECT_TRUE((bool)ExtractedStrx2Value); + EXPECT_STREQ(Strx2Value, *ExtractedStrx2Value); + + auto ExtractedStrx3Value = toString(DieDG.find(Attr_DW_FORM_strx3)); + EXPECT_TRUE((bool)ExtractedStrx3Value); + EXPECT_STREQ(Strx3Value, *ExtractedStrx3Value); + + auto ExtractedStrx4Value = toString(DieDG.find(Attr_DW_FORM_strx4)); + EXPECT_TRUE((bool)ExtractedStrx4Value); + EXPECT_STREQ(Strx4Value, *ExtractedStrx4Value); + } auto ExtractedStrpValue = toString(DieDG.find(Attr_DW_FORM_strp)); EXPECT_TRUE((bool)ExtractedStrpValue); - EXPECT_TRUE(strcmp(StrpValue, *ExtractedStrpValue) == 0); + EXPECT_STREQ(StrpValue, *ExtractedStrpValue); //---------------------------------------------------------------------- // Test reference forms @@ -516,6 +534,11 @@ template <uint16_t Version, class AddrType> void TestChildren() { EXPECT_TRUE(!NullDieDG.getSibling().isValid()); EXPECT_TRUE(!NullDieDG.getFirstChild().isValid()); } + + // Verify the previous sibling of our subprogram is our integer base type. + IntDieDG = NullDieDG.getPreviousSibling(); + EXPECT_TRUE(IntDieDG.isValid()); + EXPECT_EQ(IntDieDG.getTag(), DW_TAG_base_type); } TEST(DWARFDebugInfo, TestDWARF32Version2Addr4Children) { @@ -1098,6 +1121,27 @@ TEST(DWARFDebugInfo, TestRelations) { // Make sure the parent of all the children of the B are the B. EXPECT_EQ(C1.getParent(), C); EXPECT_EQ(C2.getParent(), C); + + // Make sure bidirectional iterator works as expected. + auto Begin = A.begin(); + auto End = A.end(); + auto It = A.begin(); + + EXPECT_EQ(It, Begin); + EXPECT_EQ(*It, B); + ++It; + EXPECT_EQ(*It, C); + ++It; + EXPECT_EQ(*It, D); + ++It; + EXPECT_EQ(It, End); + --It; + EXPECT_EQ(*It, D); + --It; + EXPECT_EQ(*It, C); + --It; + EXPECT_EQ(*It, B); + EXPECT_EQ(It, Begin); } TEST(DWARFDebugInfo, TestDWARFDie) { @@ -1191,7 +1235,7 @@ TEST(DWARFDebugInfo, TestEmptyChildren) { " Attributes:\n" "debug_info:\n" " - Length:\n" - " TotalLength: 9\n" + " TotalLength: 0\n" " Version: 4\n" " AbbrOffset: 0\n" " AddrSize: 8\n" @@ -1201,7 +1245,7 @@ TEST(DWARFDebugInfo, TestEmptyChildren) { " - AbbrCode: 0x00000000\n" " Values:\n"; - auto ErrOrSections = DWARFYAML::EmitDebugSections(StringRef(yamldata)); + auto ErrOrSections = DWARFYAML::EmitDebugSections(StringRef(yamldata), true); ASSERT_TRUE((bool)ErrOrSections); std::unique_ptr<DWARFContext> DwarfContext = DWARFContext::create(*ErrOrSections, 8); diff --git a/unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp b/unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp new file mode 100644 index 000000000000..99ed118e539b --- /dev/null +++ b/unittests/DebugInfo/DWARF/DWARFDebugLineTest.cpp @@ -0,0 +1,667 @@ +//===- DWARFDebugLineTest.cpp ---------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DwarfGenerator.h" +#include "DwarfUtils.h" +#include "llvm/DebugInfo/DWARF/DWARFContext.h" +#include "llvm/DebugInfo/DWARF/DWARFDebugLine.h" +#include "llvm/Object/ObjectFile.h" +#include "llvm/Testing/Support/Error.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace dwarf; +using namespace dwarfgen; +using namespace object; +using namespace utils; +using namespace testing; + +namespace { +struct CommonFixture { + CommonFixture() + : LineData("", true, 0), Recoverable(Error::success()), + RecordRecoverable(std::bind(&CommonFixture::recordRecoverable, this, + std::placeholders::_1)), + Unrecoverable(Error::success()), + RecordUnrecoverable(std::bind(&CommonFixture::recordUnrecoverable, this, + std::placeholders::_1)){}; + + ~CommonFixture() { + EXPECT_FALSE(Recoverable); + EXPECT_FALSE(Unrecoverable); + } + + bool setupGenerator(uint16_t Version = 4) { + Triple T = getHostTripleForAddrSize(8); + if (!isConfigurationSupported(T)) + return false; + auto ExpectedGenerator = Generator::create(T, Version); + if (ExpectedGenerator) + Gen.reset(ExpectedGenerator->release()); + return true; + } + + void generate() { + Context = createContext(); + assert(Context != nullptr && "test state is not valid"); + const DWARFObject &Obj = Context->getDWARFObj(); + LineData = DWARFDataExtractor(Obj, Obj.getLineSection(), + sys::IsLittleEndianHost, 8); + } + + std::unique_ptr<DWARFContext> createContext() { + if (!Gen) + return nullptr; + StringRef FileBytes = Gen->generate(); + MemoryBufferRef FileBuffer(FileBytes, "dwarf"); + auto Obj = object::ObjectFile::createObjectFile(FileBuffer); + if (Obj) + return DWARFContext::create(**Obj); + return nullptr; + } + + DWARFDebugLine::SectionParser setupParser() { + LineTable < = Gen->addLineTable(DWARF32); + LT.addExtendedOpcode(9, DW_LNE_set_address, {{0xadd4e55, LineTable::Quad}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xaa); + LT.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + + LineTable <2 = Gen->addLineTable(DWARF64); + LT2.addExtendedOpcode(9, DW_LNE_set_address, + {{0x11223344, LineTable::Quad}}); + LT2.addStandardOpcode(DW_LNS_copy, {}); + LT2.addByte(0xbb); + LT2.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + + generate(); + + return DWARFDebugLine::SectionParser(LineData, *Context, CUs, TUs); + } + + void recordRecoverable(Error Err) { + Recoverable = joinErrors(std::move(Recoverable), std::move(Err)); + } + void recordUnrecoverable(Error Err) { + Unrecoverable = joinErrors(std::move(Unrecoverable), std::move(Err)); + } + + void checkError(ArrayRef<StringRef> ExpectedMsgs, Error Err) { + ASSERT_TRUE(Err.operator bool()); + size_t WhichMsg = 0; + Error Remaining = + handleErrors(std::move(Err), [&](const ErrorInfoBase &Actual) { + ASSERT_LT(WhichMsg, ExpectedMsgs.size()); + // Use .str(), because googletest doesn't visualise a StringRef + // properly. + EXPECT_EQ(Actual.message(), ExpectedMsgs[WhichMsg++].str()); + }); + EXPECT_EQ(WhichMsg, ExpectedMsgs.size()); + EXPECT_FALSE(Remaining); + } + + void checkError(StringRef ExpectedMsg, Error Err) { + checkError(ArrayRef<StringRef>{ExpectedMsg}, std::move(Err)); + } + + void checkGetOrParseLineTableEmitsError(StringRef ExpectedMsg, + uint64_t Offset = 0) { + auto ExpectedLineTable = Line.getOrParseLineTable( + LineData, Offset, *Context, nullptr, RecordRecoverable); + EXPECT_FALSE(ExpectedLineTable); + EXPECT_FALSE(Recoverable); + + checkError(ExpectedMsg, ExpectedLineTable.takeError()); + } + + std::unique_ptr<Generator> Gen; + std::unique_ptr<DWARFContext> Context; + DWARFDataExtractor LineData; + DWARFDebugLine Line; + Error Recoverable; + std::function<void(Error)> RecordRecoverable; + Error Unrecoverable; + std::function<void(Error)> RecordUnrecoverable; + + SmallVector<std::unique_ptr<DWARFCompileUnit>, 2> CUs; + std::deque<DWARFUnitSection<DWARFTypeUnit>> TUs; +}; + +// Fixtures must derive from "Test", but parameterised fixtures from +// "TestWithParam". It does not seem possible to inherit from both, so we share +// the common state in a separate class, inherited by the two fixture classes. +struct DebugLineBasicFixture : public Test, public CommonFixture {}; + +struct DebugLineParameterisedFixture + : public TestWithParam<std::pair<uint16_t, DwarfFormat>>, + public CommonFixture { + void SetUp() { std::tie(Version, Format) = GetParam(); } + + uint16_t Version; + DwarfFormat Format; +}; + +void checkDefaultPrologue(uint16_t Version, DwarfFormat Format, + DWARFDebugLine::Prologue Prologue, + uint64_t BodyLength) { + // Check version specific fields and values. + uint64_t UnitLength; + uint64_t PrologueLength; + switch (Version) { + case 4: + PrologueLength = 36; + UnitLength = PrologueLength + 2; + EXPECT_EQ(Prologue.MaxOpsPerInst, 1u); + break; + case 2: + case 3: + PrologueLength = 35; + UnitLength = PrologueLength + 2; + break; + case 5: + PrologueLength = 39; + UnitLength = PrologueLength + 4; + EXPECT_EQ(Prologue.getAddressSize(), 8u); + EXPECT_EQ(Prologue.SegSelectorSize, 0u); + break; + default: + llvm_unreachable("unsupported DWARF version"); + } + UnitLength += BodyLength + (Format == DWARF32 ? 4 : 8); + + EXPECT_EQ(Prologue.TotalLength, UnitLength); + EXPECT_EQ(Prologue.PrologueLength, PrologueLength); + EXPECT_EQ(Prologue.MinInstLength, 1u); + EXPECT_EQ(Prologue.DefaultIsStmt, 1u); + EXPECT_EQ(Prologue.LineBase, -5); + EXPECT_EQ(Prologue.LineRange, 14u); + EXPECT_EQ(Prologue.OpcodeBase, 13u); + std::vector<uint8_t> ExpectedLengths = {0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1}; + EXPECT_EQ(Prologue.StandardOpcodeLengths, ExpectedLengths); + ASSERT_EQ(Prologue.IncludeDirectories.size(), 1u); + ASSERT_EQ(Prologue.IncludeDirectories[0].getForm(), DW_FORM_string); + EXPECT_STREQ(*Prologue.IncludeDirectories[0].getAsCString(), "a dir"); + ASSERT_EQ(Prologue.FileNames.size(), 1u); + ASSERT_EQ(Prologue.FileNames[0].Name.getForm(), DW_FORM_string); + EXPECT_STREQ(*Prologue.FileNames[0].Name.getAsCString(), "a file"); +} + +TEST_F(DebugLineBasicFixture, GetOrParseLineTableAtInvalidOffset) { + if (!setupGenerator()) + return; + generate(); + + checkGetOrParseLineTableEmitsError( + "offset 0x00000000 is not a valid debug line section offset", 0); + // Repeat to show that an error is reported each time. + checkGetOrParseLineTableEmitsError( + "offset 0x00000000 is not a valid debug line section offset", 0); + // Show that an error is reported for later offsets too. + checkGetOrParseLineTableEmitsError( + "offset 0x00000001 is not a valid debug line section offset", 1); +} + +TEST_F(DebugLineBasicFixture, GetOrParseLineTableAtInvalidOffsetAfterData) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + LT.setCustomPrologue({{0, LineTable::Byte}}); + + generate(); + + checkGetOrParseLineTableEmitsError( + "offset 0x00000001 is not a valid debug line section offset", 1); +} + +TEST_P(DebugLineParameterisedFixture, GetOrParseLineTableValidTable) { + if (!setupGenerator(Version)) + return; + + SCOPED_TRACE("Checking Version " + std::to_string(Version) + ", Format " + + (Format == DWARF64 ? "DWARF64" : "DWARF32")); + + LineTable < = Gen->addLineTable(Format); + LT.addExtendedOpcode(9, DW_LNE_set_address, {{0xadd4e55, LineTable::Quad}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xaa); + LT.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + + LineTable <2 = Gen->addLineTable(Format); + LT2.addExtendedOpcode(9, DW_LNE_set_address, {{0x11223344, LineTable::Quad}}); + LT2.addStandardOpcode(DW_LNS_copy, {}); + LT2.addByte(0xbb); + LT2.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + LT2.addExtendedOpcode(9, DW_LNE_set_address, {{0x55667788, LineTable::Quad}}); + LT2.addStandardOpcode(DW_LNS_copy, {}); + LT2.addByte(0xcc); + LT2.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + + generate(); + + auto ExpectedLineTable = Line.getOrParseLineTable(LineData, 0, *Context, + nullptr, RecordRecoverable); + ASSERT_TRUE(ExpectedLineTable.operator bool()); + EXPECT_FALSE(Recoverable); + const DWARFDebugLine::LineTable *Expected = *ExpectedLineTable; + checkDefaultPrologue(Version, Format, Expected->Prologue, 16); + EXPECT_EQ(Expected->Sequences.size(), 1u); + + uint64_t SecondOffset = + Expected->Prologue.sizeofTotalLength() + Expected->Prologue.TotalLength; + Recoverable = Error::success(); + auto ExpectedLineTable2 = Line.getOrParseLineTable( + LineData, SecondOffset, *Context, nullptr, RecordRecoverable); + ASSERT_TRUE(ExpectedLineTable2.operator bool()); + EXPECT_FALSE(Recoverable); + const DWARFDebugLine::LineTable *Expected2 = *ExpectedLineTable2; + checkDefaultPrologue(Version, Format, Expected2->Prologue, 32); + EXPECT_EQ(Expected2->Sequences.size(), 2u); + + EXPECT_NE(Expected, Expected2); + + // Check that if the same offset is requested, the exact same pointer is + // returned. + Recoverable = Error::success(); + auto ExpectedLineTable3 = Line.getOrParseLineTable( + LineData, 0, *Context, nullptr, RecordRecoverable); + ASSERT_TRUE(ExpectedLineTable3.operator bool()); + EXPECT_FALSE(Recoverable); + EXPECT_EQ(Expected, *ExpectedLineTable3); + + Recoverable = Error::success(); + auto ExpectedLineTable4 = Line.getOrParseLineTable( + LineData, SecondOffset, *Context, nullptr, RecordRecoverable); + ASSERT_TRUE(ExpectedLineTable4.operator bool()); + EXPECT_FALSE(Recoverable); + EXPECT_EQ(Expected2, *ExpectedLineTable4); + + // TODO: Add tests that show that the body of the programs have been read + // correctly. +} + +TEST_F(DebugLineBasicFixture, ErrorForReservedLength) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + LT.setCustomPrologue({{0xffffff00, LineTable::Long}}); + + generate(); + + checkGetOrParseLineTableEmitsError( + "parsing line table prologue at offset 0x00000000 unsupported reserved " + "unit length found of value 0xffffff00"); +} + +TEST_F(DebugLineBasicFixture, ErrorForLowVersion) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + LT.setCustomPrologue( + {{LineTable::Half, LineTable::Long}, {1, LineTable::Half}}); + + generate(); + + checkGetOrParseLineTableEmitsError("parsing line table prologue at offset " + "0x00000000 found unsupported version " + "0x01"); +} + +TEST_F(DebugLineBasicFixture, ErrorForInvalidV5IncludeDirTable) { + if (!setupGenerator(5)) + return; + + LineTable < = Gen->addLineTable(); + LT.setCustomPrologue({ + {19, LineTable::Long}, // unit length + {5, LineTable::Half}, // version + {8, LineTable::Byte}, // addr size + {0, LineTable::Byte}, // segment selector size + {11, LineTable::Long}, // prologue length + {1, LineTable::Byte}, // min instruction length + {1, LineTable::Byte}, // max ops per instruction + {1, LineTable::Byte}, // default is_stmt + {0, LineTable::Byte}, // line base + {14, LineTable::Byte}, // line range + {2, LineTable::Byte}, // opcode base (small to reduce the amount of + // setup required). + {0, LineTable::Byte}, // standard opcode lengths + {0, LineTable::Byte}, // directory entry format count (should not be + // zero). + {0, LineTable::ULEB}, // directories count + {0, LineTable::Byte}, // file name entry format count + {0, LineTable::ULEB} // file name entry count + }); + + generate(); + + checkGetOrParseLineTableEmitsError( + "parsing line table prologue at 0x00000000 found an invalid directory or " + "file table description at 0x00000014"); +} + +TEST_P(DebugLineParameterisedFixture, ErrorForTooLargePrologueLength) { + if (!setupGenerator(Version)) + return; + + SCOPED_TRACE("Checking Version " + std::to_string(Version) + ", Format " + + (Format == DWARF64 ? "DWARF64" : "DWARF32")); + + LineTable < = Gen->addLineTable(Format); + DWARFDebugLine::Prologue Prologue = LT.createBasicPrologue(); + ++Prologue.PrologueLength; + LT.setPrologue(Prologue); + + generate(); + + uint64_t ExpectedEnd = + Prologue.TotalLength + 1 + Prologue.sizeofTotalLength(); + checkGetOrParseLineTableEmitsError( + (Twine("parsing line table prologue at 0x00000000 should have ended at " + "0x000000") + + Twine::utohexstr(ExpectedEnd) + " but it ended at 0x000000" + + Twine::utohexstr(ExpectedEnd - 1)) + .str()); +} + +TEST_P(DebugLineParameterisedFixture, ErrorForTooShortPrologueLength) { + if (!setupGenerator(Version)) + return; + + SCOPED_TRACE("Checking Version " + std::to_string(Version) + ", Format " + + (Format == DWARF64 ? "DWARF64" : "DWARF32")); + + LineTable < = Gen->addLineTable(Format); + DWARFDebugLine::Prologue Prologue = LT.createBasicPrologue(); + // FIXME: Ideally, we'd test for 1 less than expected, but the code does not + // currently fail if missing only the terminator of a v2-4 file table. + if (Version < 5) + Prologue.PrologueLength -= 2; + else + Prologue.PrologueLength -= 1; + LT.setPrologue(Prologue); + + generate(); + + uint64_t ExpectedEnd = + Prologue.TotalLength - 1 + Prologue.sizeofTotalLength(); + if (Version < 5) + --ExpectedEnd; + checkGetOrParseLineTableEmitsError( + (Twine("parsing line table prologue at 0x00000000 should have ended at " + "0x000000") + + Twine::utohexstr(ExpectedEnd) + " but it ended at 0x000000" + + Twine::utohexstr(ExpectedEnd + 1)) + .str()); +} + +INSTANTIATE_TEST_CASE_P( + LineTableTestParams, DebugLineParameterisedFixture, + Values(std::make_pair( + 2, DWARF32), // Test lower-bound of v2-3 fields and DWARF32. + std::make_pair(3, DWARF32), // Test upper-bound of v2-3 fields. + std::make_pair(4, DWARF64), // Test v4 fields and DWARF64. + std::make_pair(5, DWARF32), std::make_pair(5, DWARF64)), ); + +TEST_F(DebugLineBasicFixture, ErrorForInvalidExtendedOpcodeLength) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + // The Length should be 1 for an end sequence opcode. + LT.addExtendedOpcode(2, DW_LNE_end_sequence, {}); + + generate(); + + checkGetOrParseLineTableEmitsError("unexpected line op length at offset " + "0x00000030 expected 0x02 found 0x01"); +} + +TEST_F(DebugLineBasicFixture, ErrorForMismatchedAddressSize) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + // The line data extractor expects size 8 (Quad) addresses. + LT.addExtendedOpcode(5, DW_LNE_set_address, {{0x11223344, LineTable::Long}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xaa); + LT.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + + generate(); + + checkGetOrParseLineTableEmitsError( + "mismatching address size at offset 0x00000030 expected 0x08 found 0x04"); +} + +TEST_F(DebugLineBasicFixture, CallbackUsedForUnterminatedSequence) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + LT.addExtendedOpcode(9, DW_LNE_set_address, + {{0x1122334455667788, LineTable::Quad}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xaa); + LT.addExtendedOpcode(1, DW_LNE_end_sequence, {}); + LT.addExtendedOpcode(9, DW_LNE_set_address, + {{0x99aabbccddeeff00, LineTable::Quad}}); + LT.addStandardOpcode(DW_LNS_copy, {}); + LT.addByte(0xbb); + LT.addByte(0xcc); + + generate(); + + auto ExpectedLineTable = Line.getOrParseLineTable(LineData, 0, *Context, + nullptr, RecordRecoverable); + checkError("last sequence in debug line table is not terminated!", + std::move(Recoverable)); + ASSERT_TRUE(ExpectedLineTable.operator bool()); + EXPECT_EQ((*ExpectedLineTable)->Rows.size(), 6u); + // The unterminated sequence is not added to the sequence list. + EXPECT_EQ((*ExpectedLineTable)->Sequences.size(), 1u); +} + +TEST_F(DebugLineBasicFixture, ParserParsesCorrectly) { + if (!setupGenerator()) + return; + + DWARFDebugLine::SectionParser Parser = setupParser(); + + EXPECT_EQ(Parser.getOffset(), 0u); + ASSERT_FALSE(Parser.done()); + + DWARFDebugLine::LineTable Parsed = + Parser.parseNext(RecordRecoverable, RecordUnrecoverable); + checkDefaultPrologue(4, DWARF32, Parsed.Prologue, 16); + EXPECT_EQ(Parsed.Sequences.size(), 1u); + EXPECT_EQ(Parser.getOffset(), 62u); + ASSERT_FALSE(Parser.done()); + + DWARFDebugLine::LineTable Parsed2 = + Parser.parseNext(RecordRecoverable, RecordUnrecoverable); + checkDefaultPrologue(4, DWARF64, Parsed2.Prologue, 16); + EXPECT_EQ(Parsed2.Sequences.size(), 1u); + EXPECT_EQ(Parser.getOffset(), 136u); + EXPECT_TRUE(Parser.done()); + + EXPECT_FALSE(Recoverable); + EXPECT_FALSE(Unrecoverable); +} + +TEST_F(DebugLineBasicFixture, ParserSkipsCorrectly) { + if (!setupGenerator()) + return; + + DWARFDebugLine::SectionParser Parser = setupParser(); + + EXPECT_EQ(Parser.getOffset(), 0u); + ASSERT_FALSE(Parser.done()); + + Parser.skip(RecordUnrecoverable); + EXPECT_EQ(Parser.getOffset(), 62u); + ASSERT_FALSE(Parser.done()); + + Parser.skip(RecordUnrecoverable); + EXPECT_EQ(Parser.getOffset(), 136u); + EXPECT_TRUE(Parser.done()); + + EXPECT_FALSE(Unrecoverable); +} + +TEST_F(DebugLineBasicFixture, ParserAlwaysDoneForEmptySection) { + if (!setupGenerator()) + return; + + generate(); + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + + EXPECT_TRUE(Parser.done()); +} + +TEST_F(DebugLineBasicFixture, ParserMovesToEndForBadLengthWhenParsing) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + LT.setCustomPrologue({{0xffffff00, LineTable::Long}}); + Gen->addLineTable(); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.parseNext(RecordRecoverable, RecordUnrecoverable); + + EXPECT_EQ(Parser.getOffset(), 4u); + EXPECT_TRUE(Parser.done()); + EXPECT_FALSE(Recoverable); + + checkError("parsing line table prologue at offset 0x00000000 unsupported " + "reserved unit length found of value 0xffffff00", + std::move(Unrecoverable)); +} + +TEST_F(DebugLineBasicFixture, ParserMovesToEndForBadLengthWhenSkipping) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(); + LT.setCustomPrologue({{0xffffff00, LineTable::Long}}); + Gen->addLineTable(); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.skip(RecordUnrecoverable); + + EXPECT_EQ(Parser.getOffset(), 4u); + EXPECT_TRUE(Parser.done()); + + checkError("parsing line table prologue at offset 0x00000000 unsupported " + "reserved unit length found of value 0xffffff00", + std::move(Unrecoverable)); +} + +TEST_F(DebugLineBasicFixture, ParserReportsFirstErrorInEachTableWhenParsing) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(DWARF32); + LT.setCustomPrologue({{2, LineTable::Long}, {0, LineTable::Half}}); + LineTable <2 = Gen->addLineTable(DWARF32); + LT2.setCustomPrologue({{2, LineTable::Long}, {1, LineTable::Half}}); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.parseNext(RecordRecoverable, RecordUnrecoverable); + ASSERT_FALSE(Parser.done()); + Parser.parseNext(RecordRecoverable, RecordUnrecoverable); + + EXPECT_TRUE(Parser.done()); + EXPECT_FALSE(Recoverable); + + checkError({"parsing line table prologue at offset 0x00000000 found " + "unsupported version 0x00", + "parsing line table prologue at offset 0x00000006 found " + "unsupported version 0x01"}, + std::move(Unrecoverable)); +} + +TEST_F(DebugLineBasicFixture, ParserReportsNonPrologueProblemsWhenParsing) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(DWARF32); + LT.addExtendedOpcode(0x42, DW_LNE_end_sequence, {}); + LineTable <2 = Gen->addLineTable(DWARF32); + LT2.addExtendedOpcode(9, DW_LNE_set_address, + {{0x1234567890abcdef, LineTable::Quad}}); + LT2.addStandardOpcode(DW_LNS_copy, {}); + LT2.addByte(0xbb); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.parseNext(RecordRecoverable, RecordUnrecoverable); + EXPECT_FALSE(Recoverable); + ASSERT_FALSE(Parser.done()); + checkError( + "unexpected line op length at offset 0x00000030 expected 0x42 found 0x01", + std::move(Unrecoverable)); + + // Reset the error state so that it does not confuse the next set of checks. + Unrecoverable = Error::success(); + Parser.parseNext(RecordRecoverable, RecordUnrecoverable); + + EXPECT_TRUE(Parser.done()); + checkError("last sequence in debug line table is not terminated!", + std::move(Recoverable)); + EXPECT_FALSE(Unrecoverable); +} + +TEST_F(DebugLineBasicFixture, + ParserReportsPrologueErrorsInEachTableWhenSkipping) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(DWARF32); + LT.setCustomPrologue({{2, LineTable::Long}, {0, LineTable::Half}}); + LineTable <2 = Gen->addLineTable(DWARF32); + LT2.setCustomPrologue({{2, LineTable::Long}, {1, LineTable::Half}}); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.skip(RecordUnrecoverable); + ASSERT_FALSE(Parser.done()); + Parser.skip(RecordUnrecoverable); + + EXPECT_TRUE(Parser.done()); + + checkError({"parsing line table prologue at offset 0x00000000 found " + "unsupported version 0x00", + "parsing line table prologue at offset 0x00000006 found " + "unsupported version 0x01"}, + std::move(Unrecoverable)); +} + +TEST_F(DebugLineBasicFixture, ParserIgnoresNonPrologueErrorsWhenSkipping) { + if (!setupGenerator()) + return; + + LineTable < = Gen->addLineTable(DWARF32); + LT.addExtendedOpcode(42, DW_LNE_end_sequence, {}); + generate(); + + DWARFDebugLine::SectionParser Parser(LineData, *Context, CUs, TUs); + Parser.skip(RecordUnrecoverable); + + EXPECT_TRUE(Parser.done()); + EXPECT_FALSE(Unrecoverable); +} + +} // end anonymous namespace diff --git a/unittests/DebugInfo/DWARF/DWARFFormValueTest.cpp b/unittests/DebugInfo/DWARF/DWARFFormValueTest.cpp index c552623a7866..582b58062c45 100644 --- a/unittests/DebugInfo/DWARF/DWARFFormValueTest.cpp +++ b/unittests/DebugInfo/DWARF/DWARFFormValueTest.cpp @@ -20,59 +20,6 @@ using namespace dwarf; namespace { -TEST(DWARFFormValue, FixedFormSizes) { - Optional<uint8_t> RefSize; - Optional<uint8_t> AddrSize; - - // Test 32 bit DWARF version 2 with 4 byte addresses. - DWARFFormParams Params_2_4_32 = {2, 4, DWARF32}; - RefSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_2_4_32); - AddrSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_2_4_32); - EXPECT_TRUE(RefSize.hasValue()); - EXPECT_TRUE(AddrSize.hasValue()); - EXPECT_EQ(*RefSize, *AddrSize); - - // Test 32 bit DWARF version 2 with 8 byte addresses. - DWARFFormParams Params_2_8_32 = {2, 8, DWARF32}; - RefSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_2_8_32); - AddrSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_2_8_32); - EXPECT_TRUE(RefSize.hasValue()); - EXPECT_TRUE(AddrSize.hasValue()); - EXPECT_EQ(*RefSize, *AddrSize); - - // DW_FORM_ref_addr is 4 bytes in DWARF 32 in DWARF version 3 and beyond. - DWARFFormParams Params_3_4_32 = {3, 4, DWARF32}; - RefSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_3_4_32); - EXPECT_TRUE(RefSize.hasValue()); - EXPECT_EQ(*RefSize, 4); - - DWARFFormParams Params_4_4_32 = {4, 4, DWARF32}; - RefSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_4_4_32); - EXPECT_TRUE(RefSize.hasValue()); - EXPECT_EQ(*RefSize, 4); - - DWARFFormParams Params_5_4_32 = {5, 4, DWARF32}; - RefSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_5_4_32); - EXPECT_TRUE(RefSize.hasValue()); - EXPECT_EQ(*RefSize, 4); - - // DW_FORM_ref_addr is 8 bytes in DWARF 64 in DWARF version 3 and beyond. - DWARFFormParams Params_3_8_64 = {3, 8, DWARF64}; - RefSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_3_8_64); - EXPECT_TRUE(RefSize.hasValue()); - EXPECT_EQ(*RefSize, 8); - - DWARFFormParams Params_4_8_64 = {4, 8, DWARF64}; - RefSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_4_8_64); - EXPECT_TRUE(RefSize.hasValue()); - EXPECT_EQ(*RefSize, 8); - - DWARFFormParams Params_5_8_64 = {5, 8, DWARF64}; - RefSize = DWARFFormValue::getFixedByteSize(DW_FORM_ref_addr, Params_5_8_64); - EXPECT_TRUE(RefSize.hasValue()); - EXPECT_EQ(*RefSize, 8); -} - bool isFormClass(dwarf::Form Form, DWARFFormValue::FormClass FC) { return DWARFFormValue(Form).isFormClass(FC); } diff --git a/unittests/DebugInfo/DWARF/DwarfGenerator.cpp b/unittests/DebugInfo/DWARF/DwarfGenerator.cpp index 3aa52a0d5b8f..adc400c2fd0e 100644 --- a/unittests/DebugInfo/DWARF/DwarfGenerator.cpp +++ b/unittests/DebugInfo/DWARF/DwarfGenerator.cpp @@ -22,13 +22,15 @@ #include "llvm/MC/MCDwarf.h" #include "llvm/MC/MCInstrInfo.h" #include "llvm/MC/MCObjectFileInfo.h" +#include "llvm/MC/MCObjectWriter.h" #include "llvm/MC/MCRegisterInfo.h" #include "llvm/MC/MCStreamer.h" #include "llvm/MC/MCSubtargetInfo.h" -#include "llvm/MC/MCTargetOptionsCommandFlags.def" +#include "llvm/MC/MCTargetOptionsCommandFlags.inc" #include "llvm/PassAnalysisSupport.h" #include "llvm/Support/TargetRegistry.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Target/TargetLoweringObjectFile.h" #include "llvm/Target/TargetMachine.h" #include "llvm/Target/TargetOptions.h" @@ -52,17 +54,36 @@ void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form, uint64_t U) { DIEInteger(U)); } +void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form, const MCExpr &Expr) { + auto &DG = CU->getGenerator(); + Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form, + DIEExpr(&Expr)); +} + void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form, StringRef String) { auto &DG = CU->getGenerator(); - if (Form == DW_FORM_string) { + switch (Form) { + case DW_FORM_string: Die->addValue(DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form, new (DG.getAllocator()) DIEInlineString(String, DG.getAllocator())); - } else { + break; + + case DW_FORM_strp: + case DW_FORM_GNU_str_index: + case DW_FORM_strx: + case DW_FORM_strx1: + case DW_FORM_strx2: + case DW_FORM_strx3: + case DW_FORM_strx4: Die->addValue( DG.getAllocator(), static_cast<dwarf::Attribute>(A), Form, DIEString(DG.getStringPool().getEntry(*DG.getAsmPrinter(), String))); + break; + + default: + llvm_unreachable("Unhandled form!"); } } @@ -95,6 +116,24 @@ void dwarfgen::DIE::addAttribute(uint16_t A, dwarf::Form Form) { DIEInteger(1)); } +void dwarfgen::DIE::addStrOffsetsBaseAttribute() { + auto &DG = CU->getGenerator(); + auto &MC = *DG.getMCContext(); + AsmPrinter *Asm = DG.getAsmPrinter(); + + const MCSymbol *SectionStart = + Asm->getObjFileLowering().getDwarfStrOffSection()->getBeginSymbol(); + + const MCExpr *Expr = + MCSymbolRefExpr::create(DG.getStringOffsetsStartSym(), MC); + + if (!Asm->MAI->doesDwarfUseRelocationsAcrossSections()) + Expr = MCBinaryExpr::createSub( + Expr, MCSymbolRefExpr::create(SectionStart, MC), MC); + + addAttribute(dwarf::DW_AT_str_offsets_base, DW_FORM_sec_offset, *Expr); +} + dwarfgen::DIE dwarfgen::DIE::addChild(dwarf::Tag Tag) { auto &DG = CU->getGenerator(); return dwarfgen::DIE(CU, @@ -106,6 +145,229 @@ dwarfgen::DIE dwarfgen::CompileUnit::getUnitDIE() { } //===----------------------------------------------------------------------===// +/// dwarfgen::LineTable implementation. +//===----------------------------------------------------------------------===// +DWARFDebugLine::Prologue dwarfgen::LineTable::createBasicPrologue() const { + DWARFDebugLine::Prologue P; + switch (Version) { + case 2: + case 3: + P.TotalLength = 41; + P.PrologueLength = 35; + break; + case 4: + P.TotalLength = 42; + P.PrologueLength = 36; + break; + case 5: + P.TotalLength = 47; + P.PrologueLength = 39; + P.FormParams.AddrSize = AddrSize; + break; + default: + llvm_unreachable("unsupported version"); + } + if (Format == DWARF64) { + P.TotalLength += 4; + P.FormParams.Format = DWARF64; + } + P.FormParams.Version = Version; + P.MinInstLength = 1; + P.MaxOpsPerInst = 1; + P.DefaultIsStmt = 1; + P.LineBase = -5; + P.LineRange = 14; + P.OpcodeBase = 13; + P.StandardOpcodeLengths = {0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1}; + P.IncludeDirectories.push_back(DWARFFormValue(DW_FORM_string)); + P.IncludeDirectories.back().setPValue("a dir"); + P.FileNames.push_back(DWARFDebugLine::FileNameEntry()); + P.FileNames.back().Name.setPValue("a file"); + P.FileNames.back().Name.setForm(DW_FORM_string); + return P; +} + +void dwarfgen::LineTable::setPrologue(DWARFDebugLine::Prologue NewPrologue) { + Prologue = NewPrologue; + CustomPrologue.clear(); +} + +void dwarfgen::LineTable::setCustomPrologue( + ArrayRef<ValueAndLength> NewPrologue) { + Prologue.reset(); + CustomPrologue = NewPrologue; +} + +void dwarfgen::LineTable::addByte(uint8_t Value) { + Contents.push_back({Value, Byte}); +} + +void dwarfgen::LineTable::addStandardOpcode(uint8_t Opcode, + ArrayRef<ValueAndLength> Operands) { + Contents.push_back({Opcode, Byte}); + Contents.insert(Contents.end(), Operands.begin(), Operands.end()); +} + +void dwarfgen::LineTable::addExtendedOpcode(uint64_t Length, uint8_t Opcode, + ArrayRef<ValueAndLength> Operands) { + Contents.push_back({0, Byte}); + Contents.push_back({Length, ULEB}); + Contents.push_back({Opcode, Byte}); + Contents.insert(Contents.end(), Operands.begin(), Operands.end()); +} + +void dwarfgen::LineTable::generate(MCContext &MC, AsmPrinter &Asm) const { + MC.setDwarfVersion(Version); + + MCSymbol *EndSymbol = nullptr; + if (!CustomPrologue.empty()) { + writeData(CustomPrologue, Asm); + } else if (!Prologue) { + EndSymbol = writeDefaultPrologue(Asm); + } else { + writePrologue(Asm); + } + + writeData(Contents, Asm); + if (EndSymbol != nullptr) + Asm.OutStreamer->EmitLabel(EndSymbol); +} + +void dwarfgen::LineTable::writeData(ArrayRef<ValueAndLength> Data, + AsmPrinter &Asm) const { + for (auto Entry : Data) { + switch (Entry.Length) { + case Byte: + case Half: + case Long: + case Quad: + Asm.OutStreamer->EmitIntValue(Entry.Value, Entry.Length); + continue; + case ULEB: + Asm.EmitULEB128(Entry.Value); + continue; + case SLEB: + Asm.EmitSLEB128(Entry.Value); + continue; + } + llvm_unreachable("unsupported ValueAndLength Length value"); + } +} + +MCSymbol *dwarfgen::LineTable::writeDefaultPrologue(AsmPrinter &Asm) const { + MCSymbol *UnitStart = Asm.createTempSymbol("line_unit_start"); + MCSymbol *UnitEnd = Asm.createTempSymbol("line_unit_end"); + if (Format == DwarfFormat::DWARF64) { + Asm.emitInt32(0xffffffff); + Asm.EmitLabelDifference(UnitEnd, UnitStart, 8); + } else { + Asm.EmitLabelDifference(UnitEnd, UnitStart, 4); + } + Asm.OutStreamer->EmitLabel(UnitStart); + Asm.emitInt16(Version); + if (Version == 5) { + Asm.emitInt8(AddrSize); + Asm.emitInt8(SegSize); + } + + MCSymbol *PrologueStart = Asm.createTempSymbol("line_prologue_start"); + MCSymbol *PrologueEnd = Asm.createTempSymbol("line_prologue_end"); + Asm.EmitLabelDifference(PrologueEnd, PrologueStart, + Format == DwarfFormat::DWARF64 ? 8 : 4); + Asm.OutStreamer->EmitLabel(PrologueStart); + + DWARFDebugLine::Prologue DefaultPrologue = createBasicPrologue(); + writeProloguePayload(DefaultPrologue, Asm); + Asm.OutStreamer->EmitLabel(PrologueEnd); + return UnitEnd; +} + +void dwarfgen::LineTable::writePrologue(AsmPrinter &Asm) const { + if (Format == DwarfFormat::DWARF64) { + Asm.emitInt32(0xffffffff); + Asm.emitInt64(Prologue->TotalLength); + } else { + Asm.emitInt32(Prologue->TotalLength); + } + Asm.emitInt16(Prologue->getVersion()); + if (Version == 5) { + Asm.emitInt8(Prologue->getAddressSize()); + Asm.emitInt8(Prologue->SegSelectorSize); + } + if (Format == DwarfFormat::DWARF64) + Asm.emitInt64(Prologue->PrologueLength); + else + Asm.emitInt32(Prologue->PrologueLength); + + writeProloguePayload(*Prologue, Asm); +} + +static void writeCString(StringRef Str, AsmPrinter &Asm) { + Asm.OutStreamer->EmitBytes(Str); + Asm.emitInt8(0); +} + +static void writeV2IncludeAndFileTable(const DWARFDebugLine::Prologue &Prologue, + AsmPrinter &Asm) { + for (auto Include : Prologue.IncludeDirectories) { + assert(Include.getAsCString() && "expected a string form for include dir"); + writeCString(*Include.getAsCString(), Asm); + } + Asm.emitInt8(0); + + for (auto File : Prologue.FileNames) { + assert(File.Name.getAsCString() && "expected a string form for file name"); + writeCString(*File.Name.getAsCString(), Asm); + Asm.EmitULEB128(File.DirIdx); + Asm.EmitULEB128(File.ModTime); + Asm.EmitULEB128(File.Length); + } + Asm.emitInt8(0); +} + +static void writeV5IncludeAndFileTable(const DWARFDebugLine::Prologue &Prologue, + AsmPrinter &Asm) { + Asm.emitInt8(1); // directory_entry_format_count. + // TODO: Add support for other content descriptions - we currently only + // support a single DW_LNCT_path/DW_FORM_string. + Asm.EmitULEB128(DW_LNCT_path); + Asm.EmitULEB128(DW_FORM_string); + Asm.EmitULEB128(Prologue.IncludeDirectories.size()); + for (auto Include : Prologue.IncludeDirectories) { + assert(Include.getAsCString() && "expected a string form for include dir"); + writeCString(*Include.getAsCString(), Asm); + } + + Asm.emitInt8(1); // file_name_entry_format_count. + Asm.EmitULEB128(DW_LNCT_path); + Asm.EmitULEB128(DW_FORM_string); + Asm.EmitULEB128(Prologue.FileNames.size()); + for (auto File : Prologue.FileNames) { + assert(File.Name.getAsCString() && "expected a string form for file name"); + writeCString(*File.Name.getAsCString(), Asm); + } +} + +void dwarfgen::LineTable::writeProloguePayload( + const DWARFDebugLine::Prologue &Prologue, AsmPrinter &Asm) const { + Asm.emitInt8(Prologue.MinInstLength); + if (Version >= 4) + Asm.emitInt8(Prologue.MaxOpsPerInst); + Asm.emitInt8(Prologue.DefaultIsStmt); + Asm.emitInt8(Prologue.LineBase); + Asm.emitInt8(Prologue.LineRange); + Asm.emitInt8(Prologue.OpcodeBase); + for (auto Length : Prologue.StandardOpcodeLengths) { + Asm.emitInt8(Length); + } + + if (Version < 5) + writeV2IncludeAndFileTable(Prologue, Asm); + else + writeV5IncludeAndFileTable(Prologue, Asm); +} + +//===----------------------------------------------------------------------===// /// dwarfgen::Generator implementation. //===----------------------------------------------------------------------===// @@ -148,12 +410,13 @@ llvm::Error dwarfgen::Generator::init(Triple TheTriple, uint16_t V) { return make_error<StringError>("no asm info for target " + TripleName, inconvertibleErrorCode()); - MOFI.reset(new MCObjectFileInfo); - MC.reset(new MCContext(MAI.get(), MRI.get(), MOFI.get())); - MOFI->InitMCObjectFileInfo(TheTriple, /*PIC*/ false, *MC); + MSTI.reset(TheTarget->createMCSubtargetInfo(TripleName, "", "")); + if (!MSTI) + return make_error<StringError>("no subtarget info for target " + TripleName, + inconvertibleErrorCode()); MCTargetOptions MCOptions = InitMCTargetOptionsFromFlags(); - MAB = TheTarget->createMCAsmBackend(*MRI, TripleName, "", MCOptions); + MAB = TheTarget->createMCAsmBackend(*MSTI, *MRI, MCOptions); if (!MAB) return make_error<StringError>("no asm backend for target " + TripleName, inconvertibleErrorCode()); @@ -164,11 +427,16 @@ llvm::Error dwarfgen::Generator::init(Triple TheTriple, uint16_t V) { TripleName, inconvertibleErrorCode()); - MSTI.reset(TheTarget->createMCSubtargetInfo(TripleName, "", "")); - if (!MSTI) - return make_error<StringError>("no subtarget info for target " + TripleName, + TM.reset(TheTarget->createTargetMachine(TripleName, "", "", TargetOptions(), + None)); + if (!TM) + return make_error<StringError>("no target machine for target " + TripleName, inconvertibleErrorCode()); + TLOF = TM->getObjFileLowering(); + MC.reset(new MCContext(MAI.get(), MRI.get(), TLOF)); + TLOF->Initialize(*MC, *TM); + MCE = TheTarget->createMCCodeEmitter(*MII, *MRI, *MC); if (!MCE) return make_error<StringError>("no code emitter for target " + TripleName, @@ -177,22 +445,17 @@ llvm::Error dwarfgen::Generator::init(Triple TheTriple, uint16_t V) { Stream = make_unique<raw_svector_ostream>(FileBytes); MS = TheTarget->createMCObjectStreamer( - TheTriple, *MC, std::unique_ptr<MCAsmBackend>(MAB), *Stream, - std::unique_ptr<MCCodeEmitter>(MCE), *MSTI, MCOptions.MCRelaxAll, - MCOptions.MCIncrementalLinkerCompatible, + TheTriple, *MC, std::unique_ptr<MCAsmBackend>(MAB), + MAB->createObjectWriter(*Stream), std::unique_ptr<MCCodeEmitter>(MCE), + *MSTI, MCOptions.MCRelaxAll, MCOptions.MCIncrementalLinkerCompatible, /*DWARFMustBeAtTheEnd*/ false); if (!MS) return make_error<StringError>("no object streamer for target " + TripleName, inconvertibleErrorCode()); - // Finally create the AsmPrinter we'll use to emit the DIEs. - TM.reset(TheTarget->createTargetMachine(TripleName, "", "", TargetOptions(), - None)); - if (!TM) - return make_error<StringError>("no target machine for target " + TripleName, - inconvertibleErrorCode()); + // Finally create the AsmPrinter we'll use to emit the DIEs. Asm.reset(TheTarget->createAsmPrinter(*TM, std::unique_ptr<MCStreamer>(MS))); if (!Asm) return make_error<StringError>("no asm printer for target " + TripleName, @@ -203,6 +466,7 @@ llvm::Error dwarfgen::Generator::init(Triple TheTriple, uint16_t V) { Asm->setDwarfVersion(Version); StringPool = llvm::make_unique<DwarfStringPool>(Allocator, *Asm, StringRef()); + StringOffsetsStartSym = Asm->createTempSymbol("str_offsets_base"); return Error::success(); } @@ -223,27 +487,36 @@ StringRef dwarfgen::Generator::generate() { SecOffset += CUOffset; CU->setLength(CUOffset - 4); } - Abbreviations.Emit(Asm.get(), MOFI->getDwarfAbbrevSection()); - StringPool->emit(*Asm, MOFI->getDwarfStrSection()); - MS->SwitchSection(MOFI->getDwarfInfoSection()); + Abbreviations.Emit(Asm.get(), TLOF->getDwarfAbbrevSection()); + + StringPool->emitStringOffsetsTableHeader(*Asm, TLOF->getDwarfStrOffSection(), + StringOffsetsStartSym); + StringPool->emit(*Asm, TLOF->getDwarfStrSection(), + TLOF->getDwarfStrOffSection()); + + MS->SwitchSection(TLOF->getDwarfInfoSection()); for (auto &CU : CompileUnits) { uint16_t Version = CU->getVersion(); auto Length = CU->getLength(); MC->setDwarfVersion(Version); assert(Length != -1U); - Asm->EmitInt32(Length); - Asm->EmitInt16(Version); + Asm->emitInt32(Length); + Asm->emitInt16(Version); if (Version <= 4) { - Asm->EmitInt32(0); - Asm->EmitInt8(CU->getAddressSize()); + Asm->emitInt32(0); + Asm->emitInt8(CU->getAddressSize()); } else { - Asm->EmitInt8(dwarf::DW_UT_compile); - Asm->EmitInt8(CU->getAddressSize()); - Asm->EmitInt32(0); + Asm->emitInt8(dwarf::DW_UT_compile); + Asm->emitInt8(CU->getAddressSize()); + Asm->emitInt32(0); } Asm->emitDwarfDIE(*CU->getUnitDIE().Die); } + MS->SwitchSection(TLOF->getDwarfLineSection()); + for (auto < : LineTables) + LT->generate(*MC, *Asm); + MS->Finish(); if (FileBytes.empty()) return StringRef(); @@ -263,7 +536,13 @@ bool dwarfgen::Generator::saveFile(StringRef Path) { } dwarfgen::CompileUnit &dwarfgen::Generator::addCompileUnit() { - CompileUnits.push_back(std::unique_ptr<CompileUnit>( - new CompileUnit(*this, Version, Asm->getPointerSize()))); + CompileUnits.push_back( + make_unique<CompileUnit>(*this, Version, Asm->getPointerSize())); return *CompileUnits.back(); } + +dwarfgen::LineTable &dwarfgen::Generator::addLineTable(DwarfFormat Format) { + LineTables.push_back( + make_unique<LineTable>(Version, Format, Asm->getPointerSize())); + return *LineTables.back(); +} diff --git a/unittests/DebugInfo/DWARF/DwarfGenerator.h b/unittests/DebugInfo/DWARF/DwarfGenerator.h index dd7e8709638d..40ecaa98d053 100644 --- a/unittests/DebugInfo/DWARF/DwarfGenerator.h +++ b/unittests/DebugInfo/DWARF/DwarfGenerator.h @@ -36,11 +36,11 @@ class MCCodeEmitter; class MCContext; struct MCDwarfLineTableParams; class MCInstrInfo; -class MCObjectFileInfo; class MCRegisterInfo; class MCStreamer; class MCSubtargetInfo; class raw_fd_ostream; +class TargetLoweringObjectFile; class TargetMachine; class Triple; @@ -89,6 +89,14 @@ public: /// \param U the unsigned integer to encode. void addAttribute(uint16_t Attr, dwarf::Form Form, uint64_t U); + /// Add an attribute value to be encoded as a DIEExpr + /// + /// \param Attr a dwarf::Attribute enumeration value or any uint16_t that + /// represents a user defined DWARF attribute. + /// \param Form the dwarf::Form to use when encoding the attribute. + /// \param Expr the MC expression used to compute the value. + void addAttribute(uint16_t Attr, dwarf::Form Form, const MCExpr &Expr); + /// Add an attribute value to be encoded as a DIEString or DIEInlinedString. /// /// \param Attr a dwarf::Attribute enumeration value or any uint16_t that @@ -123,6 +131,9 @@ public: /// \param S the size in bytes of the data pointed to by P . void addAttribute(uint16_t Attr, dwarf::Form Form, const void *P, size_t S); + /// Add a DW_AT_str_offsets_base attribute to this DIE. + void addStrOffsetsBaseAttribute(); + /// Add a new child to this DIE object. /// /// \param Tag the dwarf::Tag to assing to the llvm::DIE object. @@ -153,6 +164,72 @@ public: void setLength(uint64_t Length) { DU.setLength(Length); } }; +/// A DWARF line unit-like class used to generate DWARF line units. +/// +/// Instances of this class are created by instances of the Generator class. +class LineTable { +public: + enum ValueLength { Byte = 1, Half = 2, Long = 4, Quad = 8, ULEB, SLEB }; + + struct ValueAndLength { + uint64_t Value; + ValueLength Length; + }; + + LineTable(uint16_t Version, dwarf::DwarfFormat Format, uint8_t AddrSize, + uint8_t SegSize = 0) + : Version(Version), Format(Format), AddrSize(AddrSize), SegSize(SegSize) { + assert(Version >= 2 && Version <= 5 && "unsupported version"); + } + + // Create a Prologue suitable to pass to setPrologue, with a single file and + // include directory entry. + DWARFDebugLine::Prologue createBasicPrologue() const; + + // Set or replace the current prologue with the specified prologue. If no + // prologue is set, a default one will be used when generating. + void setPrologue(DWARFDebugLine::Prologue NewPrologue); + // Used to write an arbitrary payload instead of the standard prologue. This + // is useful if you wish to test handling of corrupt .debug_line sections. + void setCustomPrologue(ArrayRef<ValueAndLength> NewPrologue); + + // Add a byte to the program, with the given value. This can be used to + // specify a special opcode, or to add arbitrary contents to the section. + void addByte(uint8_t Value); + // Add a standard opcode to the program. The opcode and operands do not have + // to be valid. + void addStandardOpcode(uint8_t Opcode, ArrayRef<ValueAndLength> Operands); + // Add an extended opcode to the program with the specified length, opcode, + // and operands. These values do not have to be valid. + void addExtendedOpcode(uint64_t Length, uint8_t Opcode, + ArrayRef<ValueAndLength> Operands); + + // Write the contents of the LineUnit to the current section in the generator. + void generate(MCContext &MC, AsmPrinter &Asm) const; + +private: + void writeData(ArrayRef<ValueAndLength> Data, AsmPrinter &Asm) const; + MCSymbol *writeDefaultPrologue(AsmPrinter &Asm) const; + void writePrologue(AsmPrinter &Asm) const; + + void writeProloguePayload(const DWARFDebugLine::Prologue &Prologue, + AsmPrinter &Asm) const; + + llvm::Optional<DWARFDebugLine::Prologue> Prologue; + std::vector<ValueAndLength> CustomPrologue; + std::vector<ValueAndLength> Contents; + + // The Version field is used for determining how to write the Prologue, if a + // non-custom prologue is used. The version value actually written, will be + // that specified in the Prologue, if a custom prologue has been passed in. + // Otherwise, it will be this value. + uint16_t Version; + + dwarf::DwarfFormat Format; + uint8_t AddrSize; + uint8_t SegSize; +}; + /// A DWARF generator. /// /// Generate DWARF for unit tests by creating any instance of this class and @@ -161,7 +238,6 @@ public: class Generator { std::unique_ptr<MCRegisterInfo> MRI; std::unique_ptr<MCAsmInfo> MAI; - std::unique_ptr<MCObjectFileInfo> MOFI; std::unique_ptr<MCContext> MC; MCAsmBackend *MAB; // Owned by MCStreamer std::unique_ptr<MCInstrInfo> MII; @@ -169,12 +245,16 @@ class Generator { MCCodeEmitter *MCE; // Owned by MCStreamer MCStreamer *MS; // Owned by AsmPrinter std::unique_ptr<TargetMachine> TM; + TargetLoweringObjectFile *TLOF; // Owned by TargetMachine; std::unique_ptr<AsmPrinter> Asm; BumpPtrAllocator Allocator; std::unique_ptr<DwarfStringPool> StringPool; // Entries owned by Allocator. std::vector<std::unique_ptr<CompileUnit>> CompileUnits; + std::vector<std::unique_ptr<LineTable>> LineTables; DIEAbbrevSet Abbreviations; + MCSymbol *StringOffsetsStartSym; + SmallString<4096> FileBytes; /// The stream we use to generate the DWARF into as an ELF file. std::unique_ptr<raw_svector_ostream> Stream; @@ -210,14 +290,23 @@ public: /// /// \returns a dwarfgen::CompileUnit that can be used to retrieve the compile /// unit dwarfgen::DIE that can be used to add attributes and add child DIE - /// objedts to. + /// objects to. dwarfgen::CompileUnit &addCompileUnit(); + /// Add a line table unit to be generated. + /// \param DwarfFormat the DWARF format to use (DWARF32 or DWARF64). + /// + /// \returns a dwarfgen::LineTable that can be used to customise the contents + /// of the line table. + LineTable & + addLineTable(dwarf::DwarfFormat DwarfFormat = dwarf::DwarfFormat::DWARF32); + BumpPtrAllocator &getAllocator() { return Allocator; } AsmPrinter *getAsmPrinter() const { return Asm.get(); } MCContext *getMCContext() const { return MC.get(); } DIEAbbrevSet &getAbbrevSet() { return Abbreviations; } DwarfStringPool &getStringPool() { return *StringPool; } + MCSymbol *getStringOffsetsStartSym() const { return StringOffsetsStartSym; } /// Save the generated DWARF file to disk. /// diff --git a/unittests/DebugInfo/DWARF/DwarfUtils.cpp b/unittests/DebugInfo/DWARF/DwarfUtils.cpp new file mode 100644 index 000000000000..ef43c13db4eb --- /dev/null +++ b/unittests/DebugInfo/DWARF/DwarfUtils.cpp @@ -0,0 +1,43 @@ +//===--- unittests/DebugInfo/DWARF/DwarfUtils.cpp ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "DwarfUtils.h" +#include "llvm/ADT/Triple.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" + +using namespace llvm; + +static void initLLVMIfNeeded() { + static bool gInitialized = false; + if (!gInitialized) { + gInitialized = true; + InitializeAllTargets(); + InitializeAllTargetMCs(); + InitializeAllAsmPrinters(); + InitializeAllAsmParsers(); + } +} + +Triple llvm::dwarf::utils::getHostTripleForAddrSize(uint8_t AddrSize) { + Triple T(Triple::normalize(LLVM_HOST_TRIPLE)); + + if (AddrSize == 8 && T.isArch32Bit()) + return T.get64BitArchVariant(); + if (AddrSize == 4 && T.isArch64Bit()) + return T.get32BitArchVariant(); + return T; +} + +bool llvm::dwarf::utils::isConfigurationSupported(Triple &T) { + initLLVMIfNeeded(); + std::string Err; + return TargetRegistry::lookupTarget(T.getTriple(), Err); +} diff --git a/unittests/DebugInfo/DWARF/DwarfUtils.h b/unittests/DebugInfo/DWARF/DwarfUtils.h new file mode 100644 index 000000000000..3d8045679d54 --- /dev/null +++ b/unittests/DebugInfo/DWARF/DwarfUtils.h @@ -0,0 +1,29 @@ +//===--- unittests/DebugInfo/DWARF/DwarfUtils.h -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_UNITTESTS_DEBUG_INFO_DWARF_DWARFUTILS_H +#define LLVM_UNITTESTS_DEBUG_INFO_DWARF_DWARFUTILS_H + +#include <cstdint> + +namespace llvm { + +class Triple; + +namespace dwarf { +namespace utils { + +Triple getHostTripleForAddrSize(uint8_t AddrSize); +bool isConfigurationSupported(Triple &T); + +} // end namespace utils +} // end namespace dwarf +} // end namespace llvm + +#endif // LLVM_UNITTESTS_DEBUG_INFO_DWARF_DWARFUTILS_H diff --git a/unittests/DebugInfo/MSF/CMakeLists.txt b/unittests/DebugInfo/MSF/CMakeLists.txt index 20f3b2ab3dcd..0e48ab8f2f6b 100644 --- a/unittests/DebugInfo/MSF/CMakeLists.txt +++ b/unittests/DebugInfo/MSF/CMakeLists.txt @@ -2,14 +2,10 @@ set(LLVM_LINK_COMPONENTS DebugInfoMSF ) -set(DebugInfoMSFSources +add_llvm_unittest(DebugInfoMSFTests MappedBlockStreamTest.cpp MSFBuilderTest.cpp MSFCommonTest.cpp ) -add_llvm_unittest(DebugInfoMSFTests - ${DebugInfoMSFSources} - ) - target_link_libraries(DebugInfoMSFTests PRIVATE LLVMTestingSupport) diff --git a/unittests/DebugInfo/MSF/MSFBuilderTest.cpp b/unittests/DebugInfo/MSF/MSFBuilderTest.cpp index a91ac8d443fe..16247951804c 100644 --- a/unittests/DebugInfo/MSF/MSFBuilderTest.cpp +++ b/unittests/DebugInfo/MSF/MSFBuilderTest.cpp @@ -112,7 +112,7 @@ TEST_F(MSFBuilderTest, TestAddStreamNoDirectoryBlockIncrease) { EXPECT_THAT_EXPECTED(ExpectedMsf, Succeeded()); auto &Msf = *ExpectedMsf; - auto ExpectedL1 = Msf.build(); + auto ExpectedL1 = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedL1, Succeeded()); MSFLayout &L1 = *ExpectedL1; @@ -129,7 +129,7 @@ TEST_F(MSFBuilderTest, TestAddStreamNoDirectoryBlockIncrease) { auto Blocks = Msf2.getStreamBlocks(0); EXPECT_EQ(1U, Blocks.size()); - auto ExpectedL2 = Msf2.build(); + auto ExpectedL2 = Msf2.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedL2, Succeeded()); MSFLayout &L2 = *ExpectedL2; auto NewDirBlocks = L2.DirectoryBlocks; @@ -149,7 +149,7 @@ TEST_F(MSFBuilderTest, TestAddStreamWithDirectoryBlockIncrease) { EXPECT_THAT_EXPECTED(Msf.addStream(4096 * 4096 / sizeof(uint32_t)), Succeeded()); - auto ExpectedL1 = Msf.build(); + auto ExpectedL1 = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedL1, Succeeded()); MSFLayout &L1 = *ExpectedL1; auto DirBlocks = L1.DirectoryBlocks; @@ -289,7 +289,7 @@ TEST_F(MSFBuilderTest, BuildMsfLayout) { } ++ExpectedNumBlocks; // The directory itself should use 1 block - auto ExpectedLayout = Msf.build(); + auto ExpectedLayout = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; EXPECT_EQ(4096U, L.SB->BlockSize); @@ -316,7 +316,7 @@ TEST_F(MSFBuilderTest, UseDirectoryBlockHint) { EXPECT_THAT_ERROR(Msf.setDirectoryBlocksHint({B + 1}), Succeeded()); EXPECT_THAT_EXPECTED(Msf.addStream(2048, {B + 2}), Succeeded()); - auto ExpectedLayout = Msf.build(); + auto ExpectedLayout = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; EXPECT_EQ(msf::getMinimumBlockCount() + 2, L.SB->NumBlocks); @@ -338,7 +338,7 @@ TEST_F(MSFBuilderTest, DirectoryBlockHintInsufficient) { uint32_t Size = 4096 * 4096 / 4; EXPECT_THAT_EXPECTED(Msf.addStream(Size), Succeeded()); - auto ExpectedLayout = Msf.build(); + auto ExpectedLayout = Msf.generateLayout(); EXPECT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; EXPECT_EQ(2U, L.DirectoryBlocks.size()); @@ -356,7 +356,7 @@ TEST_F(MSFBuilderTest, DirectoryBlockHintOverestimated) { ASSERT_THAT_EXPECTED(Msf.addStream(2048), Succeeded()); - auto ExpectedLayout = Msf.build(); + auto ExpectedLayout = Msf.generateLayout(); ASSERT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; EXPECT_EQ(1U, L.DirectoryBlocks.size()); @@ -376,7 +376,7 @@ TEST_F(MSFBuilderTest, StreamDoesntUseFpmBlocks) { Expected<uint32_t> SN = Msf.addStream(StreamSize); ASSERT_THAT_EXPECTED(SN, Succeeded()); - auto ExpectedLayout = Msf.build(); + auto ExpectedLayout = Msf.generateLayout(); ASSERT_THAT_EXPECTED(ExpectedLayout, Succeeded()); MSFLayout &L = *ExpectedLayout; auto BlocksRef = L.StreamMap[*SN]; @@ -384,10 +384,9 @@ TEST_F(MSFBuilderTest, StreamDoesntUseFpmBlocks) { EXPECT_EQ(StreamSize, L.StreamSizes[*SN]); for (uint32_t I = 0; I <= 3; ++I) { - // Pages from the regular FPM are allocated, while pages from the alt fpm - // are free. + // Pages from both FPMs are always allocated. + EXPECT_FALSE(L.FreePageMap.test(2 + I * 4096)); EXPECT_FALSE(L.FreePageMap.test(1 + I * 4096)); - EXPECT_TRUE(L.FreePageMap.test(2 + I * 4096)); } for (uint32_t I = 1; I <= 3; ++I) { diff --git a/unittests/DebugInfo/MSF/MSFCommonTest.cpp b/unittests/DebugInfo/MSF/MSFCommonTest.cpp index 144f5b113fb5..ee9ac75d15ce 100644 --- a/unittests/DebugInfo/MSF/MSFCommonTest.cpp +++ b/unittests/DebugInfo/MSF/MSFCommonTest.cpp @@ -46,12 +46,47 @@ TEST(MSFCommonTest, FpmIntervals) { EXPECT_EQ(1u, getNumFpmIntervals(L, true)); SB.NumBlocks = SB.BlockSize; EXPECT_EQ(1u, getNumFpmIntervals(L, true)); - SB.NumBlocks = SB.BlockSize + 1; - EXPECT_EQ(2u, getNumFpmIntervals(L, true)); SB.NumBlocks = SB.BlockSize * 8; EXPECT_EQ(8u, getNumFpmIntervals(L, true)); - SB.NumBlocks = SB.BlockSize * 8 + 1; - EXPECT_EQ(9u, getNumFpmIntervals(L, true)); + + // The FPM is going to look like this: + // | 0 | 1 | 2 | ... | 4096 | 4097 | 4098 | ... | + // | SB | FPM0 | FPM1 | Data | Data | FPM0 | FPM1 | ... | + // + // So when there are up to 4097 blocks (last index 4096), the final blocks + // are data blocks. When there are 4098 blocks (last index 4097), there is + // one terminating FPM block, and when there are 4099 blocks, there are two + // terminating FPM blocks. Make sure all these cases are handled. + + // With 4096 or 4097 blocks, the last block is a data block so we only have + // 1 interval. + for (uint32_t I : {4096, 4097}) { + // 1 FPM0 interval + EXPECT_EQ(1U, getNumFpmIntervals(4096, I, true, 1)); + EXPECT_EQ(1U, getNumFpmIntervals(4096, I, false, 1)); + + // 1 FPM1 interval + EXPECT_EQ(1U, getNumFpmIntervals(4096, I, true, 2)); + EXPECT_EQ(1U, getNumFpmIntervals(4096, I, false, 2)); + } + + // With 4098 blocks, the last block belongs to FPM0 so we should have 2 FPM0 + // intervals. + EXPECT_EQ(2U, getNumFpmIntervals(4096, 4098, true, 1)); + EXPECT_EQ(1U, getNumFpmIntervals(4096, 4098, false, 1)); + + // And 1 FPM1 interval. + EXPECT_EQ(1U, getNumFpmIntervals(4096, 4098, true, 2)); + EXPECT_EQ(1U, getNumFpmIntervals(4096, 4098, false, 2)); + + // With 4099 blocks, the last block belongs to FPM1 so we should have 2 + // FPM0 intervals. + EXPECT_EQ(2U, getNumFpmIntervals(4096, 4099, true, 1)); + EXPECT_EQ(1U, getNumFpmIntervals(4096, 4099, false, 1)); + + // And 2 FPM1 intervals. + EXPECT_EQ(2U, getNumFpmIntervals(4096, 4099, true, 2)); + EXPECT_EQ(1U, getNumFpmIntervals(4096, 4099, false, 2)); } TEST(MSFCommonTest, FpmStreamLayout) { @@ -95,7 +130,7 @@ TEST(MSFCommonTest, FpmStreamLayout) { // 2. When we are including unused FPM data, there should be one FPM block // at every BlockSize interval in the file, even if entire FPM blocks are // unused. - SB.NumBlocks = SB.BlockSize * 8 + 1; + SB.NumBlocks = SB.BlockSize * 8 + 3; SL = getFpmStreamLayout(L, true, false); EXPECT_EQ(SB.BlockSize * 9, SL.Length); EXPECT_EQ(9u, SL.Blocks.size()); diff --git a/unittests/DebugInfo/PDB/CMakeLists.txt b/unittests/DebugInfo/PDB/CMakeLists.txt index b19ee2cf43a5..5410e5f895d4 100644 --- a/unittests/DebugInfo/PDB/CMakeLists.txt +++ b/unittests/DebugInfo/PDB/CMakeLists.txt @@ -4,14 +4,10 @@ set(LLVM_LINK_COMPONENTS DebugInfoPDB ) -set(DebugInfoPDBSources +add_llvm_unittest(DebugInfoPDBTests HashTableTest.cpp StringTableBuilderTest.cpp PDBApiTest.cpp ) -add_llvm_unittest(DebugInfoPDBTests - ${DebugInfoPDBSources} - ) - target_link_libraries(DebugInfoPDBTests PRIVATE LLVMTestingSupport) diff --git a/unittests/DebugInfo/PDB/HashTableTest.cpp b/unittests/DebugInfo/PDB/HashTableTest.cpp index f1968e55e86f..301b215badf5 100644 --- a/unittests/DebugInfo/PDB/HashTableTest.cpp +++ b/unittests/DebugInfo/PDB/HashTableTest.cpp @@ -8,9 +8,14 @@ //===----------------------------------------------------------------------===// #include "llvm/DebugInfo/PDB/Native/HashTable.h" + +#include "llvm/DebugInfo/PDB/Native/Hash.h" +#include "llvm/DebugInfo/PDB/Native/NamedStreamMap.h" +#include "llvm/Support/Allocator.h" #include "llvm/Support/BinaryByteStream.h" #include "llvm/Support/BinaryStreamReader.h" #include "llvm/Support/BinaryStreamWriter.h" +#include "llvm/Support/StringSaver.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" @@ -22,7 +27,8 @@ using namespace llvm::pdb; using namespace llvm::support; namespace { -class HashTableInternals : public HashTable { + +class HashTableInternals : public HashTable<uint32_t> { public: using HashTable::Buckets; using HashTable::Present; @@ -31,18 +37,18 @@ public: } TEST(HashTableTest, TestSimple) { - HashTable Table; + HashTableInternals Table; EXPECT_EQ(0u, Table.size()); EXPECT_GT(Table.capacity(), 0u); - Table.set(3, 7); + Table.set_as(3u, 7); EXPECT_EQ(1u, Table.size()); - ASSERT_NE(Table.end(), Table.find(3)); - EXPECT_EQ(7u, Table.get(3)); + ASSERT_NE(Table.end(), Table.find_as(3u)); + EXPECT_EQ(7u, Table.get(3u)); } TEST(HashTableTest, TestCollision) { - HashTable Table; + HashTableInternals Table; EXPECT_EQ(0u, Table.size()); EXPECT_GT(Table.capacity(), 0u); @@ -52,39 +58,33 @@ TEST(HashTableTest, TestCollision) { uint32_t N1 = Table.capacity() + 1; uint32_t N2 = 2 * N1; - Table.set(N1, 7); - Table.set(N2, 12); + Table.set_as(N1, 7); + Table.set_as(N2, 12); EXPECT_EQ(2u, Table.size()); - ASSERT_NE(Table.end(), Table.find(N1)); - ASSERT_NE(Table.end(), Table.find(N2)); + ASSERT_NE(Table.end(), Table.find_as(N1)); + ASSERT_NE(Table.end(), Table.find_as(N2)); EXPECT_EQ(7u, Table.get(N1)); EXPECT_EQ(12u, Table.get(N2)); } TEST(HashTableTest, TestRemove) { - HashTable Table; + HashTableInternals Table; EXPECT_EQ(0u, Table.size()); EXPECT_GT(Table.capacity(), 0u); - Table.set(1, 2); - Table.set(3, 4); + Table.set_as(1u, 2); + Table.set_as(3u, 4); EXPECT_EQ(2u, Table.size()); - ASSERT_NE(Table.end(), Table.find(1)); - ASSERT_NE(Table.end(), Table.find(3)); - - EXPECT_EQ(2u, Table.get(1)); - EXPECT_EQ(4u, Table.get(3)); + ASSERT_NE(Table.end(), Table.find_as(1u)); + ASSERT_NE(Table.end(), Table.find_as(3u)); - Table.remove(1u); - EXPECT_EQ(1u, Table.size()); - EXPECT_EQ(Table.end(), Table.find(1)); - ASSERT_NE(Table.end(), Table.find(3)); - EXPECT_EQ(4u, Table.get(3)); + EXPECT_EQ(2u, Table.get(1u)); + EXPECT_EQ(4u, Table.get(3u)); } TEST(HashTableTest, TestCollisionAfterMultipleProbes) { - HashTable Table; + HashTableInternals Table; EXPECT_EQ(0u, Table.size()); EXPECT_GT(Table.capacity(), 0u); @@ -95,31 +95,17 @@ TEST(HashTableTest, TestCollisionAfterMultipleProbes) { uint32_t N2 = N1 + 1; uint32_t N3 = 2 * N1; - Table.set(N1, 7); - Table.set(N2, 11); - Table.set(N3, 13); + Table.set_as(N1, 7); + Table.set_as(N2, 11); + Table.set_as(N3, 13); EXPECT_EQ(3u, Table.size()); - ASSERT_NE(Table.end(), Table.find(N1)); - ASSERT_NE(Table.end(), Table.find(N2)); - ASSERT_NE(Table.end(), Table.find(N3)); + ASSERT_NE(Table.end(), Table.find_as(N1)); + ASSERT_NE(Table.end(), Table.find_as(N2)); + ASSERT_NE(Table.end(), Table.find_as(N3)); EXPECT_EQ(7u, Table.get(N1)); EXPECT_EQ(11u, Table.get(N2)); EXPECT_EQ(13u, Table.get(N3)); - - // Remove the one that had been filled in the middle, then insert another one - // with a collision. It should fill the newly emptied slot. - Table.remove(N2); - uint32_t N4 = N1 * 3; - Table.set(N4, 17); - EXPECT_EQ(3u, Table.size()); - ASSERT_NE(Table.end(), Table.find(N1)); - ASSERT_NE(Table.end(), Table.find(N3)); - ASSERT_NE(Table.end(), Table.find(N4)); - - EXPECT_EQ(7u, Table.get(N1)); - EXPECT_EQ(13u, Table.get(N3)); - EXPECT_EQ(17u, Table.get(N4)); } TEST(HashTableTest, Grow) { @@ -127,15 +113,15 @@ TEST(HashTableTest, Grow) { // guaranteed to trigger a grow. Then verify that the size is the same, the // capacity is larger, and all the original items are still in the table. - HashTable Table; + HashTableInternals Table; uint32_t OldCapacity = Table.capacity(); for (uint32_t I = 0; I < OldCapacity; ++I) { - Table.set(OldCapacity + I * 2 + 1, I * 2 + 3); + Table.set_as(OldCapacity + I * 2 + 1, I * 2 + 3); } EXPECT_EQ(OldCapacity, Table.size()); EXPECT_GT(Table.capacity(), OldCapacity); for (uint32_t I = 0; I < OldCapacity; ++I) { - ASSERT_NE(Table.end(), Table.find(OldCapacity + I * 2 + 1)); + ASSERT_NE(Table.end(), Table.find_as(OldCapacity + I * 2 + 1)); EXPECT_EQ(I * 2 + 3, Table.get(OldCapacity + I * 2 + 1)); } } @@ -144,7 +130,7 @@ TEST(HashTableTest, Serialization) { HashTableInternals Table; uint32_t Cap = Table.capacity(); for (uint32_t I = 0; I < Cap; ++I) { - Table.set(Cap + I * 2 + 1, I * 2 + 3); + Table.set_as(Cap + I * 2 + 1, I * 2 + 3); } std::vector<uint8_t> Buffer(Table.calculateSerializedLength()); @@ -166,3 +152,109 @@ TEST(HashTableTest, Serialization) { EXPECT_EQ(Table.Present, Table2.Present); EXPECT_EQ(Table.Deleted, Table2.Deleted); } + +TEST(HashTableTest, NamedStreamMap) { + std::vector<StringRef> Streams = {"One", "Two", "Three", "Four", + "Five", "Six", "Seven"}; + StringMap<uint32_t> ExpectedIndices; + for (uint32_t I = 0; I < Streams.size(); ++I) + ExpectedIndices[Streams[I]] = I + 1; + + // To verify the hash table actually works, we want to verify that insertion + // order doesn't matter. So try inserting in every possible order of 7 items. + do { + NamedStreamMap NSM; + for (StringRef S : Streams) + NSM.set(S, ExpectedIndices[S]); + + EXPECT_EQ(Streams.size(), NSM.size()); + + uint32_t N; + EXPECT_TRUE(NSM.get("One", N)); + EXPECT_EQ(1U, N); + + EXPECT_TRUE(NSM.get("Two", N)); + EXPECT_EQ(2U, N); + + EXPECT_TRUE(NSM.get("Three", N)); + EXPECT_EQ(3U, N); + + EXPECT_TRUE(NSM.get("Four", N)); + EXPECT_EQ(4U, N); + + EXPECT_TRUE(NSM.get("Five", N)); + EXPECT_EQ(5U, N); + + EXPECT_TRUE(NSM.get("Six", N)); + EXPECT_EQ(6U, N); + + EXPECT_TRUE(NSM.get("Seven", N)); + EXPECT_EQ(7U, N); + } while (std::next_permutation(Streams.begin(), Streams.end())); +} + +namespace { +struct FooBar { + uint32_t X; + uint32_t Y; +}; + +} // namespace + +namespace llvm { +namespace pdb { +template <> struct PdbHashTraits<FooBar> { + std::vector<char> Buffer; + + PdbHashTraits() { Buffer.push_back(0); } + + uint32_t hashLookupKey(StringRef S) const { + return llvm::pdb::hashStringV1(S); + } + + StringRef storageKeyToLookupKey(uint32_t N) const { + if (N >= Buffer.size()) + return StringRef(); + + return StringRef(Buffer.data() + N); + } + + uint32_t lookupKeyToStorageKey(StringRef S) { + uint32_t N = Buffer.size(); + Buffer.insert(Buffer.end(), S.begin(), S.end()); + Buffer.push_back('\0'); + return N; + } +}; +} // namespace pdb +} // namespace llvm + +TEST(HashTableTest, NonTrivialValueType) { + HashTable<FooBar> Table; + uint32_t Cap = Table.capacity(); + for (uint32_t I = 0; I < Cap; ++I) { + FooBar F; + F.X = I; + F.Y = I + 1; + Table.set_as(utostr(I), F); + } + + std::vector<uint8_t> Buffer(Table.calculateSerializedLength()); + MutableBinaryByteStream Stream(Buffer, little); + BinaryStreamWriter Writer(Stream); + EXPECT_THAT_ERROR(Table.commit(Writer), Succeeded()); + // We should have written precisely the number of bytes we calculated earlier. + EXPECT_EQ(Buffer.size(), Writer.getOffset()); + + HashTable<FooBar> Table2; + BinaryStreamReader Reader(Stream); + EXPECT_THAT_ERROR(Table2.load(Reader), Succeeded()); + // We should have read precisely the number of bytes we calculated earlier. + EXPECT_EQ(Buffer.size(), Reader.getOffset()); + + EXPECT_EQ(Table.size(), Table2.size()); + EXPECT_EQ(Table.capacity(), Table2.capacity()); + // EXPECT_EQ(Table.Buckets, Table2.Buckets); + // EXPECT_EQ(Table.Present, Table2.Present); + // EXPECT_EQ(Table.Deleted, Table2.Deleted); +} diff --git a/unittests/DebugInfo/PDB/PDBApiTest.cpp b/unittests/DebugInfo/PDB/PDBApiTest.cpp index e998acf009ec..41b679825f17 100644 --- a/unittests/DebugInfo/PDB/PDBApiTest.cpp +++ b/unittests/DebugInfo/PDB/PDBApiTest.cpp @@ -11,7 +11,10 @@ #include "llvm/ADT/STLExtras.h" #include "llvm/DebugInfo/PDB/IPDBEnumChildren.h" +#include "llvm/DebugInfo/PDB/IPDBInjectedSource.h" +#include "llvm/DebugInfo/PDB/IPDBLineNumber.h" #include "llvm/DebugInfo/PDB/IPDBRawSymbol.h" +#include "llvm/DebugInfo/PDB/IPDBSectionContrib.h" #include "llvm/DebugInfo/PDB/IPDBSession.h" #include "llvm/DebugInfo/PDB/IPDBSourceFile.h" #include "llvm/DebugInfo/PDB/IPDBTable.h" @@ -63,7 +66,7 @@ namespace { class MockSession : public IPDBSession { uint64_t getLoadAddress() const override { return 0; } - void setLoadAddress(uint64_t Address) override {} + bool setLoadAddress(uint64_t Address) override { return false; } std::unique_ptr<PDBSymbolExe> getGlobalScope() override { return nullptr; } std::unique_ptr<PDBSymbol> getSymbolById(uint32_t SymbolId) const override { return nullptr; @@ -72,11 +75,27 @@ class MockSession : public IPDBSession { getSourceFileById(uint32_t SymbolId) const override { return nullptr; } - + bool addressForVA(uint64_t VA, uint32_t &Section, + uint32_t &Offset) const override { + return false; + } + bool addressForRVA(uint32_t RVA, uint32_t &Section, + uint32_t &Offset) const override { + return false; + } std::unique_ptr<PDBSymbol> findSymbolByAddress(uint64_t Address, PDB_SymType Type) const override { return nullptr; } + std::unique_ptr<PDBSymbol> findSymbolByRVA(uint32_t RVA, + PDB_SymType Type) const override { + return nullptr; + } + std::unique_ptr<PDBSymbol> + findSymbolBySectOffset(uint32_t Sect, uint32_t Offset, + PDB_SymType Type) const override { + return nullptr; + } std::unique_ptr<IPDBEnumLineNumbers> findLineNumbers(const PDBSymbolCompiland &Compiland, const IPDBSourceFile &File) const override { @@ -86,6 +105,15 @@ class MockSession : public IPDBSession { findLineNumbersByAddress(uint64_t Address, uint32_t Length) const override { return nullptr; } + std::unique_ptr<IPDBEnumLineNumbers> + findLineNumbersByRVA(uint32_t RVA, uint32_t Length) const override { + return nullptr; + } + std::unique_ptr<IPDBEnumLineNumbers> + findLineNumbersBySectOffset(uint32_t Section, uint32_t Offset, + uint32_t Length) const override { + return nullptr; + } std::unique_ptr<IPDBEnumSourceFiles> findSourceFiles(const PDBSymbolCompiland *Compiland, llvm::StringRef Pattern, PDB_NameSearchFlags Flags) const override { @@ -123,6 +151,14 @@ class MockSession : public IPDBSession { std::unique_ptr<IPDBEnumTables> getEnumTables() const override { return nullptr; } + + std::unique_ptr<IPDBEnumInjectedSources> getInjectedSources() const override { + return nullptr; + } + + std::unique_ptr<IPDBEnumSectionContribs> getSectionContribs() const override { + return nullptr; + } }; class MockRawSymbol : public IPDBRawSymbol { @@ -142,14 +178,48 @@ public: return nullptr; } std::unique_ptr<IPDBEnumSymbols> + findChildrenByAddr(PDB_SymType Type, StringRef Name, PDB_NameSearchFlags Flags, + uint32_t Section, uint32_t Offset) const override { + return nullptr; + } + std::unique_ptr<IPDBEnumSymbols> + findChildrenByVA(PDB_SymType Type, StringRef Name, PDB_NameSearchFlags Flags, + uint64_t VA) const override { + return nullptr; + } + std::unique_ptr<IPDBEnumSymbols> findChildrenByRVA(PDB_SymType Type, StringRef Name, PDB_NameSearchFlags Flags, uint32_t RVA) const override { return nullptr; } std::unique_ptr<IPDBEnumSymbols> + findInlineFramesByAddr(uint32_t Section, uint32_t Offset) const override { + return nullptr; + } + std::unique_ptr<IPDBEnumSymbols> findInlineFramesByRVA(uint32_t RVA) const override { return nullptr; } + std::unique_ptr<IPDBEnumSymbols> + findInlineFramesByVA(uint64_t VA) const override { + return nullptr; + } + std::unique_ptr<IPDBEnumLineNumbers> findInlineeLines() const override { + return nullptr; + } + std::unique_ptr<IPDBEnumLineNumbers> + findInlineeLinesByAddr(uint32_t Section, uint32_t Offset, + uint32_t Length) const override { + return nullptr; + } + std::unique_ptr<IPDBEnumLineNumbers> + findInlineeLinesByRVA(uint32_t RVA, uint32_t Length) const override { + return nullptr; + } + std::unique_ptr<IPDBEnumLineNumbers> + findInlineeLinesByVA(uint64_t VA, uint32_t Length) const override { + return nullptr; + } void getDataBytes(llvm::SmallVector<uint8_t, 32> &bytes) const override {} void getFrontEndVersion(VersionInfo &Version) const override {} @@ -161,6 +231,10 @@ public: return {}; } + std::unique_ptr<IPDBLineNumber> getSrcLineOnTypeDefn() const override { + return nullptr; + } + MOCK_SYMBOL_ACCESSOR(getAccess) MOCK_SYMBOL_ACCESSOR(getAddressOffset) MOCK_SYMBOL_ACCESSOR(getAddressSection) @@ -428,5 +502,4 @@ TEST_F(PDBApiTest, Dyncast) { VerifyUnknownDyncasts(); } - } // end anonymous namespace diff --git a/unittests/DebugInfo/PDB/StringTableBuilderTest.cpp b/unittests/DebugInfo/PDB/StringTableBuilderTest.cpp index 0efc2c6411b8..bf08ab20923b 100644 --- a/unittests/DebugInfo/PDB/StringTableBuilderTest.cpp +++ b/unittests/DebugInfo/PDB/StringTableBuilderTest.cpp @@ -27,10 +27,29 @@ class StringTableBuilderTest : public ::testing::Test {}; TEST_F(StringTableBuilderTest, Simple) { // Create /names table contents. PDBStringTableBuilder Builder; - EXPECT_EQ(1U, Builder.insert("foo")); - EXPECT_EQ(5U, Builder.insert("bar")); - EXPECT_EQ(1U, Builder.insert("foo")); - EXPECT_EQ(9U, Builder.insert("baz")); + + // This test case is carefully constructed to ensure that at least one + // string gets bucketed into slot 0, *and* to ensure that at least one + // has a hash collision at the end of the bucket list so it has to + // wrap around. + uint32_t FooID = Builder.insert("foo"); + uint32_t BarID = Builder.insert("bar"); + uint32_t BazID = Builder.insert("baz"); + uint32_t BuzzID = Builder.insert("buzz"); + uint32_t BazzID = Builder.insert("bazz"); + uint32_t BarrID = Builder.insert("barr"); + + // Re-inserting the same item should return the same id. + EXPECT_EQ(FooID, Builder.insert("foo")); + EXPECT_EQ(BarID, Builder.insert("bar")); + EXPECT_EQ(BazID, Builder.insert("baz")); + EXPECT_EQ(BuzzID, Builder.insert("buzz")); + EXPECT_EQ(BazzID, Builder.insert("bazz")); + EXPECT_EQ(BarrID, Builder.insert("barr")); + + // Each ID should be distinct. + std::set<uint32_t> Distinct{FooID, BarID, BazID, BuzzID, BazzID, BarrID}; + EXPECT_EQ(6U, Distinct.size()); std::vector<uint8_t> Buffer(Builder.calculateSerializedSize()); MutableBinaryByteStream OutStream(Buffer, little); @@ -43,13 +62,20 @@ TEST_F(StringTableBuilderTest, Simple) { PDBStringTable Table; EXPECT_THAT_ERROR(Table.reload(Reader), Succeeded()); - EXPECT_EQ(3U, Table.getNameCount()); + EXPECT_EQ(6U, Table.getNameCount()); EXPECT_EQ(1U, Table.getHashVersion()); - EXPECT_THAT_EXPECTED(Table.getStringForID(1), HasValue("foo")); - EXPECT_THAT_EXPECTED(Table.getStringForID(5), HasValue("bar")); - EXPECT_THAT_EXPECTED(Table.getStringForID(9), HasValue("baz")); - EXPECT_THAT_EXPECTED(Table.getIDForString("foo"), HasValue(1U)); - EXPECT_THAT_EXPECTED(Table.getIDForString("bar"), HasValue(5U)); - EXPECT_THAT_EXPECTED(Table.getIDForString("baz"), HasValue(9U)); + EXPECT_THAT_EXPECTED(Table.getStringForID(FooID), HasValue("foo")); + EXPECT_THAT_EXPECTED(Table.getStringForID(BarID), HasValue("bar")); + EXPECT_THAT_EXPECTED(Table.getStringForID(BazID), HasValue("baz")); + EXPECT_THAT_EXPECTED(Table.getStringForID(BuzzID), HasValue("buzz")); + EXPECT_THAT_EXPECTED(Table.getStringForID(BazzID), HasValue("bazz")); + EXPECT_THAT_EXPECTED(Table.getStringForID(BarrID), HasValue("barr")); + + EXPECT_THAT_EXPECTED(Table.getIDForString("foo"), HasValue(FooID)); + EXPECT_THAT_EXPECTED(Table.getIDForString("bar"), HasValue(BarID)); + EXPECT_THAT_EXPECTED(Table.getIDForString("baz"), HasValue(BazID)); + EXPECT_THAT_EXPECTED(Table.getIDForString("buzz"), HasValue(BuzzID)); + EXPECT_THAT_EXPECTED(Table.getIDForString("bazz"), HasValue(BazzID)); + EXPECT_THAT_EXPECTED(Table.getIDForString("barr"), HasValue(BarrID)); } diff --git a/unittests/Demangle/CMakeLists.txt b/unittests/Demangle/CMakeLists.txt new file mode 100644 index 000000000000..3633d3212eb0 --- /dev/null +++ b/unittests/Demangle/CMakeLists.txt @@ -0,0 +1,7 @@ +set(LLVM_LINK_COMPONENTS + Demangle +) + +add_llvm_unittest(DemangleTests + PartialDemangleTest.cpp +) diff --git a/unittests/Demangle/PartialDemangleTest.cpp b/unittests/Demangle/PartialDemangleTest.cpp new file mode 100644 index 000000000000..49d50169e5c7 --- /dev/null +++ b/unittests/Demangle/PartialDemangleTest.cpp @@ -0,0 +1,149 @@ +//===----------------------- PartialDemangleTest.cpp ----------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is dual licensed under the MIT and the University of Illinois Open +// Source Licenses. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include <cstdlib> +#include "llvm/Demangle/Demangle.h" +#include "gtest/gtest.h" + +struct ChoppedName { + const char *Mangled; + const char *ContextName, *BaseName, *ReturnType, *Params; +}; + +static ChoppedName NamesToTest[] = { + {"_Z1fv", "", "f", "", "()"}, + {"_ZN1a1b1cIiiiEEvm", "a::b", "c", "void", "(unsigned long)"}, + {"_ZZ5OuterIiEivEN5Inner12inner_memberEv", + "int Outer<int>()::Inner", "inner_member", "", "()"}, + {"_Z1fIiEPFvvEv", "", "f", "void (*)()", "()"}, + {"_ZN1S1fIiEEvv", "S", "f", "void", "()"}, + + // Call operator for a lambda in f(). + {"_ZZ1fvENK3$_0clEi", "f()::$_0", "operator()", "", "(int)"}, + + // A call operator for a lambda in a lambda in f(). + {"_ZZZ1fvENK3$_0clEvENKUlvE_clEv", + "f()::$_0::operator()() const::'lambda'()", "operator()", "", "()"}, + + {"_ZZN1S1fEiiEd0_NKUlvE_clEv", + "S::f(int, int)::'lambda'()", "operator()", "", "()"}, + + {"_ZN1Scv7MuncherIJDpPT_EEIJFivEA_iEEEv", + "S", "operator Muncher<int (*)(), int (*) []>", "", "()"}, + + // Attributes. + {"_ZN5test4IdE1fEUa9enable_ifIXeqfL0p_Li1EEXeqfL0p0_Li2EEEi", + "test4<double>", "f", "", "(int)"}, + {"_ZN1SC2B8ctor_tagEv", "S", "S", "", "()"}, + {"_ZN1S1fB4MERPIiEEvv", "S", "f", "void", "()"}, + + {"_ZNSsC1EmcRKSaIcE", + "std::basic_string<char, std::char_traits<char>, std::allocator<char> >", + "basic_string", "", "(unsigned long, char, std::allocator<char> const&)"}, + {"_ZNSsixEm", "std::string", "operator[]", "", "(unsigned long)"}, + {"_ZSt17__throw_bad_allocv", "std", "__throw_bad_alloc", "", "()"}, + + {"_ZN1AI1BEC2Ev", "A<B>", "A", "", "()"}, + {"_ZN1AI1BED2Ev", "A<B>", "~A", "", "()"}, + {"_ZN1AI1BECI24BaseEi", "A<B>", "A", "", "(int)"}, + {"_ZNKR1AI1BE1fIiEEiv", "A<B>", "f", "int", "()"}, + + {"_ZN1SIJicfEE3mfnIJjcdEEEvicfDpT_", "S<int, char, float>", + "mfn", "void", "(int, char, float, unsigned int, char, double)"}, +}; + +TEST(PartialDemangleTest, TestNameChopping) { + size_t Size = 1; + char *Buf = static_cast<char *>(std::malloc(Size)); + + llvm::ItaniumPartialDemangler D; + + for (ChoppedName &N : NamesToTest) { + EXPECT_FALSE(D.partialDemangle(N.Mangled)); + EXPECT_TRUE(D.isFunction()); + EXPECT_FALSE(D.isData()); + EXPECT_FALSE(D.isSpecialName()); + + Buf = D.getFunctionDeclContextName(Buf, &Size); + EXPECT_STREQ(Buf, N.ContextName); + + Buf = D.getFunctionBaseName(Buf, &Size); + EXPECT_STREQ(Buf, N.BaseName); + + Buf = D.getFunctionReturnType(Buf, &Size); + EXPECT_STREQ(Buf, N.ReturnType); + + Buf = D.getFunctionParameters(Buf, &Size); + EXPECT_STREQ(Buf, N.Params); + } + + std::free(Buf); +} + +TEST(PartialDemangleTest, TestNameMeta) { + llvm::ItaniumPartialDemangler Demangler; + + EXPECT_FALSE(Demangler.partialDemangle("_ZNK1f1gEv")); + EXPECT_TRUE(Demangler.isFunction()); + EXPECT_TRUE(Demangler.hasFunctionQualifiers()); + EXPECT_FALSE(Demangler.isSpecialName()); + EXPECT_FALSE(Demangler.isData()); + + EXPECT_FALSE(Demangler.partialDemangle("_Z1fv")); + EXPECT_FALSE(Demangler.hasFunctionQualifiers()); + + EXPECT_FALSE(Demangler.partialDemangle("_ZTV1S")); + EXPECT_TRUE(Demangler.isSpecialName()); + EXPECT_FALSE(Demangler.isData()); + EXPECT_FALSE(Demangler.isFunction()); + + EXPECT_FALSE(Demangler.partialDemangle("_ZN1aDC1a1b1cEE")); + EXPECT_FALSE(Demangler.isFunction()); + EXPECT_FALSE(Demangler.isSpecialName()); + EXPECT_TRUE(Demangler.isData()); +} + +TEST(PartialDemanglerTest, TestCtorOrDtor) { + static const char *Pos[] = { + "_ZN1AC1Ev", // A::A() + "_ZN1AC1IiEET_", // A::A<int>(int) + "_ZN1AD2Ev", // A::~A() + "_ZN1BIiEC1IcEET_", // B<int>::B<char>(char) + "_ZN1AC1B1TEv", // A::A[abi:T]() + "_ZNSt1AD2Ev", // std::A::~A() + "_ZN2ns1AD1Ev", // ns::A::~A() + }; + static const char *Neg[] = { + "_Z1fv", + "_ZN1A1gIiEEvT_", // void A::g<int>(int) + }; + + llvm::ItaniumPartialDemangler D; + for (const char *N : Pos) { + EXPECT_FALSE(D.partialDemangle(N)); + EXPECT_TRUE(D.isCtorOrDtor()); + } + for (const char *N : Neg) { + EXPECT_FALSE(D.partialDemangle(N)); + EXPECT_FALSE(D.isCtorOrDtor()); + } +} + +TEST(PartialDemanglerTest, TestMisc) { + llvm::ItaniumPartialDemangler D1, D2; + + EXPECT_FALSE(D1.partialDemangle("_Z1fv")); + EXPECT_FALSE(D2.partialDemangle("_Z1g")); + std::swap(D1, D2); + EXPECT_FALSE(D1.isFunction()); + EXPECT_TRUE(D2.isFunction()); + + EXPECT_TRUE(D1.partialDemangle("Not a mangled name!")); + +} diff --git a/unittests/ExecutionEngine/MCJIT/CMakeLists.txt b/unittests/ExecutionEngine/MCJIT/CMakeLists.txt index e29787f8f426..b5f8a14c41c1 100644 --- a/unittests/ExecutionEngine/MCJIT/CMakeLists.txt +++ b/unittests/ExecutionEngine/MCJIT/CMakeLists.txt @@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS Core ExecutionEngine IPO + InstCombine MC MCJIT RuntimeDyld diff --git a/unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h b/unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h index a3d3d1fffdbe..d3ebb95abc3a 100644 --- a/unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h +++ b/unittests/ExecutionEngine/MCJIT/MCJITTestAPICommon.h @@ -46,10 +46,10 @@ protected: // fail to initialize the AssumptionCacheTracker. initializeAssumptionCacheTrackerPass(*PassRegistry::getPassRegistry()); -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 // On Windows, generate ELF objects by specifying "-elf" in triple HostTriple += "-elf"; -#endif // LLVM_ON_WIN32 +#endif // _WIN32 HostTriple = Triple::normalize(HostTriple); } diff --git a/unittests/ExecutionEngine/Orc/CMakeLists.txt b/unittests/ExecutionEngine/Orc/CMakeLists.txt index dd0281d0b73d..6dbff7c592a4 100644 --- a/unittests/ExecutionEngine/Orc/CMakeLists.txt +++ b/unittests/ExecutionEngine/Orc/CMakeLists.txt @@ -11,9 +11,11 @@ set(LLVM_LINK_COMPONENTS add_llvm_unittest(OrcJITTests CompileOnDemandLayerTest.cpp + CoreAPIsTest.cpp IndirectionUtilsTest.cpp GlobalMappingLayerTest.cpp LazyEmittingLayerTest.cpp + LegacyAPIInteropTest.cpp ObjectTransformLayerTest.cpp OrcCAPITest.cpp OrcTestCommon.cpp diff --git a/unittests/ExecutionEngine/Orc/CompileOnDemandLayerTest.cpp b/unittests/ExecutionEngine/Orc/CompileOnDemandLayerTest.cpp index 61ce310e6311..a1f864bae01f 100644 --- a/unittests/ExecutionEngine/Orc/CompileOnDemandLayerTest.cpp +++ b/unittests/ExecutionEngine/Orc/CompileOnDemandLayerTest.cpp @@ -18,7 +18,8 @@ namespace { class DummyCallbackManager : public orc::JITCompileCallbackManager { public: - DummyCallbackManager() : JITCompileCallbackManager(0) {} + DummyCallbackManager(ExecutionSession &ES) + : JITCompileCallbackManager(ES, 0) {} public: Error grow() override { llvm_unreachable("not implemented"); } @@ -35,11 +36,11 @@ public: llvm_unreachable("Not implemented"); } - JITSymbol findStub(StringRef Name, bool ExportedStubsOnly) override { + JITEvaluatedSymbol findStub(StringRef Name, bool ExportedStubsOnly) override { llvm_unreachable("Not implemented"); } - JITSymbol findPointer(StringRef Name) override { + JITEvaluatedSymbol findPointer(StringRef Name) override { llvm_unreachable("Not implemented"); } @@ -57,11 +58,23 @@ TEST(CompileOnDemandLayerTest, FindSymbol) { return JITSymbol(nullptr); }; - DummyCallbackManager CallbackMgr; + + ExecutionSession ES(std::make_shared<SymbolStringPool>()); + DummyCallbackManager CallbackMgr(ES); + + auto GetResolver = + [](orc::VModuleKey) -> std::shared_ptr<llvm::orc::SymbolResolver> { + llvm_unreachable("Should never be called"); + }; + + auto SetResolver = [](orc::VModuleKey, std::shared_ptr<orc::SymbolResolver>) { + llvm_unreachable("Should never be called"); + }; llvm::orc::CompileOnDemandLayer<decltype(TestBaseLayer)> COD( - TestBaseLayer, [](Function &F) { return std::set<Function *>{&F}; }, - CallbackMgr, [] { return llvm::make_unique<DummyStubsManager>(); }, true); + ES, TestBaseLayer, GetResolver, SetResolver, + [](Function &F) { return std::set<Function *>{&F}; }, CallbackMgr, + [] { return llvm::make_unique<DummyStubsManager>(); }, true); auto Sym = COD.findSymbol("foo", true); diff --git a/unittests/ExecutionEngine/Orc/CoreAPIsTest.cpp b/unittests/ExecutionEngine/Orc/CoreAPIsTest.cpp new file mode 100644 index 000000000000..baa1e3b5e8cb --- /dev/null +++ b/unittests/ExecutionEngine/Orc/CoreAPIsTest.cpp @@ -0,0 +1,744 @@ +//===----------- CoreAPIsTest.cpp - Unit tests for Core ORC APIs ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "OrcTestCommon.h" +#include "llvm/Config/llvm-config.h" +#include "llvm/ExecutionEngine/Orc/Core.h" +#include "llvm/ExecutionEngine/Orc/OrcError.h" + +#include <set> +#include <thread> + +using namespace llvm; +using namespace llvm::orc; + +class CoreAPIsStandardTest : public CoreAPIsBasedStandardTest {}; + +namespace { + +class SimpleMaterializationUnit : public MaterializationUnit { +public: + using MaterializeFunction = + std::function<void(MaterializationResponsibility)>; + using DiscardFunction = std::function<void(const VSO &, SymbolStringPtr)>; + using DestructorFunction = std::function<void()>; + + SimpleMaterializationUnit( + SymbolFlagsMap SymbolFlags, MaterializeFunction Materialize, + DiscardFunction Discard = DiscardFunction(), + DestructorFunction Destructor = DestructorFunction()) + : MaterializationUnit(std::move(SymbolFlags)), + Materialize(std::move(Materialize)), Discard(std::move(Discard)), + Destructor(std::move(Destructor)) {} + + ~SimpleMaterializationUnit() override { + if (Destructor) + Destructor(); + } + + void materialize(MaterializationResponsibility R) override { + Materialize(std::move(R)); + } + + void discard(const VSO &V, SymbolStringPtr Name) override { + if (Discard) + Discard(V, std::move(Name)); + else + llvm_unreachable("Discard not supported"); + } + +private: + MaterializeFunction Materialize; + DiscardFunction Discard; + DestructorFunction Destructor; +}; + +TEST_F(CoreAPIsStandardTest, BasicSuccessfulLookup) { + bool OnResolutionRun = false; + bool OnReadyRun = false; + + auto OnResolution = [&](Expected<SymbolMap> Result) { + EXPECT_TRUE(!!Result) << "Resolution unexpectedly returned error"; + auto &Resolved = *Result; + auto I = Resolved.find(Foo); + EXPECT_NE(I, Resolved.end()) << "Could not find symbol definition"; + EXPECT_EQ(I->second.getAddress(), FooAddr) + << "Resolution returned incorrect result"; + OnResolutionRun = true; + }; + auto OnReady = [&](Error Err) { + cantFail(std::move(Err)); + OnReadyRun = true; + }; + + std::shared_ptr<MaterializationResponsibility> FooMR; + + cantFail(V.define(llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, FooSym.getFlags()}}), + [&](MaterializationResponsibility R) { + FooMR = std::make_shared<MaterializationResponsibility>(std::move(R)); + }))); + + ES.lookup({&V}, {Foo}, OnResolution, OnReady, NoDependenciesToRegister); + + EXPECT_FALSE(OnResolutionRun) << "Should not have been resolved yet"; + EXPECT_FALSE(OnReadyRun) << "Should not have been marked ready yet"; + + FooMR->resolve({{Foo, FooSym}}); + + EXPECT_TRUE(OnResolutionRun) << "Should have been resolved"; + EXPECT_FALSE(OnReadyRun) << "Should not have been marked ready yet"; + + FooMR->finalize(); + + EXPECT_TRUE(OnReadyRun) << "Should have been marked ready"; +} + +TEST_F(CoreAPIsStandardTest, ExecutionSessionFailQuery) { + bool OnResolutionRun = false; + bool OnReadyRun = false; + + auto OnResolution = [&](Expected<SymbolMap> Result) { + EXPECT_FALSE(!!Result) << "Resolution unexpectedly returned success"; + auto Msg = toString(Result.takeError()); + EXPECT_EQ(Msg, "xyz") << "Resolution returned incorrect result"; + OnResolutionRun = true; + }; + auto OnReady = [&](Error Err) { + cantFail(std::move(Err)); + OnReadyRun = true; + }; + + AsynchronousSymbolQuery Q(SymbolNameSet({Foo}), OnResolution, OnReady); + + ES.legacyFailQuery(Q, + make_error<StringError>("xyz", inconvertibleErrorCode())); + + EXPECT_TRUE(OnResolutionRun) << "OnResolutionCallback was not run"; + EXPECT_FALSE(OnReadyRun) << "OnReady unexpectedly run"; +} + +TEST_F(CoreAPIsStandardTest, EmptyLookup) { + bool OnResolvedRun = false; + bool OnReadyRun = false; + + auto OnResolution = [&](Expected<SymbolMap> Result) { + cantFail(std::move(Result)); + OnResolvedRun = true; + }; + + auto OnReady = [&](Error Err) { + cantFail(std::move(Err)); + OnReadyRun = true; + }; + + ES.lookup({&V}, {}, OnResolution, OnReady, NoDependenciesToRegister); + + EXPECT_TRUE(OnResolvedRun) << "OnResolved was not run for empty query"; + EXPECT_TRUE(OnReadyRun) << "OnReady was not run for empty query"; +} + +TEST_F(CoreAPIsStandardTest, ChainedVSOLookup) { + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + + auto &V2 = ES.createVSO("V2"); + + bool OnResolvedRun = false; + bool OnReadyRun = false; + + auto Q = std::make_shared<AsynchronousSymbolQuery>( + SymbolNameSet({Foo}), + [&](Expected<SymbolMap> Result) { + cantFail(std::move(Result)); + OnResolvedRun = true; + }, + [&](Error Err) { + cantFail(std::move(Err)); + OnReadyRun = true; + }); + + V2.legacyLookup(Q, V.legacyLookup(Q, {Foo})); + + EXPECT_TRUE(OnResolvedRun) << "OnResolved was not run for empty query"; + EXPECT_TRUE(OnReadyRun) << "OnReady was not run for empty query"; +} + +TEST_F(CoreAPIsStandardTest, LookupFlagsTest) { + // Test that lookupFlags works on a predefined symbol, and does not trigger + // materialization of a lazy symbol. Make the lazy symbol weak to test that + // the weak flag is propagated correctly. + + BarSym.setFlags(static_cast<JITSymbolFlags::FlagNames>( + JITSymbolFlags::Exported | JITSymbolFlags::Weak)); + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Bar, BarSym.getFlags()}}), + [](MaterializationResponsibility R) { + llvm_unreachable("Symbol materialized on flags lookup"); + }); + + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + cantFail(V.define(std::move(MU))); + + SymbolNameSet Names({Foo, Bar, Baz}); + + auto SymbolFlags = V.lookupFlags(Names); + + EXPECT_EQ(SymbolFlags.size(), 2U) + << "Returned symbol flags contains unexpected results"; + EXPECT_EQ(SymbolFlags.count(Foo), 1U) << "Missing lookupFlags result for Foo"; + EXPECT_EQ(SymbolFlags[Foo], FooSym.getFlags()) + << "Incorrect flags returned for Foo"; + EXPECT_EQ(SymbolFlags.count(Bar), 1U) + << "Missing lookupFlags result for Bar"; + EXPECT_EQ(SymbolFlags[Bar], BarSym.getFlags()) + << "Incorrect flags returned for Bar"; +} + +TEST_F(CoreAPIsStandardTest, TestBasicAliases) { + cantFail(V.define(absoluteSymbols({{Foo, FooSym}, {Bar, BarSym}}))); + cantFail(V.define(symbolAliases({{Baz, {Foo, JITSymbolFlags::Exported}}, + {Qux, {Bar, JITSymbolFlags::Weak}}}))); + cantFail(V.define(absoluteSymbols({{Qux, QuxSym}}))); + + auto Result = lookup({&V}, {Baz, Qux}); + EXPECT_TRUE(!!Result) << "Unexpected lookup failure"; + EXPECT_EQ(Result->count(Baz), 1U) << "No result for \"baz\""; + EXPECT_EQ(Result->count(Qux), 1U) << "No result for \"qux\""; + EXPECT_EQ((*Result)[Baz].getAddress(), FooSym.getAddress()) + << "\"Baz\"'s address should match \"Foo\"'s"; + EXPECT_EQ((*Result)[Qux].getAddress(), QuxSym.getAddress()) + << "The \"Qux\" alias should have been overriden"; +} + +TEST_F(CoreAPIsStandardTest, TestChainedAliases) { + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + cantFail(V.define(symbolAliases( + {{Baz, {Bar, BazSym.getFlags()}}, {Bar, {Foo, BarSym.getFlags()}}}))); + + auto Result = lookup({&V}, {Bar, Baz}); + EXPECT_TRUE(!!Result) << "Unexpected lookup failure"; + EXPECT_EQ(Result->count(Bar), 1U) << "No result for \"bar\""; + EXPECT_EQ(Result->count(Baz), 1U) << "No result for \"baz\""; + EXPECT_EQ((*Result)[Bar].getAddress(), FooSym.getAddress()) + << "\"Bar\"'s address should match \"Foo\"'s"; + EXPECT_EQ((*Result)[Baz].getAddress(), FooSym.getAddress()) + << "\"Baz\"'s address should match \"Foo\"'s"; +} + +TEST_F(CoreAPIsStandardTest, TestBasicReExports) { + // Test that the basic use case of re-exporting a single symbol from another + // VSO works. + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + + auto &V2 = ES.createVSO("V2"); + + cantFail(V2.define(reexports(V, {{Bar, {Foo, BarSym.getFlags()}}}))); + + auto Result = cantFail(lookup({&V2}, Bar)); + EXPECT_EQ(Result.getAddress(), FooSym.getAddress()) + << "Re-export Bar for symbol Foo should match FooSym's address"; +} + +TEST_F(CoreAPIsStandardTest, TestThatReExportsDontUnnecessarilyMaterialize) { + // Test that re-exports do not materialize symbols that have not been queried + // for. + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + + bool BarMaterialized = false; + auto BarMU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Bar, BarSym.getFlags()}}), + [&](MaterializationResponsibility R) { + BarMaterialized = true; + R.resolve({{Bar, BarSym}}); + R.finalize(); + }); + + cantFail(V.define(BarMU)); + + auto &V2 = ES.createVSO("V2"); + + cantFail(V2.define(reexports( + V, {{Baz, {Foo, BazSym.getFlags()}}, {Qux, {Bar, QuxSym.getFlags()}}}))); + + auto Result = cantFail(lookup({&V2}, Baz)); + EXPECT_EQ(Result.getAddress(), FooSym.getAddress()) + << "Re-export Baz for symbol Foo should match FooSym's address"; + + EXPECT_FALSE(BarMaterialized) << "Bar should not have been materialized"; +} + +TEST_F(CoreAPIsStandardTest, TestTrivialCircularDependency) { + Optional<MaterializationResponsibility> FooR; + auto FooMU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, FooSym.getFlags()}}), + [&](MaterializationResponsibility R) { FooR.emplace(std::move(R)); }); + + cantFail(V.define(FooMU)); + + bool FooReady = false; + auto OnResolution = [](Expected<SymbolMap> R) { cantFail(std::move(R)); }; + auto OnReady = [&](Error Err) { + cantFail(std::move(Err)); + FooReady = true; + }; + + ES.lookup({&V}, {Foo}, std::move(OnResolution), std::move(OnReady), + NoDependenciesToRegister); + + FooR->resolve({{Foo, FooSym}}); + FooR->finalize(); + + EXPECT_TRUE(FooReady) + << "Self-dependency prevented symbol from being marked ready"; +} + +TEST_F(CoreAPIsStandardTest, TestCircularDependenceInOneVSO) { + // Test that a circular symbol dependency between three symbols in a VSO does + // not prevent any symbol from becoming 'ready' once all symbols are + // finalized. + + // Create three MaterializationResponsibility objects: one for each of Foo, + // Bar and Baz. These are optional because MaterializationResponsibility + // does not have a default constructor). + Optional<MaterializationResponsibility> FooR; + Optional<MaterializationResponsibility> BarR; + Optional<MaterializationResponsibility> BazR; + + // Create a MaterializationUnit for each symbol that moves the + // MaterializationResponsibility into one of the locals above. + auto FooMU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, FooSym.getFlags()}}), + [&](MaterializationResponsibility R) { FooR.emplace(std::move(R)); }); + + auto BarMU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Bar, BarSym.getFlags()}}), + [&](MaterializationResponsibility R) { BarR.emplace(std::move(R)); }); + + auto BazMU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Baz, BazSym.getFlags()}}), + [&](MaterializationResponsibility R) { BazR.emplace(std::move(R)); }); + + // Define the symbols. + cantFail(V.define(FooMU)); + cantFail(V.define(BarMU)); + cantFail(V.define(BazMU)); + + // Query each of the symbols to trigger materialization. + bool FooResolved = false; + bool FooReady = false; + + auto OnFooResolution = [&](Expected<SymbolMap> Result) { + cantFail(std::move(Result)); + FooResolved = true; + }; + + auto OnFooReady = [&](Error Err) { + cantFail(std::move(Err)); + FooReady = true; + }; + + // Issue a lookup for Foo. Use NoDependenciesToRegister: We're going to add + // the dependencies manually below. + ES.lookup({&V}, {Foo}, std::move(OnFooResolution), std::move(OnFooReady), + NoDependenciesToRegister); + + bool BarResolved = false; + bool BarReady = false; + auto OnBarResolution = [&](Expected<SymbolMap> Result) { + cantFail(std::move(Result)); + BarResolved = true; + }; + + auto OnBarReady = [&](Error Err) { + cantFail(std::move(Err)); + BarReady = true; + }; + + ES.lookup({&V}, {Bar}, std::move(OnBarResolution), std::move(OnBarReady), + NoDependenciesToRegister); + + bool BazResolved = false; + bool BazReady = false; + + auto OnBazResolution = [&](Expected<SymbolMap> Result) { + cantFail(std::move(Result)); + BazResolved = true; + }; + + auto OnBazReady = [&](Error Err) { + cantFail(std::move(Err)); + BazReady = true; + }; + + ES.lookup({&V}, {Baz}, std::move(OnBazResolution), std::move(OnBazReady), + NoDependenciesToRegister); + + // Add a circular dependency: Foo -> Bar, Bar -> Baz, Baz -> Foo. + FooR->addDependenciesForAll({{&V, SymbolNameSet({Bar})}}); + BarR->addDependenciesForAll({{&V, SymbolNameSet({Baz})}}); + BazR->addDependenciesForAll({{&V, SymbolNameSet({Foo})}}); + + // Add self-dependencies for good measure. This tests that the implementation + // of addDependencies filters these out. + FooR->addDependenciesForAll({{&V, SymbolNameSet({Foo})}}); + BarR->addDependenciesForAll({{&V, SymbolNameSet({Bar})}}); + BazR->addDependenciesForAll({{&V, SymbolNameSet({Baz})}}); + + // Check that nothing has been resolved yet. + EXPECT_FALSE(FooResolved) << "\"Foo\" should not be resolved yet"; + EXPECT_FALSE(BarResolved) << "\"Bar\" should not be resolved yet"; + EXPECT_FALSE(BazResolved) << "\"Baz\" should not be resolved yet"; + + // Resolve the symbols (but do not finalized them). + FooR->resolve({{Foo, FooSym}}); + BarR->resolve({{Bar, BarSym}}); + BazR->resolve({{Baz, BazSym}}); + + // Verify that the symbols have been resolved, but are not ready yet. + EXPECT_TRUE(FooResolved) << "\"Foo\" should be resolved now"; + EXPECT_TRUE(BarResolved) << "\"Bar\" should be resolved now"; + EXPECT_TRUE(BazResolved) << "\"Baz\" should be resolved now"; + + EXPECT_FALSE(FooReady) << "\"Foo\" should not be ready yet"; + EXPECT_FALSE(BarReady) << "\"Bar\" should not be ready yet"; + EXPECT_FALSE(BazReady) << "\"Baz\" should not be ready yet"; + + // Finalize two of the symbols. + FooR->finalize(); + BarR->finalize(); + + // Verify that nothing is ready until the circular dependence is resolved. + EXPECT_FALSE(FooReady) << "\"Foo\" still should not be ready"; + EXPECT_FALSE(BarReady) << "\"Bar\" still should not be ready"; + EXPECT_FALSE(BazReady) << "\"Baz\" still should not be ready"; + + // Finalize the last symbol. + BazR->finalize(); + + // Verify that everything becomes ready once the circular dependence resolved. + EXPECT_TRUE(FooReady) << "\"Foo\" should be ready now"; + EXPECT_TRUE(BarReady) << "\"Bar\" should be ready now"; + EXPECT_TRUE(BazReady) << "\"Baz\" should be ready now"; +} + +TEST_F(CoreAPIsStandardTest, DropMaterializerWhenEmpty) { + bool DestructorRun = false; + + JITSymbolFlags WeakExported(JITSymbolFlags::Exported); + WeakExported |= JITSymbolFlags::Weak; + + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, WeakExported}, {Bar, WeakExported}}), + [](MaterializationResponsibility R) { + llvm_unreachable("Unexpected call to materialize"); + }, + [&](const VSO &V, SymbolStringPtr Name) { + EXPECT_TRUE(Name == Foo || Name == Bar) + << "Discard of unexpected symbol?"; + }, + [&]() { DestructorRun = true; }); + + cantFail(V.define(MU)); + + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + + EXPECT_FALSE(DestructorRun) + << "MaterializationUnit should not have been destroyed yet"; + + cantFail(V.define(absoluteSymbols({{Bar, BarSym}}))); + + EXPECT_TRUE(DestructorRun) + << "MaterializationUnit should have been destroyed"; +} + +TEST_F(CoreAPIsStandardTest, AddAndMaterializeLazySymbol) { + bool FooMaterialized = false; + bool BarDiscarded = false; + + JITSymbolFlags WeakExported(JITSymbolFlags::Exported); + WeakExported |= JITSymbolFlags::Weak; + + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, JITSymbolFlags::Exported}, {Bar, WeakExported}}), + [&](MaterializationResponsibility R) { + assert(BarDiscarded && "Bar should have been discarded by this point"); + R.resolve(SymbolMap({{Foo, FooSym}})); + R.finalize(); + FooMaterialized = true; + }, + [&](const VSO &V, SymbolStringPtr Name) { + EXPECT_EQ(Name, Bar) << "Expected Name to be Bar"; + BarDiscarded = true; + }); + + cantFail(V.define(MU)); + cantFail(V.define(absoluteSymbols({{Bar, BarSym}}))); + + SymbolNameSet Names({Foo}); + + bool OnResolutionRun = false; + bool OnReadyRun = false; + + auto OnResolution = [&](Expected<SymbolMap> Result) { + EXPECT_TRUE(!!Result) << "Resolution unexpectedly returned error"; + auto I = Result->find(Foo); + EXPECT_NE(I, Result->end()) << "Could not find symbol definition"; + EXPECT_EQ(I->second.getAddress(), FooSym.getAddress()) + << "Resolution returned incorrect result"; + OnResolutionRun = true; + }; + + auto OnReady = [&](Error Err) { + cantFail(std::move(Err)); + OnReadyRun = true; + }; + + ES.lookup({&V}, Names, std::move(OnResolution), std::move(OnReady), + NoDependenciesToRegister); + + EXPECT_TRUE(FooMaterialized) << "Foo was not materialized"; + EXPECT_TRUE(BarDiscarded) << "Bar was not discarded"; + EXPECT_TRUE(OnResolutionRun) << "OnResolutionCallback was not run"; + EXPECT_TRUE(OnReadyRun) << "OnReady was not run"; +} + +TEST_F(CoreAPIsStandardTest, DefineMaterializingSymbol) { + bool ExpectNoMoreMaterialization = false; + ES.setDispatchMaterialization( + [&](VSO &V, std::unique_ptr<MaterializationUnit> MU) { + if (ExpectNoMoreMaterialization) + ADD_FAILURE() << "Unexpected materialization"; + MU->doMaterialize(V); + }); + + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, FooSym.getFlags()}}), + [&](MaterializationResponsibility R) { + cantFail( + R.defineMaterializing(SymbolFlagsMap({{Bar, BarSym.getFlags()}}))); + R.resolve(SymbolMap({{Foo, FooSym}, {Bar, BarSym}})); + R.finalize(); + }); + + cantFail(V.define(MU)); + cantFail(lookup({&V}, Foo)); + + // Assert that materialization is complete by now. + ExpectNoMoreMaterialization = true; + + // Look up bar to verify that no further materialization happens. + auto BarResult = cantFail(lookup({&V}, Bar)); + EXPECT_EQ(BarResult.getAddress(), BarSym.getAddress()) + << "Expected Bar == BarSym"; +} + +TEST_F(CoreAPIsStandardTest, FallbackDefinitionGeneratorTest) { + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + + V.setFallbackDefinitionGenerator([&](VSO &W, const SymbolNameSet &Names) { + cantFail(W.define(absoluteSymbols({{Bar, BarSym}}))); + return SymbolNameSet({Bar}); + }); + + auto Result = cantFail(lookup({&V}, {Foo, Bar})); + + EXPECT_EQ(Result.count(Bar), 1U) << "Expected to find fallback def for 'bar'"; + EXPECT_EQ(Result[Bar].getAddress(), BarSym.getAddress()) + << "Expected fallback def for Bar to be equal to BarSym"; +} + +TEST_F(CoreAPIsStandardTest, FailResolution) { + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap( + {{Foo, JITSymbolFlags::Weak}, {Bar, JITSymbolFlags::Weak}}), + [&](MaterializationResponsibility R) { R.failMaterialization(); }); + + cantFail(V.define(MU)); + + SymbolNameSet Names({Foo, Bar}); + auto Result = lookup({&V}, Names); + + EXPECT_FALSE(!!Result) << "Expected failure"; + if (!Result) { + handleAllErrors(Result.takeError(), + [&](FailedToMaterialize &F) { + EXPECT_EQ(F.getSymbols(), Names) + << "Expected to fail on symbols in Names"; + }, + [](ErrorInfoBase &EIB) { + std::string ErrMsg; + { + raw_string_ostream ErrOut(ErrMsg); + EIB.log(ErrOut); + } + ADD_FAILURE() + << "Expected a FailedToResolve error. Got:\n" + << ErrMsg; + }); + } +} + +TEST_F(CoreAPIsStandardTest, TestLookupWithUnthreadedMaterialization) { + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, JITSymbolFlags::Exported}}), + [&](MaterializationResponsibility R) { + R.resolve({{Foo, FooSym}}); + R.finalize(); + }); + + cantFail(V.define(MU)); + + auto FooLookupResult = cantFail(lookup({&V}, Foo)); + + EXPECT_EQ(FooLookupResult.getAddress(), FooSym.getAddress()) + << "lookup returned an incorrect address"; + EXPECT_EQ(FooLookupResult.getFlags(), FooSym.getFlags()) + << "lookup returned incorrect flags"; +} + +TEST_F(CoreAPIsStandardTest, TestLookupWithThreadedMaterialization) { +#if LLVM_ENABLE_THREADS + + std::thread MaterializationThread; + ES.setDispatchMaterialization( + [&](VSO &V, std::unique_ptr<MaterializationUnit> MU) { + auto SharedMU = std::shared_ptr<MaterializationUnit>(std::move(MU)); + MaterializationThread = + std::thread([SharedMU, &V]() { SharedMU->doMaterialize(V); }); + }); + + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + + auto FooLookupResult = cantFail(lookup({&V}, Foo)); + + EXPECT_EQ(FooLookupResult.getAddress(), FooSym.getAddress()) + << "lookup returned an incorrect address"; + EXPECT_EQ(FooLookupResult.getFlags(), FooSym.getFlags()) + << "lookup returned incorrect flags"; + MaterializationThread.join(); +#endif +} + +TEST_F(CoreAPIsStandardTest, TestGetRequestedSymbolsAndReplace) { + // Test that GetRequestedSymbols returns the set of symbols that currently + // have pending queries, and test that MaterializationResponsibility's + // replace method can be used to return definitions to the VSO in a new + // MaterializationUnit. + SymbolNameSet Names({Foo, Bar}); + + bool FooMaterialized = false; + bool BarMaterialized = false; + + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, FooSym.getFlags()}, {Bar, BarSym.getFlags()}}), + [&](MaterializationResponsibility R) { + auto Requested = R.getRequestedSymbols(); + EXPECT_EQ(Requested.size(), 1U) << "Expected one symbol requested"; + EXPECT_EQ(*Requested.begin(), Foo) << "Expected \"Foo\" requested"; + + auto NewMU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Bar, BarSym.getFlags()}}), + [&](MaterializationResponsibility R2) { + R2.resolve(SymbolMap({{Bar, BarSym}})); + R2.finalize(); + BarMaterialized = true; + }); + + R.replace(std::move(NewMU)); + + R.resolve(SymbolMap({{Foo, FooSym}})); + R.finalize(); + + FooMaterialized = true; + }); + + cantFail(V.define(MU)); + + EXPECT_FALSE(FooMaterialized) << "Foo should not be materialized yet"; + EXPECT_FALSE(BarMaterialized) << "Bar should not be materialized yet"; + + auto FooSymResult = cantFail(lookup({&V}, Foo)); + EXPECT_EQ(FooSymResult.getAddress(), FooSym.getAddress()) + << "Address mismatch for Foo"; + + EXPECT_TRUE(FooMaterialized) << "Foo should be materialized now"; + EXPECT_FALSE(BarMaterialized) << "Bar still should not be materialized"; + + auto BarSymResult = cantFail(lookup({&V}, Bar)); + EXPECT_EQ(BarSymResult.getAddress(), BarSym.getAddress()) + << "Address mismatch for Bar"; + EXPECT_TRUE(BarMaterialized) << "Bar should be materialized now"; +} + +TEST_F(CoreAPIsStandardTest, TestMaterializationResponsibilityDelegation) { + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, FooSym.getFlags()}, {Bar, BarSym.getFlags()}}), + [&](MaterializationResponsibility R) { + auto R2 = R.delegate({Bar}); + + R.resolve({{Foo, FooSym}}); + R.finalize(); + R2.resolve({{Bar, BarSym}}); + R2.finalize(); + }); + + cantFail(V.define(MU)); + + auto Result = lookup({&V}, {Foo, Bar}); + + EXPECT_TRUE(!!Result) << "Result should be a success value"; + EXPECT_EQ(Result->count(Foo), 1U) << "\"Foo\" entry missing"; + EXPECT_EQ(Result->count(Bar), 1U) << "\"Bar\" entry missing"; + EXPECT_EQ((*Result)[Foo].getAddress(), FooSym.getAddress()) + << "Address mismatch for \"Foo\""; + EXPECT_EQ((*Result)[Bar].getAddress(), BarSym.getAddress()) + << "Address mismatch for \"Bar\""; +} + +TEST_F(CoreAPIsStandardTest, TestMaterializeWeakSymbol) { + // Confirm that once a weak definition is selected for materialization it is + // treated as strong. + JITSymbolFlags WeakExported = JITSymbolFlags::Exported; + WeakExported &= JITSymbolFlags::Weak; + + std::unique_ptr<MaterializationResponsibility> FooResponsibility; + auto MU = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, FooSym.getFlags()}}), + [&](MaterializationResponsibility R) { + FooResponsibility = + llvm::make_unique<MaterializationResponsibility>(std::move(R)); + }); + + cantFail(V.define(MU)); + auto OnResolution = [](Expected<SymbolMap> Result) { + cantFail(std::move(Result)); + }; + + auto OnReady = [](Error Err) { cantFail(std::move(Err)); }; + + ES.lookup({&V}, {Foo}, std::move(OnResolution), std::move(OnReady), + NoDependenciesToRegister); + + auto MU2 = llvm::make_unique<SimpleMaterializationUnit>( + SymbolFlagsMap({{Foo, JITSymbolFlags::Exported}}), + [](MaterializationResponsibility R) { + llvm_unreachable("This unit should never be materialized"); + }); + + auto Err = V.define(MU2); + EXPECT_TRUE(!!Err) << "Expected failure value"; + EXPECT_TRUE(Err.isA<DuplicateDefinition>()) + << "Expected a duplicate definition error"; + consumeError(std::move(Err)); + + FooResponsibility->resolve(SymbolMap({{Foo, FooSym}})); + FooResponsibility->finalize(); +} + +} // namespace diff --git a/unittests/ExecutionEngine/Orc/LazyEmittingLayerTest.cpp b/unittests/ExecutionEngine/Orc/LazyEmittingLayerTest.cpp index 0dba66d47535..3801fa918db7 100644 --- a/unittests/ExecutionEngine/Orc/LazyEmittingLayerTest.cpp +++ b/unittests/ExecutionEngine/Orc/LazyEmittingLayerTest.cpp @@ -15,11 +15,8 @@ namespace { struct MockBaseLayer { typedef int ModuleHandleT; - ModuleHandleT addModule( - std::shared_ptr<llvm::Module>, - std::unique_ptr<llvm::RuntimeDyld::MemoryManager> MemMgr, - std::unique_ptr<llvm::JITSymbolResolver> Resolver) { - EXPECT_FALSE(MemMgr); + ModuleHandleT addModule(llvm::orc::VModuleKey, + std::shared_ptr<llvm::Module>) { return 42; } }; @@ -27,7 +24,8 @@ struct MockBaseLayer { TEST(LazyEmittingLayerTest, Empty) { MockBaseLayer M; llvm::orc::LazyEmittingLayer<MockBaseLayer> L(M); - cantFail(L.addModule(std::unique_ptr<llvm::Module>(), nullptr)); + cantFail( + L.addModule(llvm::orc::VModuleKey(), std::unique_ptr<llvm::Module>())); } } diff --git a/unittests/ExecutionEngine/Orc/LegacyAPIInteropTest.cpp b/unittests/ExecutionEngine/Orc/LegacyAPIInteropTest.cpp new file mode 100644 index 000000000000..746ae1dca490 --- /dev/null +++ b/unittests/ExecutionEngine/Orc/LegacyAPIInteropTest.cpp @@ -0,0 +1,183 @@ +//===----------- CoreAPIsTest.cpp - Unit tests for Core ORC APIs ----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "OrcTestCommon.h" +#include "llvm/ExecutionEngine/Orc/Legacy.h" +#include "gtest/gtest.h" + +using namespace llvm; +using namespace llvm::orc; + +class LegacyAPIsStandardTest : public CoreAPIsBasedStandardTest {}; + +namespace { + +TEST_F(LegacyAPIsStandardTest, TestLambdaSymbolResolver) { + cantFail(V.define(absoluteSymbols({{Foo, FooSym}, {Bar, BarSym}}))); + + auto Resolver = createSymbolResolver( + [&](const SymbolNameSet &Symbols) { return V.lookupFlags(Symbols); }, + [&](std::shared_ptr<AsynchronousSymbolQuery> Q, SymbolNameSet Symbols) { + return V.legacyLookup(std::move(Q), Symbols); + }); + + SymbolNameSet Symbols({Foo, Bar, Baz}); + + SymbolFlagsMap SymbolFlags = Resolver->lookupFlags(Symbols); + + EXPECT_EQ(SymbolFlags.size(), 2U) + << "lookupFlags returned the wrong number of results"; + EXPECT_EQ(SymbolFlags.count(Foo), 1U) << "Missing lookupFlags result for foo"; + EXPECT_EQ(SymbolFlags.count(Bar), 1U) << "Missing lookupFlags result for bar"; + EXPECT_EQ(SymbolFlags[Foo], FooSym.getFlags()) + << "Incorrect lookupFlags result for Foo"; + EXPECT_EQ(SymbolFlags[Bar], BarSym.getFlags()) + << "Incorrect lookupFlags result for Bar"; + + bool OnResolvedRun = false; + + auto OnResolved = [&](Expected<SymbolMap> Result) { + OnResolvedRun = true; + EXPECT_TRUE(!!Result) << "Unexpected error"; + EXPECT_EQ(Result->size(), 2U) << "Unexpected number of resolved symbols"; + EXPECT_EQ(Result->count(Foo), 1U) << "Missing lookup result for foo"; + EXPECT_EQ(Result->count(Bar), 1U) << "Missing lookup result for bar"; + EXPECT_EQ((*Result)[Foo].getAddress(), FooSym.getAddress()) + << "Incorrect address for foo"; + EXPECT_EQ((*Result)[Bar].getAddress(), BarSym.getAddress()) + << "Incorrect address for bar"; + }; + auto OnReady = [&](Error Err) { + EXPECT_FALSE(!!Err) << "Finalization should never fail in this test"; + }; + + auto Q = std::make_shared<AsynchronousSymbolQuery>(SymbolNameSet({Foo, Bar}), + OnResolved, OnReady); + auto Unresolved = Resolver->lookup(std::move(Q), Symbols); + + EXPECT_EQ(Unresolved.size(), 1U) << "Expected one unresolved symbol"; + EXPECT_EQ(Unresolved.count(Baz), 1U) << "Expected baz to not be resolved"; + EXPECT_TRUE(OnResolvedRun) << "OnResolved was never run"; +} + +TEST(LegacyAPIInteropTest, QueryAgainstVSO) { + + ExecutionSession ES(std::make_shared<SymbolStringPool>()); + auto Foo = ES.getSymbolStringPool().intern("foo"); + + auto &V = ES.createVSO("V"); + JITEvaluatedSymbol FooSym(0xdeadbeef, JITSymbolFlags::Exported); + cantFail(V.define(absoluteSymbols({{Foo, FooSym}}))); + + auto LookupFlags = [&](const SymbolNameSet &Names) { + return V.lookupFlags(Names); + }; + + auto Lookup = [&](std::shared_ptr<AsynchronousSymbolQuery> Query, + SymbolNameSet Symbols) { + return V.legacyLookup(std::move(Query), Symbols); + }; + + auto UnderlyingResolver = + createSymbolResolver(std::move(LookupFlags), std::move(Lookup)); + JITSymbolResolverAdapter Resolver(ES, *UnderlyingResolver, nullptr); + + JITSymbolResolver::LookupSet Names{StringRef("foo")}; + + auto LFR = Resolver.lookupFlags(Names); + EXPECT_TRUE(!!LFR) << "lookupFlags failed"; + EXPECT_EQ(LFR->size(), 1U) + << "lookupFlags returned the wrong number of results"; + EXPECT_EQ(LFR->count(*Foo), 1U) + << "lookupFlags did not contain a result for 'foo'"; + EXPECT_EQ((*LFR)[*Foo], FooSym.getFlags()) + << "lookupFlags contained the wrong result for 'foo'"; + + auto LR = Resolver.lookup(Names); + EXPECT_TRUE(!!LR) << "lookup failed"; + EXPECT_EQ(LR->size(), 1U) << "lookup returned the wrong number of results"; + EXPECT_EQ(LR->count(*Foo), 1U) << "lookup did not contain a result for 'foo'"; + EXPECT_EQ((*LR)[*Foo].getFlags(), FooSym.getFlags()) + << "lookup returned the wrong result for flags of 'foo'"; + EXPECT_EQ((*LR)[*Foo].getAddress(), FooSym.getAddress()) + << "lookup returned the wrong result for address of 'foo'"; +} + +TEST(LegacyAPIInteropTset, LegacyLookupHelpersFn) { + constexpr JITTargetAddress FooAddr = 0xdeadbeef; + JITSymbolFlags FooFlags = JITSymbolFlags::Exported; + + bool BarMaterialized = false; + constexpr JITTargetAddress BarAddr = 0xcafef00d; + JITSymbolFlags BarFlags = static_cast<JITSymbolFlags::FlagNames>( + JITSymbolFlags::Exported | JITSymbolFlags::Weak); + + auto LegacyLookup = [&](const std::string &Name) -> JITSymbol { + if (Name == "foo") + return {FooAddr, FooFlags}; + + if (Name == "bar") { + auto BarMaterializer = [&]() -> Expected<JITTargetAddress> { + BarMaterialized = true; + return BarAddr; + }; + + return {BarMaterializer, BarFlags}; + } + + return nullptr; + }; + + ExecutionSession ES; + auto Foo = ES.getSymbolStringPool().intern("foo"); + auto Bar = ES.getSymbolStringPool().intern("bar"); + auto Baz = ES.getSymbolStringPool().intern("baz"); + + SymbolNameSet Symbols({Foo, Bar, Baz}); + + auto SymbolFlags = lookupFlagsWithLegacyFn(Symbols, LegacyLookup); + + EXPECT_TRUE(!!SymbolFlags) << "Expected lookupFlagsWithLegacyFn to succeed"; + EXPECT_EQ(SymbolFlags->size(), 2U) << "Wrong number of flags returned"; + EXPECT_EQ(SymbolFlags->count(Foo), 1U) << "Flags for foo missing"; + EXPECT_EQ(SymbolFlags->count(Bar), 1U) << "Flags for foo missing"; + EXPECT_EQ((*SymbolFlags)[Foo], FooFlags) << "Wrong flags for foo"; + EXPECT_EQ((*SymbolFlags)[Bar], BarFlags) << "Wrong flags for foo"; + EXPECT_FALSE(BarMaterialized) + << "lookupFlags should not have materialized bar"; + + bool OnResolvedRun = false; + bool OnReadyRun = false; + auto OnResolved = [&](Expected<SymbolMap> Result) { + OnResolvedRun = true; + EXPECT_TRUE(!!Result) << "lookuWithLegacy failed to resolve"; + + EXPECT_EQ(Result->size(), 2U) << "Wrong number of symbols resolved"; + EXPECT_EQ(Result->count(Foo), 1U) << "Result for foo missing"; + EXPECT_EQ(Result->count(Bar), 1U) << "Result for bar missing"; + EXPECT_EQ((*Result)[Foo].getAddress(), FooAddr) << "Wrong address for foo"; + EXPECT_EQ((*Result)[Foo].getFlags(), FooFlags) << "Wrong flags for foo"; + EXPECT_EQ((*Result)[Bar].getAddress(), BarAddr) << "Wrong address for bar"; + EXPECT_EQ((*Result)[Bar].getFlags(), BarFlags) << "Wrong flags for bar"; + }; + auto OnReady = [&](Error Err) { + EXPECT_FALSE(!!Err) << "Finalization unexpectedly failed"; + OnReadyRun = true; + }; + + AsynchronousSymbolQuery Q({Foo, Bar}, OnResolved, OnReady); + auto Unresolved = lookupWithLegacyFn(ES, Q, Symbols, LegacyLookup); + + EXPECT_TRUE(OnResolvedRun) << "OnResolved was not run"; + EXPECT_TRUE(OnReadyRun) << "OnReady was not run"; + EXPECT_EQ(Unresolved.size(), 1U) << "Expected one unresolved symbol"; + EXPECT_EQ(Unresolved.count(Baz), 1U) << "Expected baz to be unresolved"; +} + +} // namespace diff --git a/unittests/ExecutionEngine/Orc/ObjectTransformLayerTest.cpp b/unittests/ExecutionEngine/Orc/ObjectTransformLayerTest.cpp index 7cd6443b5d4a..6ad3c19ada95 100644 --- a/unittests/ExecutionEngine/Orc/ObjectTransformLayerTest.cpp +++ b/unittests/ExecutionEngine/Orc/ObjectTransformLayerTest.cpp @@ -14,6 +14,7 @@ #include "llvm/ExecutionEngine/Orc/NullResolver.h" #include "llvm/ExecutionEngine/Orc/RTDyldObjectLinkingLayer.h" #include "llvm/ExecutionEngine/SectionMemoryManager.h" +#include "llvm/IR/Module.h" #include "llvm/Object/ObjectFile.h" #include "gtest/gtest.h" @@ -44,42 +45,32 @@ struct AllocatingTransform { // transform layer called the base layer and forwarded any return value. class MockBaseLayer { public: - typedef int ObjHandleT; - MockBaseLayer() : MockSymbol(nullptr) { resetExpectations(); } - template <typename ObjPtrT> - llvm::Expected<ObjHandleT> - addObject(ObjPtrT Obj, - std::shared_ptr<llvm::JITSymbolResolver> Resolver) { - EXPECT_EQ(MockResolver, Resolver) << "Resolver should pass through"; + template <typename ObjPtrT> llvm::Error addObject(VModuleKey K, ObjPtrT Obj) { + EXPECT_EQ(MockKey, K) << "Key should pass through"; EXPECT_EQ(MockObject + 1, *Obj) << "Transform should be applied"; LastCalled = "addObject"; - MockObjHandle = 111; - return MockObjHandle; + return llvm::Error::success(); } - template <typename ObjPtrT> - void expectAddObject(ObjPtrT Obj, - std::shared_ptr<llvm::JITSymbolResolver> Resolver) { - MockResolver = Resolver; + template <typename ObjPtrT> void expectAddObject(VModuleKey K, ObjPtrT Obj) { + MockKey = K; MockObject = *Obj; } - - void verifyAddObject(ObjHandleT Returned) { + void verifyAddObject() { EXPECT_EQ("addObject", LastCalled); - EXPECT_EQ(MockObjHandle, Returned) << "Return should pass through"; resetExpectations(); } - llvm::Error removeObject(ObjHandleT H) { - EXPECT_EQ(MockObjHandle, H); + llvm::Error removeObject(VModuleKey K) { + EXPECT_EQ(MockKey, K); LastCalled = "removeObject"; return llvm::Error::success(); } - void expectRemoveObject(ObjHandleT H) { MockObjHandle = H; } + void expectRemoveObject(VModuleKey K) { MockKey = K; } void verifyRemoveObject() { EXPECT_EQ("removeObject", LastCalled); resetExpectations(); @@ -105,18 +96,18 @@ public: resetExpectations(); } - llvm::JITSymbol findSymbolIn(ObjHandleT H, const std::string &Name, + llvm::JITSymbol findSymbolIn(VModuleKey K, const std::string &Name, bool ExportedSymbolsOnly) { - EXPECT_EQ(MockObjHandle, H) << "Handle should pass through"; + EXPECT_EQ(MockKey, K) << "VModuleKey should pass through"; EXPECT_EQ(MockName, Name) << "Name should pass through"; EXPECT_EQ(MockBool, ExportedSymbolsOnly) << "Flag should pass through"; LastCalled = "findSymbolIn"; MockSymbol = llvm::JITSymbol(122, llvm::JITSymbolFlags::None); return llvm::JITSymbol(122, llvm::JITSymbolFlags::None); } - void expectFindSymbolIn(ObjHandleT H, const std::string &Name, + void expectFindSymbolIn(VModuleKey K, const std::string &Name, bool ExportedSymbolsOnly) { - MockObjHandle = H; + MockKey = K; MockName = Name; MockBool = ExportedSymbolsOnly; } @@ -128,29 +119,29 @@ public: resetExpectations(); } - llvm::Error emitAndFinalize(ObjHandleT H) { - EXPECT_EQ(MockObjHandle, H) << "Handle should pass through"; + llvm::Error emitAndFinalize(VModuleKey K) { + EXPECT_EQ(MockKey, K) << "VModuleKey should pass through"; LastCalled = "emitAndFinalize"; return llvm::Error::success(); } - void expectEmitAndFinalize(ObjHandleT H) { MockObjHandle = H; } + void expectEmitAndFinalize(VModuleKey K) { MockKey = K; } void verifyEmitAndFinalize() { EXPECT_EQ("emitAndFinalize", LastCalled); resetExpectations(); } - void mapSectionAddress(ObjHandleT H, const void *LocalAddress, + void mapSectionAddress(VModuleKey K, const void *LocalAddress, llvm::JITTargetAddress TargetAddr) { - EXPECT_EQ(MockObjHandle, H); + EXPECT_EQ(MockKey, K); EXPECT_EQ(MockLocalAddress, LocalAddress); EXPECT_EQ(MockTargetAddress, TargetAddr); LastCalled = "mapSectionAddress"; } - void expectMapSectionAddress(ObjHandleT H, const void *LocalAddress, + void expectMapSectionAddress(VModuleKey K, const void *LocalAddress, llvm::JITTargetAddress TargetAddr) { - MockObjHandle = H; + MockKey = K; MockLocalAddress = LocalAddress; MockTargetAddress = TargetAddr; } @@ -162,9 +153,8 @@ public: private: // Backing fields for remembering parameter/return values std::string LastCalled; - std::shared_ptr<llvm::JITSymbolResolver> MockResolver; + VModuleKey MockKey; MockObjectFile MockObject; - ObjHandleT MockObjHandle; std::string MockName; bool MockBool; llvm::JITSymbol MockSymbol; @@ -175,9 +165,8 @@ private: // Clear remembered parameters between calls void resetExpectations() { LastCalled = "nothing"; - MockResolver = nullptr; + MockKey = 0; MockObject = 0; - MockObjHandle = 0; MockName = "bogus"; MockSymbol = llvm::JITSymbol(nullptr); MockLocalAddress = nullptr; @@ -190,6 +179,8 @@ private: TEST(ObjectTransformLayerTest, Main) { MockBaseLayer M; + ExecutionSession ES(std::make_shared<SymbolStringPool>()); + // Create one object transform layer using a transform (as a functor) // that allocates new objects, and deals in unique pointers. ObjectTransformLayer<MockBaseLayer, AllocatingTransform> T1(M); @@ -205,22 +196,23 @@ TEST(ObjectTransformLayerTest, Main) { }); // Test addObject with T1 (allocating) + auto K1 = ES.allocateVModule(); auto Obj1 = std::make_shared<MockObjectFile>(211); - auto SR = std::make_shared<NullResolver>(); - M.expectAddObject(Obj1, SR); - auto H = cantFail(T1.addObject(std::move(Obj1), SR)); - M.verifyAddObject(H); + M.expectAddObject(K1, Obj1); + cantFail(T1.addObject(K1, std::move(Obj1))); + M.verifyAddObject(); // Test addObjectSet with T2 (mutating) + auto K2 = ES.allocateVModule(); auto Obj2 = std::make_shared<MockObjectFile>(222); - M.expectAddObject(Obj2, SR); - H = cantFail(T2.addObject(Obj2, SR)); - M.verifyAddObject(H); + M.expectAddObject(K2, Obj2); + cantFail(T2.addObject(K2, Obj2)); + M.verifyAddObject(); EXPECT_EQ(223, *Obj2) << "Expected mutation"; // Test removeObjectSet - M.expectRemoveObject(H); - cantFail(T1.removeObject(H)); + M.expectRemoveObject(K2); + cantFail(T1.removeObject(K2)); M.verifyRemoveObject(); // Test findSymbol @@ -233,20 +225,20 @@ TEST(ObjectTransformLayerTest, Main) { // Test findSymbolIn Name = "bar"; ExportedOnly = false; - M.expectFindSymbolIn(H, Name, ExportedOnly); - llvm::JITSymbol Sym2 = T1.findSymbolIn(H, Name, ExportedOnly); + M.expectFindSymbolIn(K1, Name, ExportedOnly); + llvm::JITSymbol Sym2 = T1.findSymbolIn(K1, Name, ExportedOnly); M.verifyFindSymbolIn(std::move(Sym2)); // Test emitAndFinalize - M.expectEmitAndFinalize(H); - cantFail(T2.emitAndFinalize(H)); + M.expectEmitAndFinalize(K1); + cantFail(T2.emitAndFinalize(K1)); M.verifyEmitAndFinalize(); // Test mapSectionAddress char Buffer[24]; llvm::JITTargetAddress MockAddress = 255; - M.expectMapSectionAddress(H, Buffer, MockAddress); - T1.mapSectionAddress(H, Buffer, MockAddress); + M.expectMapSectionAddress(K1, Buffer, MockAddress); + T1.mapSectionAddress(K1, Buffer, MockAddress); M.verifyMapSectionAddress(); // Verify transform getter (non-const) @@ -290,37 +282,35 @@ TEST(ObjectTransformLayerTest, Main) { }; // Construct the jit layers. - RTDyldObjectLinkingLayer BaseLayer( - []() { - return std::make_shared<llvm::SectionMemoryManager>(); - }); - - auto IdentityTransform = - [](std::shared_ptr<llvm::object::OwningBinary<llvm::object::ObjectFile>> - Obj) { - return Obj; - }; + RTDyldObjectLinkingLayer BaseLayer(ES, [](VModuleKey) { + return RTDyldObjectLinkingLayer::Resources{ + std::make_shared<llvm::SectionMemoryManager>(), + std::make_shared<NullResolver>()}; + }); + + auto IdentityTransform = [](std::unique_ptr<llvm::MemoryBuffer> Obj) { + return Obj; + }; ObjectTransformLayer<decltype(BaseLayer), decltype(IdentityTransform)> TransformLayer(BaseLayer, IdentityTransform); auto NullCompiler = [](llvm::Module &) { - return llvm::object::OwningBinary<llvm::object::ObjectFile>(nullptr, - nullptr); + return std::unique_ptr<llvm::MemoryBuffer>(nullptr); }; IRCompileLayer<decltype(TransformLayer), decltype(NullCompiler)> CompileLayer(TransformLayer, NullCompiler); // Make sure that the calls from IRCompileLayer to ObjectTransformLayer // compile. - auto Resolver = std::make_shared<NullResolver>(); - cantFail(CompileLayer.addModule(std::shared_ptr<llvm::Module>(), Resolver)); + cantFail(CompileLayer.addModule(ES.allocateVModule(), + std::unique_ptr<llvm::Module>())); // Make sure that the calls from ObjectTransformLayer to ObjectLinkingLayer // compile. - decltype(TransformLayer)::ObjHandleT H2; - cantFail(TransformLayer.emitAndFinalize(H2)); - TransformLayer.findSymbolIn(H2, Name, false); + VModuleKey DummyKey = ES.allocateVModule(); + cantFail(TransformLayer.emitAndFinalize(DummyKey)); + TransformLayer.findSymbolIn(DummyKey, Name, false); TransformLayer.findSymbol(Name, true); - TransformLayer.mapSectionAddress(H2, nullptr, 0); - cantFail(TransformLayer.removeObject(H2)); + TransformLayer.mapSectionAddress(DummyKey, nullptr, 0); + cantFail(TransformLayer.removeObject(DummyKey)); } } diff --git a/unittests/ExecutionEngine/Orc/OrcCAPITest.cpp b/unittests/ExecutionEngine/Orc/OrcCAPITest.cpp index d9448d47667f..b288b6bab2c9 100644 --- a/unittests/ExecutionEngine/Orc/OrcCAPITest.cpp +++ b/unittests/ExecutionEngine/Orc/OrcCAPITest.cpp @@ -38,13 +38,11 @@ protected: return MB.takeModule(); } - std::shared_ptr<object::OwningBinary<object::ObjectFile>> - createTestObject() { + std::unique_ptr<MemoryBuffer> createTestObject() { orc::SimpleCompiler IRCompiler(*TM); auto M = createTestModule(TM->getTargetTriple()); M->setDataLayout(TM->createDataLayout()); - return std::make_shared<object::OwningBinary<object::ObjectFile>>( - IRCompiler(*M)); + return IRCompiler(*M); } typedef int (*MainFnTy)(); @@ -75,9 +73,8 @@ protected: CompileContext *CCtx = static_cast<CompileContext*>(Ctx); auto *ET = CCtx->APIExecTest; CCtx->M = ET->createTestModule(ET->TM->getTargetTriple()); - LLVMSharedModuleRef SM = LLVMOrcMakeSharedModule(wrap(CCtx->M.release())); - LLVMOrcAddEagerlyCompiledIR(JITStack, &CCtx->H, SM, myResolver, nullptr); - LLVMOrcDisposeSharedModuleRef(SM); + LLVMOrcAddEagerlyCompiledIR(JITStack, &CCtx->H, wrap(CCtx->M.release()), + myResolver, nullptr); CCtx->Compiled = true; LLVMOrcTargetAddress MainAddr; LLVMOrcGetSymbolAddress(JITStack, &MainAddr, "main"); @@ -89,7 +86,7 @@ protected: char *OrcCAPIExecutionTest::testFuncName = nullptr; TEST_F(OrcCAPIExecutionTest, TestEagerIRCompilation) { - if (!TM) + if (!SupportsJIT) return; LLVMOrcJITStackRef JIT = @@ -99,16 +96,28 @@ TEST_F(OrcCAPIExecutionTest, TestEagerIRCompilation) { LLVMOrcGetMangledSymbol(JIT, &testFuncName, "testFunc"); - LLVMSharedModuleRef SM = LLVMOrcMakeSharedModule(wrap(M.release())); LLVMOrcModuleHandle H; - LLVMOrcAddEagerlyCompiledIR(JIT, &H, SM, myResolver, nullptr); - LLVMOrcDisposeSharedModuleRef(SM); - LLVMOrcTargetAddress MainAddr; - LLVMOrcGetSymbolAddress(JIT, &MainAddr, "main"); - MainFnTy MainFn = (MainFnTy)MainAddr; - int Result = MainFn(); - EXPECT_EQ(Result, 42) - << "Eagerly JIT'd code did not return expected result"; + LLVMOrcAddEagerlyCompiledIR(JIT, &H, wrap(M.release()), myResolver, nullptr); + + // get symbol address searching the entire stack + { + LLVMOrcTargetAddress MainAddr; + LLVMOrcGetSymbolAddress(JIT, &MainAddr, "main"); + MainFnTy MainFn = (MainFnTy)MainAddr; + int Result = MainFn(); + EXPECT_EQ(Result, 42) + << "Eagerly JIT'd code did not return expected result"; + } + + // and then just searching a single handle + { + LLVMOrcTargetAddress MainAddr; + LLVMOrcGetSymbolAddressIn(JIT, &MainAddr, H, "main"); + MainFnTy MainFn = (MainFnTy)MainAddr; + int Result = MainFn(); + EXPECT_EQ(Result, 42) + << "Eagerly JIT'd code did not return expected result"; + } LLVMOrcRemoveModule(JIT, H); @@ -117,7 +126,7 @@ TEST_F(OrcCAPIExecutionTest, TestEagerIRCompilation) { } TEST_F(OrcCAPIExecutionTest, TestLazyIRCompilation) { - if (!TM) + if (!SupportsIndirection) return; LLVMOrcJITStackRef JIT = @@ -127,10 +136,8 @@ TEST_F(OrcCAPIExecutionTest, TestLazyIRCompilation) { LLVMOrcGetMangledSymbol(JIT, &testFuncName, "testFunc"); - LLVMSharedModuleRef SM = LLVMOrcMakeSharedModule(wrap(M.release())); LLVMOrcModuleHandle H; - LLVMOrcAddLazilyCompiledIR(JIT, &H, SM, myResolver, nullptr); - LLVMOrcDisposeSharedModuleRef(SM); + LLVMOrcAddLazilyCompiledIR(JIT, &H, wrap(M.release()), myResolver, nullptr); LLVMOrcTargetAddress MainAddr; LLVMOrcGetSymbolAddress(JIT, &MainAddr, "main"); MainFnTy MainFn = (MainFnTy)MainAddr; @@ -145,15 +152,10 @@ TEST_F(OrcCAPIExecutionTest, TestLazyIRCompilation) { } TEST_F(OrcCAPIExecutionTest, TestAddObjectFile) { - if (!TM) + if (!SupportsJIT) return; - std::unique_ptr<MemoryBuffer> ObjBuffer; - { - auto OwningObj = createTestObject(); - auto ObjAndBuffer = OwningObj->takeBinary(); - ObjBuffer = std::move(ObjAndBuffer.second); - } + auto ObjBuffer = createTestObject(); LLVMOrcJITStackRef JIT = LLVMOrcCreateInstance(wrap(TM.get())); @@ -175,7 +177,7 @@ TEST_F(OrcCAPIExecutionTest, TestAddObjectFile) { } TEST_F(OrcCAPIExecutionTest, TestDirectCallbacksAPI) { - if (!TM) + if (!SupportsIndirection) return; LLVMOrcJITStackRef JIT = diff --git a/unittests/ExecutionEngine/Orc/OrcTestCommon.cpp b/unittests/ExecutionEngine/Orc/OrcTestCommon.cpp index ccd2fc0fb189..a12b1876e29c 100644 --- a/unittests/ExecutionEngine/Orc/OrcTestCommon.cpp +++ b/unittests/ExecutionEngine/Orc/OrcTestCommon.cpp @@ -15,6 +15,11 @@ using namespace llvm; +const JITTargetAddress llvm::orc::CoreAPIsBasedStandardTest::FooAddr; +const JITTargetAddress llvm::orc::CoreAPIsBasedStandardTest::BarAddr; +const JITTargetAddress llvm::orc::CoreAPIsBasedStandardTest::BazAddr; +const JITTargetAddress llvm::orc::CoreAPIsBasedStandardTest::QuxAddr; + bool OrcNativeTarget::NativeTargetInitialized = false; ModuleBuilder::ModuleBuilder(LLVMContext &Context, StringRef Triple, diff --git a/unittests/ExecutionEngine/Orc/OrcTestCommon.h b/unittests/ExecutionEngine/Orc/OrcTestCommon.h index 28a5f08ee439..c6caaf07db0e 100644 --- a/unittests/ExecutionEngine/Orc/OrcTestCommon.h +++ b/unittests/ExecutionEngine/Orc/OrcTestCommon.h @@ -17,17 +17,59 @@ #include "llvm/ExecutionEngine/ExecutionEngine.h" #include "llvm/ExecutionEngine/JITSymbol.h" +#include "llvm/ExecutionEngine/Orc/IndirectionUtils.h" #include "llvm/IR/Function.h" #include "llvm/IR/IRBuilder.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" #include "llvm/IR/TypeBuilder.h" #include "llvm/Object/ObjectFile.h" +#include "llvm/Support/TargetRegistry.h" #include "llvm/Support/TargetSelect.h" +#include "gtest/gtest.h" + #include <memory> namespace llvm { +namespace orc { +// CoreAPIsStandardTest that saves a bunch of boilerplate by providing the +// following: +// +// (1) ES -- An ExecutionSession +// (2) Foo, Bar, Baz, Qux -- SymbolStringPtrs for strings "foo", "bar", "baz", +// and "qux" respectively. +// (3) FooAddr, BarAddr, BazAddr, QuxAddr -- Dummy addresses. Guaranteed +// distinct and non-null. +// (4) FooSym, BarSym, BazSym, QuxSym -- JITEvaluatedSymbols with FooAddr, +// BarAddr, BazAddr, and QuxAddr respectively. All with default strong, +// linkage and non-hidden visibility. +// (5) V -- A VSO associated with ES. +class CoreAPIsBasedStandardTest : public testing::Test { +public: +protected: + ExecutionSession ES; + VSO &V = ES.createVSO("V"); + SymbolStringPtr Foo = ES.getSymbolStringPool().intern("foo"); + SymbolStringPtr Bar = ES.getSymbolStringPool().intern("bar"); + SymbolStringPtr Baz = ES.getSymbolStringPool().intern("baz"); + SymbolStringPtr Qux = ES.getSymbolStringPool().intern("qux"); + static const JITTargetAddress FooAddr = 1U; + static const JITTargetAddress BarAddr = 2U; + static const JITTargetAddress BazAddr = 3U; + static const JITTargetAddress QuxAddr = 4U; + JITEvaluatedSymbol FooSym = + JITEvaluatedSymbol(FooAddr, JITSymbolFlags::Exported); + JITEvaluatedSymbol BarSym = + JITEvaluatedSymbol(BarAddr, JITSymbolFlags::Exported); + JITEvaluatedSymbol BazSym = + JITEvaluatedSymbol(BazAddr, JITSymbolFlags::Exported); + JITEvaluatedSymbol QuxSym = + JITEvaluatedSymbol(QuxAddr, JITSymbolFlags::Exported); +}; + +} // end namespace orc + class OrcNativeTarget { public: static void initialize() { @@ -59,15 +101,26 @@ public: // If we found a TargetMachine, check that it's one that Orc supports. const Triple& TT = TM->getTargetTriple(); + // Bail out for windows platforms. We do not support these yet. if ((TT.getArch() != Triple::x86_64 && TT.getArch() != Triple::x86) || - TT.isOSWindows()) - TM = nullptr; + TT.isOSWindows()) + return; + + // Target can JIT? + SupportsJIT = TM->getTarget().hasJIT(); + // Use ability to create callback manager to detect whether Orc + // has indirection support on this platform. This way the test + // and Orc code do not get out of sync. + SupportsIndirection = !!orc::createLocalCompileCallbackManager(TT, ES, 0); } }; protected: + orc::ExecutionSession ES; LLVMContext Context; std::unique_ptr<TargetMachine> TM; + bool SupportsJIT = false; + bool SupportsIndirection = false; }; class ModuleBuilder { diff --git a/unittests/ExecutionEngine/Orc/RTDyldObjectLinkingLayerTest.cpp b/unittests/ExecutionEngine/Orc/RTDyldObjectLinkingLayerTest.cpp index ed7b327124d7..420631c36ad2 100644 --- a/unittests/ExecutionEngine/Orc/RTDyldObjectLinkingLayerTest.cpp +++ b/unittests/ExecutionEngine/Orc/RTDyldObjectLinkingLayerTest.cpp @@ -12,6 +12,7 @@ #include "llvm/ExecutionEngine/ExecutionEngine.h" #include "llvm/ExecutionEngine/Orc/CompileUtils.h" #include "llvm/ExecutionEngine/Orc/LambdaResolver.h" +#include "llvm/ExecutionEngine/Orc/Legacy.h" #include "llvm/ExecutionEngine/Orc/NullResolver.h" #include "llvm/ExecutionEngine/SectionMemoryManager.h" #include "llvm/IR/Constants.h" @@ -66,7 +67,12 @@ TEST(RTDyldObjectLinkingLayerTest, TestSetProcessAllSections) { bool DebugSectionSeen = false; auto MM = std::make_shared<MemoryManagerWrapper>(DebugSectionSeen); - RTDyldObjectLinkingLayer ObjLayer([&MM]() { return MM; }); + ExecutionSession ES(std::make_shared<SymbolStringPool>()); + + RTDyldObjectLinkingLayer ObjLayer(ES, [&MM](VModuleKey) { + return RTDyldObjectLinkingLayer::Resources{ + MM, std::make_shared<NullResolver>()}; + }); LLVMContext Context; auto M = llvm::make_unique<Module>("", Context); @@ -88,46 +94,48 @@ TEST(RTDyldObjectLinkingLayerTest, TestSetProcessAllSections) { if (!TM) return; - auto Obj = - std::make_shared<object::OwningBinary<object::ObjectFile>>( - SimpleCompiler(*TM)(*M)); - - auto Resolver = - createLambdaResolver( - [](const std::string &Name) { - return JITSymbol(nullptr); - }, - [](const std::string &Name) { - return JITSymbol(nullptr); - }); + auto Obj = SimpleCompiler(*TM)(*M); { // Test with ProcessAllSections = false (the default). - auto H = cantFail(ObjLayer.addObject(Obj, Resolver)); - cantFail(ObjLayer.emitAndFinalize(H)); + auto K = ES.allocateVModule(); + cantFail(ObjLayer.addObject( + K, MemoryBuffer::getMemBufferCopy(Obj->getBuffer()))); + cantFail(ObjLayer.emitAndFinalize(K)); EXPECT_EQ(DebugSectionSeen, false) << "Unexpected debug info section"; - cantFail(ObjLayer.removeObject(H)); + cantFail(ObjLayer.removeObject(K)); } { // Test with ProcessAllSections = true. ObjLayer.setProcessAllSections(true); - auto H = cantFail(ObjLayer.addObject(Obj, Resolver)); - cantFail(ObjLayer.emitAndFinalize(H)); + auto K = ES.allocateVModule(); + cantFail(ObjLayer.addObject(K, std::move(Obj))); + cantFail(ObjLayer.emitAndFinalize(K)); EXPECT_EQ(DebugSectionSeen, true) << "Expected debug info section not seen"; - cantFail(ObjLayer.removeObject(H)); + cantFail(ObjLayer.removeObject(K)); } } TEST_F(RTDyldObjectLinkingLayerExecutionTest, NoDuplicateFinalization) { - if (!TM) + if (!SupportsJIT) return; + ExecutionSession ES(std::make_shared<SymbolStringPool>()); + auto MM = std::make_shared<SectionMemoryManagerWrapper>(); - RTDyldObjectLinkingLayer ObjLayer([&MM]() { return MM; }); + std::map<orc::VModuleKey, std::shared_ptr<orc::SymbolResolver>> Resolvers; + + RTDyldObjectLinkingLayer ObjLayer(ES, [&](VModuleKey K) { + auto I = Resolvers.find(K); + assert(I != Resolvers.end() && "Missing resolver"); + auto R = std::move(I->second); + Resolvers.erase(I); + return RTDyldObjectLinkingLayer::Resources{MM, std::move(R)}; + }); SimpleCompiler Compile(*TM); // Create a pair of modules that will trigger recursive finalization: @@ -153,9 +161,7 @@ TEST_F(RTDyldObjectLinkingLayerExecutionTest, NoDuplicateFinalization) { Builder.CreateRet(FourtyTwo); } - auto Obj1 = - std::make_shared<object::OwningBinary<object::ObjectFile>>( - Compile(*MB1.getModule())); + auto Obj1 = Compile(*MB1.getModule()); ModuleBuilder MB2(Context, "", "dummy"); { @@ -166,25 +172,29 @@ TEST_F(RTDyldObjectLinkingLayerExecutionTest, NoDuplicateFinalization) { IRBuilder<> Builder(FooEntry); Builder.CreateRet(Builder.CreateCall(BarDecl)); } - auto Obj2 = - std::make_shared<object::OwningBinary<object::ObjectFile>>( - Compile(*MB2.getModule())); - - auto Resolver = - createLambdaResolver( - [&](const std::string &Name) { - if (auto Sym = ObjLayer.findSymbol(Name, true)) - return Sym; - return JITSymbol(nullptr); + auto Obj2 = Compile(*MB2.getModule()); + + auto K1 = ES.allocateVModule(); + Resolvers[K1] = std::make_shared<NullResolver>(); + cantFail(ObjLayer.addObject(K1, std::move(Obj1))); + + auto K2 = ES.allocateVModule(); + auto LegacyLookup = [&](const std::string &Name) { + return ObjLayer.findSymbol(Name, true); + }; + + Resolvers[K2] = createSymbolResolver( + [&](const SymbolNameSet &Symbols) { + return cantFail(lookupFlagsWithLegacyFn(Symbols, LegacyLookup)); }, - [](const std::string &Name) { - return JITSymbol(nullptr); + [&](std::shared_ptr<AsynchronousSymbolQuery> Query, + const SymbolNameSet &Symbols) { + return lookupWithLegacyFn(ES, *Query, Symbols, LegacyLookup); }); - cantFail(ObjLayer.addObject(std::move(Obj1), Resolver)); - auto H = cantFail(ObjLayer.addObject(std::move(Obj2), Resolver)); - cantFail(ObjLayer.emitAndFinalize(H)); - cantFail(ObjLayer.removeObject(H)); + cantFail(ObjLayer.addObject(K2, std::move(Obj2))); + cantFail(ObjLayer.emitAndFinalize(K2)); + cantFail(ObjLayer.removeObject(K2)); // Finalization of module 2 should trigger finalization of module 1. // Verify that finalize on SMMW is only called once. @@ -193,12 +203,17 @@ TEST_F(RTDyldObjectLinkingLayerExecutionTest, NoDuplicateFinalization) { } TEST_F(RTDyldObjectLinkingLayerExecutionTest, NoPrematureAllocation) { - if (!TM) + if (!SupportsJIT) return; + ExecutionSession ES(std::make_shared<SymbolStringPool>()); + auto MM = std::make_shared<SectionMemoryManagerWrapper>(); - RTDyldObjectLinkingLayer ObjLayer([&MM]() { return MM; }); + RTDyldObjectLinkingLayer ObjLayer(ES, [&MM](VModuleKey K) { + return RTDyldObjectLinkingLayer::Resources{ + MM, std::make_shared<NullResolver>()}; + }); SimpleCompiler Compile(*TM); // Create a pair of unrelated modules: @@ -225,9 +240,7 @@ TEST_F(RTDyldObjectLinkingLayerExecutionTest, NoPrematureAllocation) { Builder.CreateRet(FourtyTwo); } - auto Obj1 = - std::make_shared<object::OwningBinary<object::ObjectFile>>( - Compile(*MB1.getModule())); + auto Obj1 = Compile(*MB1.getModule()); ModuleBuilder MB2(Context, "", "dummy"); { @@ -239,15 +252,13 @@ TEST_F(RTDyldObjectLinkingLayerExecutionTest, NoPrematureAllocation) { Value *Seven = ConstantInt::getSigned(Int32Ty, 7); Builder.CreateRet(Seven); } - auto Obj2 = - std::make_shared<object::OwningBinary<object::ObjectFile>>( - Compile(*MB2.getModule())); + auto Obj2 = Compile(*MB2.getModule()); - auto NR = std::make_shared<NullResolver>(); - auto H = cantFail(ObjLayer.addObject(std::move(Obj1), NR)); - cantFail(ObjLayer.addObject(std::move(Obj2), NR)); - cantFail(ObjLayer.emitAndFinalize(H)); - cantFail(ObjLayer.removeObject(H)); + auto K = ES.allocateVModule(); + cantFail(ObjLayer.addObject(K, std::move(Obj1))); + cantFail(ObjLayer.addObject(ES.allocateVModule(), std::move(Obj2))); + cantFail(ObjLayer.emitAndFinalize(K)); + cantFail(ObjLayer.removeObject(K)); // Only one call to needsToReserveAllocationSpace should have been made. EXPECT_EQ(MM->NeedsToReserveAllocationSpaceCount, 1) @@ -256,10 +267,14 @@ TEST_F(RTDyldObjectLinkingLayerExecutionTest, NoPrematureAllocation) { } TEST_F(RTDyldObjectLinkingLayerExecutionTest, TestNotifyLoadedSignature) { + ExecutionSession ES(std::make_shared<SymbolStringPool>()); RTDyldObjectLinkingLayer ObjLayer( - []() { return nullptr; }, - [](RTDyldObjectLinkingLayer::ObjHandleT, - const RTDyldObjectLinkingLayer::ObjectPtr &obj, + ES, + [](VModuleKey) { + return RTDyldObjectLinkingLayer::Resources{ + nullptr, std::make_shared<NullResolver>()}; + }, + [](VModuleKey, const object::ObjectFile &obj, const RuntimeDyld::LoadedObjectInfo &info) {}); } diff --git a/unittests/ExecutionEngine/Orc/RemoteObjectLayerTest.cpp b/unittests/ExecutionEngine/Orc/RemoteObjectLayerTest.cpp index 819c5f8eb34b..ae408a06f84d 100644 --- a/unittests/ExecutionEngine/Orc/RemoteObjectLayerTest.cpp +++ b/unittests/ExecutionEngine/Orc/RemoteObjectLayerTest.cpp @@ -24,8 +24,7 @@ public: using ObjHandleT = uint64_t; - using ObjectPtr = - std::shared_ptr<object::OwningBinary<object::ObjectFile>>; + using ObjectPtr = std::unique_ptr<MemoryBuffer>; using LookupFn = std::function<JITSymbol(StringRef, bool)>; using SymbolLookupTable = std::map<ObjHandleT, LookupFn>; @@ -43,7 +42,7 @@ public: Expected<ObjHandleT> addObject(ObjectPtr Obj, std::shared_ptr<JITSymbolResolver> Resolver) { - return AddObject(Obj, SymTab); + return AddObject(std::move(Obj), SymTab); } Error removeObject(ObjHandleT H) { @@ -102,8 +101,7 @@ MockObjectLayer::ObjectPtr createTestObject() { B.CreateRet(ConstantInt::getSigned(Type::getInt32Ty(Ctx), 42)); SimpleCompiler IRCompiler(*TM); - return std::make_shared<object::OwningBinary<object::ObjectFile>>( - IRCompiler(*MB.getModule())); + return IRCompiler(*MB.getModule()); } TEST(RemoteObjectLayer, AddObject) { @@ -121,7 +119,7 @@ TEST(RemoteObjectLayer, AddObject) { // Copy the bytes out of the test object: the copy will be used to verify // that the original is correctly transmitted over RPC to the mock layer. - StringRef ObjBytes = TestObject->getBinary()->getData(); + StringRef ObjBytes = TestObject->getBuffer(); std::vector<char> ObjContents(ObjBytes.size()); std::copy(ObjBytes.begin(), ObjBytes.end(), ObjContents.begin()); @@ -134,7 +132,7 @@ TEST(RemoteObjectLayer, AddObject) { MockObjectLayer::SymbolLookupTable &SymTab) { // Check that the received object file content matches the original. - StringRef RPCObjContents = Obj->getBinary()->getData(); + StringRef RPCObjContents = Obj->getBuffer(); EXPECT_EQ(RPCObjContents.size(), ObjContents.size()) << "RPC'd object file has incorrect size"; EXPECT_TRUE(std::equal(RPCObjContents.begin(), RPCObjContents.end(), @@ -159,7 +157,7 @@ TEST(RemoteObjectLayer, AddObject) { }); cantFail(Client.addObject(std::move(TestObject), - std::make_shared<NullResolver>())); + std::make_shared<NullLegacyResolver>())); cantFail(ClientEP.callB<remote::utils::TerminateSession>()); ServerThread.join(); } @@ -205,8 +203,8 @@ TEST(RemoteObjectLayer, AddObjectFailure) { cantFail(ServerEP.handleOne()); }); - auto HandleOrErr = - Client.addObject(std::move(TestObject), std::make_shared<NullResolver>()); + auto HandleOrErr = Client.addObject(std::move(TestObject), + std::make_shared<NullLegacyResolver>()); EXPECT_FALSE(HandleOrErr) << "Expected error from addObject"; @@ -258,8 +256,8 @@ TEST(RemoteObjectLayer, RemoveObject) { cantFail(ServerEP.handleOne()); }); - auto H = cantFail(Client.addObject(std::move(TestObject), - std::make_shared<NullResolver>())); + auto H = cantFail(Client.addObject(std::move(TestObject), + std::make_shared<NullLegacyResolver>())); cantFail(Client.removeObject(H)); @@ -309,8 +307,8 @@ TEST(RemoteObjectLayer, RemoveObjectFailure) { cantFail(ServerEP.handleOne()); }); - auto H = cantFail(Client.addObject(std::move(TestObject), - std::make_shared<NullResolver>())); + auto H = cantFail(Client.addObject(std::move(TestObject), + std::make_shared<NullLegacyResolver>())); auto Err = Client.removeObject(H); EXPECT_TRUE(!!Err) << "Expected error from removeObject"; @@ -374,7 +372,7 @@ TEST(RemoteObjectLayer, FindSymbol) { }); cantFail(Client.addObject(std::move(TestObject), - std::make_shared<NullResolver>())); + std::make_shared<NullLegacyResolver>())); // Check that we can find and materialize a valid symbol. auto Sym1 = Client.findSymbol("foobar", true); @@ -463,7 +461,7 @@ TEST(RemoteObjectLayer, FindSymbolIn) { }); auto H = cantFail(Client.addObject(std::move(TestObject), - std::make_shared<NullResolver>())); + std::make_shared<NullLegacyResolver>())); auto Sym1 = Client.findSymbolIn(H, "foobar", true); @@ -523,7 +521,7 @@ TEST(RemoteObjectLayer, EmitAndFinalize) { }); auto H = cantFail(Client.addObject(std::move(TestObject), - std::make_shared<NullResolver>())); + std::make_shared<NullLegacyResolver>())); auto Err = Client.emitAndFinalize(H); EXPECT_FALSE(!!Err) << "emitAndFinalize should work"; @@ -573,7 +571,7 @@ TEST(RemoteObjectLayer, EmitAndFinalizeFailure) { }); auto H = cantFail(Client.addObject(std::move(TestObject), - std::make_shared<NullResolver>())); + std::make_shared<NullLegacyResolver>())); auto Err = Client.emitAndFinalize(H); EXPECT_TRUE(!!Err) << "emitAndFinalize should work"; diff --git a/unittests/ExecutionEngine/Orc/SymbolStringPoolTest.cpp b/unittests/ExecutionEngine/Orc/SymbolStringPoolTest.cpp index 79929e332182..861a9661223a 100644 --- a/unittests/ExecutionEngine/Orc/SymbolStringPoolTest.cpp +++ b/unittests/ExecutionEngine/Orc/SymbolStringPoolTest.cpp @@ -34,6 +34,12 @@ TEST(SymbolStringPool, UniquingAndComparisons) { (void)(P1 < P3); } +TEST(SymbolStringPool, Dereference) { + SymbolStringPool SP; + auto Foo = SP.intern("foo"); + EXPECT_EQ(*Foo, "foo") << "Equality on dereferenced string failed"; +} + TEST(SymbolStringPool, ClearDeadEntries) { SymbolStringPool SP; { diff --git a/unittests/FuzzMutate/RandomIRBuilderTest.cpp b/unittests/FuzzMutate/RandomIRBuilderTest.cpp index b60da6cbf9fc..d6ad07d00538 100644 --- a/unittests/FuzzMutate/RandomIRBuilderTest.cpp +++ b/unittests/FuzzMutate/RandomIRBuilderTest.cpp @@ -266,4 +266,34 @@ TEST(RandomIRBuilderTest, FirstClassTypes) { } } +TEST(RandomIRBuilderTest, SwiftError) { + // Check that we never pick swifterror value as a source for operation + // other than load, store and call. + + LLVMContext Ctx; + const char *SourceCode = "declare void @use(i8** swifterror %err)" + "define void @test() {\n" + "entry:\n" + " %err = alloca swifterror i8*, align 8\n" + " call void @use(i8** swifterror %err)\n" + " ret void\n" + "}"; + auto M = parseAssembly(SourceCode, Ctx); + + std::vector<Type *> Types = {Type::getInt8Ty(Ctx)}; + RandomIRBuilder IB(Seed, Types); + + // Get first basic block of the test function + Function &F = *M->getFunction("test"); + BasicBlock &BB = *F.begin(); + Instruction *Alloca = &*BB.begin(); + + fuzzerop::OpDescriptor Descr = fuzzerop::gepDescriptor(1); + + for (int i = 0; i < 10; ++i) { + Value *V = IB.findOrCreateSource(BB, {Alloca}, {}, Descr.SourcePreds[0]); + ASSERT_FALSE(isa<AllocaInst>(V)); + } +} + } diff --git a/unittests/FuzzMutate/StrategiesTest.cpp b/unittests/FuzzMutate/StrategiesTest.cpp index 4fcd45692714..320a4e950f3d 100644 --- a/unittests/FuzzMutate/StrategiesTest.cpp +++ b/unittests/FuzzMutate/StrategiesTest.cpp @@ -64,6 +64,18 @@ std::unique_ptr<Module> parseAssembly( return M; } +void IterateOnSource(StringRef Source, IRMutator &Mutator) { + LLVMContext Ctx; + + for (int i = 0; i < 10; ++i) { + auto M = parseAssembly(Source.data(), Ctx); + ASSERT_TRUE(M && !verifyModule(*M, &errs())); + + Mutator.mutateModule(*M, Seed, Source.size(), Source.size() + 100); + EXPECT_TRUE(!verifyModule(*M, &errs())); + } +} + TEST(InjectorIRStrategyTest, EmptyModule) { // Test that we can inject into empty module @@ -81,7 +93,6 @@ TEST(InjectorIRStrategyTest, EmptyModule) { TEST(InstDeleterIRStrategyTest, EmptyFunction) { // Test that we don't crash even if we can't remove from one of the functions. - LLVMContext Ctx; StringRef Source = "" "define <8 x i32> @func1() {\n" "ret <8 x i32> undef\n" @@ -96,15 +107,33 @@ TEST(InstDeleterIRStrategyTest, EmptyFunction) { auto Mutator = createDeleterMutator(); ASSERT_TRUE(Mutator); - // We need to choose 'func1' in order for the crash to appear. - // Loop 10 times and assume we are lucky. - for (int i = 0; i < 10; ++i) { - auto M = parseAssembly(Source.data(), Ctx); - ASSERT_TRUE(M && !verifyModule(*M, &errs())); + IterateOnSource(Source, *Mutator); +} - Mutator->mutateModule(*M, Seed, Source.size(), Source.size() + 100); - EXPECT_TRUE(!verifyModule(*M, &errs())); - } +TEST(InstDeleterIRStrategyTest, PhiNodes) { + // Test that inst deleter works correctly with the phi nodes. + + LLVMContext Ctx; + StringRef Source = "\n\ + define i32 @earlyreturncrash(i32 %x) {\n\ + entry:\n\ + switch i32 %x, label %sw.epilog [\n\ + i32 1, label %sw.bb1\n\ + ]\n\ + \n\ + sw.bb1:\n\ + br label %sw.epilog\n\ + \n\ + sw.epilog:\n\ + %a.0 = phi i32 [ 7, %entry ], [ 9, %sw.bb1 ]\n\ + %b.0 = phi i32 [ 10, %entry ], [ 4, %sw.bb1 ]\n\ + ret i32 %a.0\n\ + }"; + + auto Mutator = createDeleterMutator(); + ASSERT_TRUE(Mutator); + + IterateOnSource(Source, *Mutator); } } diff --git a/unittests/IR/AttributesTest.cpp b/unittests/IR/AttributesTest.cpp index ab018d845382..dd88cc359343 100644 --- a/unittests/IR/AttributesTest.cpp +++ b/unittests/IR/AttributesTest.cpp @@ -63,6 +63,76 @@ TEST(Attributes, AddAttributes) { EXPECT_TRUE(AL.hasFnAttribute(Attribute::NoReturn)); } +TEST(Attributes, RemoveAlign) { + LLVMContext C; + + Attribute AlignAttr = Attribute::getWithAlignment(C, 8); + Attribute StackAlignAttr = Attribute::getWithStackAlignment(C, 32); + AttrBuilder B_align_readonly; + B_align_readonly.addAttribute(AlignAttr); + B_align_readonly.addAttribute(Attribute::ReadOnly); + AttrBuilder B_align; + B_align.addAttribute(AlignAttr); + AttrBuilder B_stackalign_optnone; + B_stackalign_optnone.addAttribute(StackAlignAttr); + B_stackalign_optnone.addAttribute(Attribute::OptimizeNone); + AttrBuilder B_stackalign; + B_stackalign.addAttribute(StackAlignAttr); + + AttributeSet AS = AttributeSet::get(C, B_align_readonly); + EXPECT_TRUE(AS.getAlignment() == 8); + EXPECT_TRUE(AS.hasAttribute(Attribute::ReadOnly)); + AS = AS.removeAttribute(C, Attribute::Alignment); + EXPECT_FALSE(AS.hasAttribute(Attribute::Alignment)); + EXPECT_TRUE(AS.hasAttribute(Attribute::ReadOnly)); + AS = AttributeSet::get(C, B_align_readonly); + AS = AS.removeAttributes(C, B_align); + EXPECT_TRUE(AS.getAlignment() == 0); + EXPECT_TRUE(AS.hasAttribute(Attribute::ReadOnly)); + + AttributeList AL; + AL = AL.addParamAttributes(C, 0, B_align_readonly); + AL = AL.addAttributes(C, 0, B_stackalign_optnone); + EXPECT_TRUE(AL.hasAttributes(0)); + EXPECT_TRUE(AL.hasAttribute(0, Attribute::StackAlignment)); + EXPECT_TRUE(AL.hasAttribute(0, Attribute::OptimizeNone)); + EXPECT_TRUE(AL.getStackAlignment(0) == 32); + EXPECT_TRUE(AL.hasParamAttrs(0)); + EXPECT_TRUE(AL.hasParamAttr(0, Attribute::Alignment)); + EXPECT_TRUE(AL.hasParamAttr(0, Attribute::ReadOnly)); + EXPECT_TRUE(AL.getParamAlignment(0) == 8); + + AL = AL.removeParamAttribute(C, 0, Attribute::Alignment); + EXPECT_FALSE(AL.hasParamAttr(0, Attribute::Alignment)); + EXPECT_TRUE(AL.hasParamAttr(0, Attribute::ReadOnly)); + EXPECT_TRUE(AL.hasAttribute(0, Attribute::StackAlignment)); + EXPECT_TRUE(AL.hasAttribute(0, Attribute::OptimizeNone)); + EXPECT_TRUE(AL.getStackAlignment(0) == 32); + + AL = AL.removeAttribute(C, 0, Attribute::StackAlignment); + EXPECT_FALSE(AL.hasParamAttr(0, Attribute::Alignment)); + EXPECT_TRUE(AL.hasParamAttr(0, Attribute::ReadOnly)); + EXPECT_FALSE(AL.hasAttribute(0, Attribute::StackAlignment)); + EXPECT_TRUE(AL.hasAttribute(0, Attribute::OptimizeNone)); + + AttributeList AL2; + AL2 = AL2.addParamAttributes(C, 0, B_align_readonly); + AL2 = AL2.addAttributes(C, 0, B_stackalign_optnone); + + AL2 = AL2.removeParamAttributes(C, 0, B_align); + EXPECT_FALSE(AL2.hasParamAttr(0, Attribute::Alignment)); + EXPECT_TRUE(AL2.hasParamAttr(0, Attribute::ReadOnly)); + EXPECT_TRUE(AL2.hasAttribute(0, Attribute::StackAlignment)); + EXPECT_TRUE(AL2.hasAttribute(0, Attribute::OptimizeNone)); + EXPECT_TRUE(AL2.getStackAlignment(0) == 32); + + AL2 = AL2.removeAttributes(C, 0, B_stackalign); + EXPECT_FALSE(AL2.hasParamAttr(0, Attribute::Alignment)); + EXPECT_TRUE(AL2.hasParamAttr(0, Attribute::ReadOnly)); + EXPECT_FALSE(AL2.hasAttribute(0, Attribute::StackAlignment)); + EXPECT_TRUE(AL2.hasAttribute(0, Attribute::OptimizeNone)); +} + TEST(Attributes, AddMatchingAlignAttr) { LLVMContext C; AttributeList AL; @@ -89,4 +159,12 @@ TEST(Attributes, EmptyGet) { EXPECT_TRUE(AL.isEmpty()); } +TEST(Attributes, OverflowGet) { + LLVMContext C; + std::pair<unsigned, Attribute> Attrs[] = { { AttributeList::ReturnIndex, Attribute::get(C, Attribute::SExt) }, + { AttributeList::FunctionIndex, Attribute::get(C, Attribute::ReadOnly) } }; + AttributeList AL = AttributeList::get(C, Attrs); + EXPECT_EQ(2U, AL.getNumAttrSets()); +} + } // end anonymous namespace diff --git a/unittests/IR/BasicBlockTest.cpp b/unittests/IR/BasicBlockTest.cpp index f1777e35b82c..07ed997f6381 100644 --- a/unittests/IR/BasicBlockTest.cpp +++ b/unittests/IR/BasicBlockTest.cpp @@ -33,6 +33,12 @@ TEST(BasicBlockTest, PhiRange) { std::unique_ptr<BasicBlock> BB2(BasicBlock::Create(Context)); BranchInst::Create(BB.get(), BB2.get()); + // Make sure this doesn't crash if there are no phis. + for (auto &PN : BB->phis()) { + (void)PN; + EXPECT_TRUE(false) << "empty block should have no phis"; + } + // Make it a cycle. auto *BI = BranchInst::Create(BB.get(), BB.get()); @@ -63,6 +69,15 @@ TEST(BasicBlockTest, PhiRange) { CI = BB->phis().begin(); EXPECT_NE(CI, BB->phis().end()); + // Test that filtering iterators work with basic blocks. + auto isPhi = [](Instruction &I) { return isa<PHINode>(&I); }; + auto Phis = make_filter_range(*BB, isPhi); + auto ReversedPhis = reverse(make_filter_range(*BB, isPhi)); + EXPECT_EQ(std::distance(Phis.begin(), Phis.end()), 3); + EXPECT_EQ(&*Phis.begin(), P1); + EXPECT_EQ(std::distance(ReversedPhis.begin(), ReversedPhis.end()), 3); + EXPECT_EQ(&*ReversedPhis.begin(), P3); + // And iterate a const range. for (const auto &PN : const_cast<const BasicBlock *>(BB.get())->phis()) { EXPECT_EQ(BB.get(), PN.getIncomingBlock(0)); @@ -71,5 +86,47 @@ TEST(BasicBlockTest, PhiRange) { } } +#define CHECK_ITERATORS(Range1, Range2) \ + EXPECT_EQ(std::distance(Range1.begin(), Range1.end()), \ + std::distance(Range2.begin(), Range2.end())); \ + for (auto Pair : zip(Range1, Range2)) \ + EXPECT_EQ(&std::get<0>(Pair), std::get<1>(Pair)); + +TEST(BasicBlockTest, TestInstructionsWithoutDebug) { + LLVMContext Ctx; + + Module *M = new Module("MyModule", Ctx); + Type *ArgTy1[] = {Type::getInt32PtrTy(Ctx)}; + FunctionType *FT = FunctionType::get(Type::getVoidTy(Ctx), ArgTy1, false); + Argument *V = new Argument(Type::getInt32Ty(Ctx)); + Function *F = Function::Create(FT, Function::ExternalLinkage, "", M); + + Value *DbgAddr = Intrinsic::getDeclaration(M, Intrinsic::dbg_addr); + Value *DbgDeclare = + Intrinsic::getDeclaration(M, Intrinsic::dbg_declare); + Value *DbgValue = Intrinsic::getDeclaration(M, Intrinsic::dbg_value); + Value *DIV = MetadataAsValue::get(Ctx, (Metadata *)nullptr); + SmallVector<Value *, 3> Args = {DIV, DIV, DIV}; + + BasicBlock *BB1 = BasicBlock::Create(Ctx, "", F); + const BasicBlock *BBConst = BB1; + IRBuilder<> Builder1(BB1); + + AllocaInst *Var = Builder1.CreateAlloca(Builder1.getInt8Ty()); + Builder1.CreateCall(DbgValue, Args); + Instruction *AddInst = cast<Instruction>(Builder1.CreateAdd(V, V)); + Instruction *MulInst = cast<Instruction>(Builder1.CreateMul(AddInst, V)); + Builder1.CreateCall(DbgDeclare, Args); + Instruction *SubInst = cast<Instruction>(Builder1.CreateSub(MulInst, V)); + Builder1.CreateCall(DbgAddr, Args); + + SmallVector<Instruction *, 4> Exp = {Var, AddInst, MulInst, SubInst}; + CHECK_ITERATORS(BB1->instructionsWithoutDebug(), Exp); + CHECK_ITERATORS(BBConst->instructionsWithoutDebug(), Exp); + + delete M; + delete V; +} + } // End anonymous namespace. } // End llvm namespace. diff --git a/unittests/IR/CFGBuilder.cpp b/unittests/IR/CFGBuilder.cpp index 50494ab5c7ca..0f9fb8b18cc1 100644 --- a/unittests/IR/CFGBuilder.cpp +++ b/unittests/IR/CFGBuilder.cpp @@ -36,9 +36,9 @@ CFGBuilder::CFGBuilder(Function *F, const std::vector<Arc> &InitialArcs, } static void ConnectBlocks(BasicBlock *From, BasicBlock *To) { - DEBUG(dbgs() << "Creating BB arc " << From->getName() << " -> " - << To->getName() << "\n"; - dbgs().flush()); + LLVM_DEBUG(dbgs() << "Creating BB arc " << From->getName() << " -> " + << To->getName() << "\n"; + dbgs().flush()); auto *IntTy = IntegerType::get(From->getContext(), 32); if (isa<UnreachableInst>(From->getTerminator())) @@ -57,9 +57,9 @@ static void ConnectBlocks(BasicBlock *From, BasicBlock *To) { } static void DisconnectBlocks(BasicBlock *From, BasicBlock *To) { - DEBUG(dbgs() << "Deleting BB arc " << From->getName() << " -> " - << To->getName() << "\n"; - dbgs().flush()); + LLVM_DEBUG(dbgs() << "Deleting BB arc " << From->getName() << " -> " + << To->getName() << "\n"; + dbgs().flush()); SwitchInst *SI = cast<SwitchInst>(From->getTerminator()); if (SI->getNumCases() == 0) { diff --git a/unittests/IR/CMakeLists.txt b/unittests/IR/CMakeLists.txt index 83f9dfd31765..58ebf159c3e1 100644 --- a/unittests/IR/CMakeLists.txt +++ b/unittests/IR/CMakeLists.txt @@ -6,7 +6,7 @@ set(LLVM_LINK_COMPONENTS Passes ) -set(IRSources +add_llvm_unittest(IRTests AsmWriterTest.cpp AttributesTest.cpp BasicBlockTest.cpp @@ -15,8 +15,10 @@ set(IRSources ConstantsTest.cpp DebugInfoTest.cpp DebugTypeODRUniquingTest.cpp + DeferredDominanceTest.cpp DominatorTreeTest.cpp DominatorTreeBatchUpdatesTest.cpp + DomTreeUpdaterTest.cpp FunctionTest.cpp PassBuilderCallbacksTest.cpp IRBuilderTest.cpp @@ -24,6 +26,7 @@ set(IRSources IntrinsicsTest.cpp LegacyPassManagerTest.cpp MDBuilderTest.cpp + ManglerTest.cpp MetadataTest.cpp ModuleTest.cpp PassManagerTest.cpp @@ -38,13 +41,3 @@ set(IRSources VerifierTest.cpp WaymarkTest.cpp ) - -# HACK: Declare a couple of source files as optionally compiled to satisfy the -# missing-file-checker in LLVM's weird CMake build. -set(LLVM_OPTIONAL_SOURCES - ValueMapTest.cpp - ) - -add_llvm_unittest(IRTests - ${IRSources} - ) diff --git a/unittests/IR/ConstantRangeTest.cpp b/unittests/IR/ConstantRangeTest.cpp index 351256d49932..c0a30f1b4d94 100644 --- a/unittests/IR/ConstantRangeTest.cpp +++ b/unittests/IR/ConstantRangeTest.cpp @@ -1021,4 +1021,103 @@ TEST(ConstantRange, GetEquivalentICmp) { EXPECT_EQ(RHS, APInt(32, -1)); } +TEST(ConstantRange, MakeGuaranteedNoWrapRegionMulUnsignedSingleValue) { + typedef OverflowingBinaryOperator OBO; + + for (uint64_t I = std::numeric_limits<uint8_t>::min(); + I <= std::numeric_limits<uint8_t>::max(); I++) { + auto Range = ConstantRange::makeGuaranteedNoWrapRegion( + Instruction::Mul, ConstantRange(APInt(8, I), APInt(8, I + 1)), + OBO::NoUnsignedWrap); + + for (uint64_t V = std::numeric_limits<uint8_t>::min(); + V <= std::numeric_limits<uint8_t>::max(); V++) { + bool Overflow; + (void)APInt(8, I).umul_ov(APInt(8, V), Overflow); + EXPECT_EQ(!Overflow, Range.contains(APInt(8, V))); + } + } +} + +TEST(ConstantRange, MakeGuaranteedNoWrapRegionMulSignedSingleValue) { + typedef OverflowingBinaryOperator OBO; + + for (int64_t I = std::numeric_limits<int8_t>::min(); + I <= std::numeric_limits<int8_t>::max(); I++) { + auto Range = ConstantRange::makeGuaranteedNoWrapRegion( + Instruction::Mul, + ConstantRange(APInt(8, I, /*isSigned=*/true), + APInt(8, I + 1, /*isSigned=*/true)), + OBO::NoSignedWrap); + + for (int64_t V = std::numeric_limits<int8_t>::min(); + V <= std::numeric_limits<int8_t>::max(); V++) { + bool Overflow; + (void)APInt(8, I, /*isSigned=*/true) + .smul_ov(APInt(8, V, /*isSigned=*/true), Overflow); + EXPECT_EQ(!Overflow, Range.contains(APInt(8, V, /*isSigned=*/true))); + } + } +} + +TEST(ConstantRange, MakeGuaranteedNoWrapRegionMulUnsignedAndSignedSingleValue) { + typedef OverflowingBinaryOperator OBO; + + for (uint64_t I = std::numeric_limits<uint8_t>::min(); + I <= std::numeric_limits<uint8_t>::max(); I++) { + auto Range = ConstantRange::makeGuaranteedNoWrapRegion( + Instruction::Mul, ConstantRange(APInt(8, I), APInt(8, I + 1)), + OBO::NoUnsignedWrap | OBO::NoSignedWrap); + + for (uint64_t V = std::numeric_limits<uint8_t>::min(); + V <= std::numeric_limits<uint8_t>::max(); V++) { + bool UOverflow; + (void)APInt(8, I).umul_ov(APInt(8, V), UOverflow); + bool SOverflow; + (void)APInt(8, I).smul_ov(APInt(8, V), SOverflow); + EXPECT_EQ(!(UOverflow || SOverflow), Range.contains(APInt(8, V))); + } + } +} + +TEST(ConstantRange, MakeGuaranteedNoWrapRegionMulUnsignedRange) { + typedef OverflowingBinaryOperator OBO; + + for (uint64_t Lo = std::numeric_limits<uint8_t>::min(); + Lo <= std::numeric_limits<uint8_t>::max(); Lo++) { + for (uint64_t Hi = Lo; Hi <= std::numeric_limits<uint8_t>::max(); Hi++) { + EXPECT_EQ( + ConstantRange::makeGuaranteedNoWrapRegion( + Instruction::Mul, ConstantRange(APInt(8, Lo), APInt(8, Hi + 1)), + OBO::NoUnsignedWrap), + ConstantRange::makeGuaranteedNoWrapRegion( + Instruction::Mul, ConstantRange(APInt(8, Hi), APInt(8, Hi + 1)), + OBO::NoUnsignedWrap)); + } + } +} + +TEST(ConstantRange, MakeGuaranteedNoWrapRegionMulSignedRange) { + typedef OverflowingBinaryOperator OBO; + + int Lo = -12, Hi = 16; + auto Range = ConstantRange::makeGuaranteedNoWrapRegion( + Instruction::Mul, + ConstantRange(APInt(8, Lo, /*isSigned=*/true), + APInt(8, Hi + 1, /*isSigned=*/true)), + OBO::NoSignedWrap); + + for (int64_t V = std::numeric_limits<int8_t>::min(); + V <= std::numeric_limits<int8_t>::max(); V++) { + bool AnyOverflow = false; + for (int64_t I = Lo; I <= Hi; I++) { + bool Overflow; + (void)APInt(8, I, /*isSigned=*/true) + .smul_ov(APInt(8, V, /*isSigned=*/true), Overflow); + AnyOverflow |= Overflow; + } + EXPECT_EQ(!AnyOverflow, Range.contains(APInt(8, V, /*isSigned=*/true))); + } +} + } // anonymous namespace diff --git a/unittests/IR/ConstantsTest.cpp b/unittests/IR/ConstantsTest.cpp index ccffa50bf133..cf67e55b32a2 100644 --- a/unittests/IR/ConstantsTest.cpp +++ b/unittests/IR/ConstantsTest.cpp @@ -473,8 +473,7 @@ TEST(ConstantsTest, BitcastToGEP) { GlobalValue::ExternalLinkage, nullptr); auto *PtrTy = PointerType::get(i32, 0); auto *C = ConstantExpr::getBitCast(G, PtrTy); - ASSERT_EQ(dyn_cast<ConstantExpr>(C)->getOpcode(), - Instruction::BitCast); + ASSERT_EQ(cast<ConstantExpr>(C)->getOpcode(), Instruction::BitCast); } } // end anonymous namespace diff --git a/unittests/IR/DebugTypeODRUniquingTest.cpp b/unittests/IR/DebugTypeODRUniquingTest.cpp index 7eb08e24b408..af77091221c3 100644 --- a/unittests/IR/DebugTypeODRUniquingTest.cpp +++ b/unittests/IR/DebugTypeODRUniquingTest.cpp @@ -30,7 +30,7 @@ TEST(DebugTypeODRUniquingTest, getODRType) { // Without a type map, this should return null. EXPECT_FALSE(DICompositeType::getODRType( Context, UUID, dwarf::DW_TAG_class_type, nullptr, nullptr, 0, nullptr, - nullptr, 0, 0, 0, DINode::FlagZero, nullptr, 0, nullptr, nullptr)); + nullptr, 0, 0, 0, DINode::FlagZero, nullptr, 0, nullptr, nullptr, nullptr)); // Enable the mapping. There still shouldn't be a type. Context.enableDebugTypeODRUniquing(); @@ -39,7 +39,7 @@ TEST(DebugTypeODRUniquingTest, getODRType) { // Create some ODR-uniqued type. auto &CT = *DICompositeType::getODRType( Context, UUID, dwarf::DW_TAG_class_type, nullptr, nullptr, 0, nullptr, - nullptr, 0, 0, 0, DINode::FlagZero, nullptr, 0, nullptr, nullptr); + nullptr, 0, 0, 0, DINode::FlagZero, nullptr, 0, nullptr, nullptr, nullptr); EXPECT_EQ(UUID.getString(), CT.getIdentifier()); // Check that we get it back, even if we change a field. @@ -47,12 +47,12 @@ TEST(DebugTypeODRUniquingTest, getODRType) { EXPECT_EQ(&CT, DICompositeType::getODRType( Context, UUID, dwarf::DW_TAG_class_type, nullptr, nullptr, 0, nullptr, nullptr, 0, 0, 0, DINode::FlagZero, nullptr, 0, - nullptr, nullptr)); + nullptr, nullptr, nullptr)); EXPECT_EQ(&CT, DICompositeType::getODRType( Context, UUID, dwarf::DW_TAG_class_type, MDString::get(Context, "name"), nullptr, 0, nullptr, nullptr, 0, - 0, 0, DINode::FlagZero, nullptr, 0, nullptr, nullptr)); + 0, 0, DINode::FlagZero, nullptr, 0, nullptr, nullptr, nullptr)); // Check that it's discarded with the type map. Context.disableDebugTypeODRUniquing(); @@ -71,32 +71,32 @@ TEST(DebugTypeODRUniquingTest, buildODRType) { MDString &UUID = *MDString::get(Context, "Type"); auto &CT = *DICompositeType::buildODRType( Context, UUID, dwarf::DW_TAG_class_type, nullptr, nullptr, 0, nullptr, - nullptr, 0, 0, 0, DINode::FlagFwdDecl, nullptr, 0, nullptr, nullptr); + nullptr, 0, 0, 0, DINode::FlagFwdDecl, nullptr, 0, nullptr, nullptr, nullptr); EXPECT_EQ(&CT, DICompositeType::getODRTypeIfExists(Context, UUID)); EXPECT_EQ(dwarf::DW_TAG_class_type, CT.getTag()); // Update with another forward decl. This should be a no-op. EXPECT_EQ(&CT, DICompositeType::buildODRType( Context, UUID, dwarf::DW_TAG_structure_type, nullptr, nullptr, 0, nullptr, - nullptr, 0, 0, 0, DINode::FlagFwdDecl, nullptr, 0, nullptr, nullptr)); + nullptr, 0, 0, 0, DINode::FlagFwdDecl, nullptr, 0, nullptr, nullptr, nullptr)); EXPECT_EQ(dwarf::DW_TAG_class_type, CT.getTag()); // Update with a definition. This time we should see a change. EXPECT_EQ(&CT, DICompositeType::buildODRType( Context, UUID, dwarf::DW_TAG_structure_type, nullptr, nullptr, 0, nullptr, nullptr, 0, 0, 0, DINode::FlagZero, - nullptr, 0, nullptr, nullptr)); + nullptr, 0, nullptr, nullptr, nullptr)); EXPECT_EQ(dwarf::DW_TAG_structure_type, CT.getTag()); // Further updates should be ignored. EXPECT_EQ(&CT, DICompositeType::buildODRType( Context, UUID, dwarf::DW_TAG_class_type, nullptr, nullptr, 0, nullptr, - nullptr, 0, 0, 0, DINode::FlagFwdDecl, nullptr, 0, nullptr, nullptr)); + nullptr, 0, 0, 0, DINode::FlagFwdDecl, nullptr, 0, nullptr, nullptr, nullptr)); EXPECT_EQ(dwarf::DW_TAG_structure_type, CT.getTag()); EXPECT_EQ(&CT, DICompositeType::buildODRType( Context, UUID, dwarf::DW_TAG_class_type, nullptr, nullptr, 0, nullptr, nullptr, 0, 0, 0, DINode::FlagZero, nullptr, 0, - nullptr, nullptr)); + nullptr, nullptr, nullptr)); EXPECT_EQ(dwarf::DW_TAG_structure_type, CT.getTag()); } @@ -108,7 +108,7 @@ TEST(DebugTypeODRUniquingTest, buildODRTypeFields) { MDString &UUID = *MDString::get(Context, "UUID"); auto &CT = *DICompositeType::buildODRType( Context, UUID, 0, nullptr, nullptr, 0, nullptr, nullptr, 0, 0, 0, - DINode::FlagFwdDecl, nullptr, 0, nullptr, nullptr); + DINode::FlagFwdDecl, nullptr, 0, nullptr, nullptr, nullptr); // Create macros for running through all the fields except Identifier and Flags. #define FOR_EACH_MDFIELD() \ @@ -141,7 +141,7 @@ TEST(DebugTypeODRUniquingTest, buildODRTypeFields) { DICompositeType::buildODRType( Context, UUID, Tag, Name, File, Line, Scope, BaseType, SizeInBits, AlignInBits, OffsetInBits, DINode::FlagArtificial, - Elements, RuntimeLang, VTableHolder, TemplateParams)); + Elements, RuntimeLang, VTableHolder, TemplateParams, nullptr)); // Confirm that all the right fields got updated. #define DO_FOR_FIELD(X) EXPECT_EQ(X, CT.getRaw##X()); diff --git a/unittests/IR/DeferredDominanceTest.cpp b/unittests/IR/DeferredDominanceTest.cpp new file mode 100644 index 000000000000..96156f89a744 --- /dev/null +++ b/unittests/IR/DeferredDominanceTest.cpp @@ -0,0 +1,344 @@ +//===- llvm/unittests/IR/DeferredDominanceTest.cpp - DDT unit tests -------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/AsmParser/Parser.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/SourceMgr.h" +#include "gtest/gtest.h" + +using namespace llvm; + +static std::unique_ptr<Module> makeLLVMModule(LLVMContext &Context, + StringRef ModuleStr) { + SMDiagnostic Err; + std::unique_ptr<Module> M = parseAssemblyString(ModuleStr, Err, Context); + assert(M && "Bad LLVM IR?"); + return M; +} + +TEST(DeferredDominance, BasicOperations) { + StringRef FuncName = "f"; + StringRef ModuleString = + "define i32 @f(i32 %i, i32 *%p) {\n" + " bb0:\n" + " store i32 %i, i32 *%p\n" + " switch i32 %i, label %bb1 [\n" + " i32 0, label %bb2\n" + " i32 1, label %bb2\n" + " i32 2, label %bb3\n" + " ]\n" + " bb1:\n" + " ret i32 1\n" + " bb2:\n" + " ret i32 2\n" + " bb3:\n" + " ret i32 3\n" + "}\n"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + ASSERT_NE(F, nullptr) << "Couldn't get function " << FuncName << "."; + + // Make the DDT. + DominatorTree DT(*F); + DeferredDominance DDT(DT); + ASSERT_TRUE(DDT.flush().verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + BasicBlock *BB2 = &*FI++; + BasicBlock *BB3 = &*FI++; + + // Test discards of invalid self-domination updates. These use the single + // short-hand interface but are still queued inside DDT. + DDT.deleteEdge(BB0, BB0); + DDT.insertEdge(BB1, BB1); + + // Delete edge bb0 -> bb3 and push the update twice to verify duplicate + // entries are discarded. + std::vector<DominatorTree::UpdateType> Updates; + Updates.reserve(4); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + + // Unnecessary Insert: no edge bb1 -> bb2 after change to bb0. + Updates.push_back({DominatorTree::Insert, BB1, BB2}); + // Unnecessary Delete: edge exists bb0 -> bb1 after change to bb0. + Updates.push_back({DominatorTree::Delete, BB0, BB1}); + + // CFG Change: remove edge bb0 -> bb3 and one duplicate edge bb0 -> bb2. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, BB2, ConstantInt::getTrue(F->getContext()), BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + + // Deletion of a BasicBlock is an immediate event. We remove all uses to the + // contained Instructions and change the Terminator to "unreachable" when + // queued for deletion. Its parent is still F until DDT.flush() is called. We + // don't defer this action because it can cause problems for other transforms + // or analysis as it's part of the actual CFG. We only defer updates to the + // DominatorTree. This code will crash if it is placed before the + // BranchInst::Create() call above. + ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator())); + EXPECT_FALSE(DDT.pendingDeletedBB(BB3)); + DDT.deleteBB(BB3); + EXPECT_TRUE(DDT.pendingDeletedBB(BB3)); + ASSERT_TRUE(isa<UnreachableInst>(BB3->getTerminator())); + EXPECT_EQ(BB3->getParent(), F); + + // Verify. Updates to DDT must be applied *after* all changes to the CFG + // (including block deletion). + DDT.applyUpdates(Updates); + ASSERT_TRUE(DDT.flush().verify()); +} + +TEST(DeferredDominance, PairedUpdate) { + StringRef FuncName = "f"; + StringRef ModuleString = + "define i32 @f(i32 %i, i32 *%p) {\n" + " bb0:\n" + " store i32 %i, i32 *%p\n" + " switch i32 %i, label %bb1 [\n" + " i32 0, label %bb2\n" + " i32 1, label %bb2\n" + " ]\n" + " bb1:\n" + " ret i32 1\n" + " bb2:\n" + " ret i32 2\n" + "}\n"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + ASSERT_NE(F, nullptr) << "Couldn't get function " << FuncName << "."; + + // Make the DDT. + DominatorTree DT(*F); + DeferredDominance DDT(DT); + ASSERT_TRUE(DDT.flush().verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + BasicBlock *BB2 = &*FI++; + + // CFG Change: only edge from bb0 is bb0 -> bb1. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 3u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u); + + // Must be done after the CFG change. The applyUpdate() routine analyzes the + // current state of the CFG. + DDT.deleteEdge(BB0, BB2); + + // CFG Change: bb0 now has bb0 -> bb1 and bb0 -> bb2. + // With this change no dominance has been altered from the original IR. DT + // doesn't care if the type of TerminatorInstruction changed, only if the + // unique edges have. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, BB2, ConstantInt::getTrue(F->getContext()), BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + + // Must be done after the CFG change. The applyUpdate() routine analyzes the + // current state of the CFG. This DDT update pairs with the previous one and + // is cancelled out before ever applying updates to DT. + DDT.insertEdge(BB0, BB2); + + // Test the empty DeletedBB list. + EXPECT_FALSE(DDT.pendingDeletedBB(BB0)); + EXPECT_FALSE(DDT.pendingDeletedBB(BB1)); + EXPECT_FALSE(DDT.pendingDeletedBB(BB2)); + + // The DT has no changes, this flush() simply returns a reference to the + // internal DT calculated at the beginning of this test. + ASSERT_TRUE(DDT.flush().verify()); +} + +TEST(DeferredDominance, ReplaceEntryBB) { + StringRef FuncName = "f"; + StringRef ModuleString = + "define i32 @f() {\n" + "bb0:\n" + " br label %bb1\n" + " bb1:\n" + " ret i32 1\n" + "}\n"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + ASSERT_NE(F, nullptr) << "Couldn't get function " << FuncName << "."; + + // Make the DDT. + DominatorTree DT(*F); + DeferredDominance DDT(DT); + ASSERT_TRUE(DDT.flush().verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + + // Add a block as the new function entry BB. We also link it to BB0. + BasicBlock *NewEntry = + BasicBlock::Create(F->getContext(), "new_entry", F, BB0); + BranchInst::Create(BB0, NewEntry); + EXPECT_EQ(F->begin()->getName(), NewEntry->getName()); + EXPECT_TRUE(&F->getEntryBlock() == NewEntry); + + // Insert the new edge between new_eentry -> bb0. Without this the + // recalculate() call below will not actually recalculate the DT as there + // are no changes pending and no blocks deleted. + DDT.insertEdge(NewEntry, BB0); + + // Changing the Entry BB requires a full recalulation. + DDT.recalculate(*F); + ASSERT_TRUE(DDT.flush().verify()); + + // CFG Change: remove new_edge -> bb0 and redirect to new_edge -> bb1. + EXPECT_EQ(NewEntry->getTerminator()->getNumSuccessors(), 1u); + NewEntry->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, NewEntry); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u); + + // Update the DDT. At this point bb0 now has no predecessors but is still a + // Child of F. + DDT.applyUpdates({{DominatorTree::Delete, NewEntry, BB0}, + {DominatorTree::Insert, NewEntry, BB1}}); + ASSERT_TRUE(DDT.flush().verify()); + + // Now remove bb0 from F. + ASSERT_FALSE(isa<UnreachableInst>(BB0->getTerminator())); + EXPECT_FALSE(DDT.pendingDeletedBB(BB0)); + DDT.deleteBB(BB0); + EXPECT_TRUE(DDT.pendingDeletedBB(BB0)); + ASSERT_TRUE(isa<UnreachableInst>(BB0->getTerminator())); + EXPECT_EQ(BB0->getParent(), F); + + // Perform a full recalculation of the DDT. It is not necessary here but we + // do this to test the case when there are no pending DT updates but there are + // pending deleted BBs. + DDT.recalculate(*F); + ASSERT_TRUE(DDT.flush().verify()); +} + +TEST(DeferredDominance, InheritedPreds) { + StringRef FuncName = "f"; + StringRef ModuleString = + "define i32 @f(i32 %i, i32 *%p) {\n" + " bb0:\n" + " store i32 %i, i32 *%p\n" + " switch i32 %i, label %bb1 [\n" + " i32 2, label %bb2\n" + " i32 3, label %bb3\n" + " ]\n" + " bb1:\n" + " br label %bb3\n" + " bb2:\n" + " br label %bb3\n" + " bb3:\n" + " ret i32 3\n" + "}\n"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + ASSERT_NE(F, nullptr) << "Couldn't get function " << FuncName << "."; + + // Make the DDT. + DominatorTree DT(*F); + DeferredDominance DDT(DT); + ASSERT_TRUE(DDT.flush().verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + BasicBlock *BB2 = &*FI++; + BasicBlock *BB3 = &*FI++; + + // There are several CFG locations where we have: + // + // pred1..predN + // | | + // +> curr <+ converted into: pred1..predN curr + // | | | + // v +> succ <+ + // succ + // + // There is a specific shape of this we have to be careful of: + // + // pred1..predN + // || | + // |+> curr <+ converted into: pred1..predN curr + // | | | | + // | v +> succ <+ + // +-> succ + // + // While the final CFG form is functionally identical the updates to + // DDT are not. In the first case we must have DDT.insertEdge(Pred1, Succ) + // while in the latter case we must *NOT* have DDT.insertEdge(Pred1, Succ). + + // CFG Change: bb0 now only has bb0 -> bb1 and bb0 -> bb3. We are preparing to + // remove bb2. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 3u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, BB3, ConstantInt::getTrue(F->getContext()), BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + + // Remove bb2 from F. This has to happen before the call to applyUpdates() for + // DDT to detect there is no longer an edge between bb2 -> bb3. The deleteBB() + // method converts bb2's TI into "unreachable". + ASSERT_FALSE(isa<UnreachableInst>(BB2->getTerminator())); + EXPECT_FALSE(DDT.pendingDeletedBB(BB2)); + DDT.deleteBB(BB2); + EXPECT_TRUE(DDT.pendingDeletedBB(BB2)); + ASSERT_TRUE(isa<UnreachableInst>(BB2->getTerminator())); + EXPECT_EQ(BB2->getParent(), F); + + // Queue up the DDT updates. + std::vector<DominatorTree::UpdateType> Updates; + Updates.reserve(4); + Updates.push_back({DominatorTree::Delete, BB0, BB2}); + Updates.push_back({DominatorTree::Delete, BB2, BB3}); + + // Handle the specific shape case next. + // CFG Change: bb0 now only branches to bb3. We are preparing to remove bb1. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB3, BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u); + + // Remove bb1 from F. This has to happen before the call to applyUpdates() for + // DDT to detect there is no longer an edge between bb1 -> bb3. The deleteBB() + // method converts bb1's TI into "unreachable". + ASSERT_FALSE(isa<UnreachableInst>(BB1->getTerminator())); + EXPECT_FALSE(DDT.pendingDeletedBB(BB1)); + DDT.deleteBB(BB1); + EXPECT_TRUE(DDT.pendingDeletedBB(BB1)); + ASSERT_TRUE(isa<UnreachableInst>(BB1->getTerminator())); + EXPECT_EQ(BB1->getParent(), F); + + // Update the DDT. In this case we don't call DDT.insertEdge(BB0, BB3) because + // the edge previously existed at the start of this test when DT was first + // created. + Updates.push_back({DominatorTree::Delete, BB0, BB1}); + Updates.push_back({DominatorTree::Delete, BB1, BB3}); + + // Verify everything. + DDT.applyUpdates(Updates); + ASSERT_TRUE(DDT.flush().verify()); +} diff --git a/unittests/IR/DomTreeUpdaterTest.cpp b/unittests/IR/DomTreeUpdaterTest.cpp new file mode 100644 index 000000000000..03b6b529fd8a --- /dev/null +++ b/unittests/IR/DomTreeUpdaterTest.cpp @@ -0,0 +1,701 @@ +//==- llvm/unittests/IR/DomTreeUpdaterTest.cpp - DomTreeUpdater unit tests ===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/IR/DomTreeUpdater.h" +#include "llvm/Analysis/PostDominators.h" +#include "llvm/AsmParser/Parser.h" +#include "llvm/IR/Constants.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "llvm/Support/SourceMgr.h" +#include "gtest/gtest.h" +#include <algorithm> + +using namespace llvm; + +static std::unique_ptr<Module> makeLLVMModule(LLVMContext &Context, + StringRef ModuleStr) { + SMDiagnostic Err; + std::unique_ptr<Module> M = parseAssemblyString(ModuleStr, Err, Context); + assert(M && "Bad LLVM IR?"); + return M; +} + +TEST(DomTreeUpdater, EagerUpdateBasicOperations) { + StringRef FuncName = "f"; + StringRef ModuleString = R"( + define i32 @f(i32 %i, i32 *%p) { + bb0: + store i32 %i, i32 *%p + switch i32 %i, label %bb1 [ + i32 1, label %bb2 + i32 2, label %bb3 + ] + bb1: + ret i32 1 + bb2: + ret i32 2 + bb3: + ret i32 3 + })"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + + // Make the DomTreeUpdater. + DominatorTree DT(*F); + PostDominatorTree PDT(*F); + DomTreeUpdater DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Eager); + + ASSERT_TRUE(DTU.hasDomTree()); + ASSERT_TRUE(DTU.hasPostDomTree()); + ASSERT_TRUE(DTU.isEager()); + ASSERT_FALSE(DTU.isLazy()); + ASSERT_TRUE(DTU.getDomTree().verify()); + ASSERT_TRUE(DTU.getPostDomTree().verify()); + ASSERT_FALSE(DTU.hasPendingUpdates()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + BasicBlock *BB2 = &*FI++; + BasicBlock *BB3 = &*FI++; + SwitchInst *SI = dyn_cast<SwitchInst>(BB0->getTerminator()); + ASSERT_NE(SI, nullptr) << "Couldn't get SwitchInst."; + + DTU.insertEdgeRelaxed(BB0, BB0); + DTU.deleteEdgeRelaxed(BB0, BB0); + + // Delete edge bb0 -> bb3 and push the update twice to verify duplicate + // entries are discarded. + std::vector<DominatorTree::UpdateType> Updates; + Updates.reserve(4); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + + // Invalid Insert: no edge bb1 -> bb2 after change to bb0. + Updates.push_back({DominatorTree::Insert, BB1, BB2}); + // Invalid Delete: edge exists bb0 -> bb1 after change to bb0. + Updates.push_back({DominatorTree::Delete, BB0, BB1}); + + // CFG Change: remove edge bb0 -> bb3. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 3u); + BB3->removePredecessor(BB0); + for (auto i = SI->case_begin(), e = SI->case_end(); i != e; ++i) { + if (i->getCaseSuccessor() == BB3) { + SI->removeCase(i); + break; + } + } + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + // Deletion of a BasicBlock is an immediate event. We remove all uses to the + // contained Instructions and change the Terminator to "unreachable" when + // queued for deletion. + ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator())); + EXPECT_FALSE(DTU.isBBPendingDeletion(BB3)); + DTU.applyUpdates(Updates, /*ForceRemoveDuplicates*/ true); + ASSERT_FALSE(DTU.hasPendingUpdates()); + + // Invalid Insert: no edge bb1 -> bb2 after change to bb0. + DTU.insertEdgeRelaxed(BB1, BB2); + // Invalid Delete: edge exists bb0 -> bb1 after change to bb0. + DTU.deleteEdgeRelaxed(BB0, BB1); + + // DTU working with Eager UpdateStrategy does not need to flush. + ASSERT_TRUE(DT.verify()); + ASSERT_TRUE(PDT.verify()); + + // Test callback utils. + ASSERT_EQ(BB3->getParent(), F); + DTU.callbackDeleteBB(BB3, + [&F](BasicBlock *BB) { ASSERT_NE(BB->getParent(), F); }); + + ASSERT_TRUE(DT.verify()); + ASSERT_TRUE(PDT.verify()); + ASSERT_FALSE(DTU.hasPendingUpdates()); + + // Unnecessary flush() test + DTU.flush(); + EXPECT_TRUE(DT.verify()); + EXPECT_TRUE(PDT.verify()); + + // Remove all case branch to BB2 to test Eager recalculation. + // Code section from llvm::ConstantFoldTerminator + for (auto i = SI->case_begin(), e = SI->case_end(); i != e;) { + if (i->getCaseSuccessor() == BB2) { + // Remove this entry. + BB2->removePredecessor(BB0); + i = SI->removeCase(i); + e = SI->case_end(); + } else + ++i; + } + ASSERT_FALSE(DT.verify()); + ASSERT_FALSE(PDT.verify()); + DTU.recalculate(*F); + ASSERT_TRUE(DT.verify()); + ASSERT_TRUE(PDT.verify()); +} + +TEST(DomTreeUpdater, EagerUpdateReplaceEntryBB) { + StringRef FuncName = "f"; + StringRef ModuleString = R"( + define i32 @f() { + bb0: + br label %bb1 + bb1: + ret i32 1 + } + )"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + + // Make the DTU. + DominatorTree DT(*F); + PostDominatorTree PDT(*F); + DomTreeUpdater DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Eager); + ASSERT_TRUE(DTU.hasDomTree()); + ASSERT_TRUE(DTU.hasPostDomTree()); + ASSERT_TRUE(DTU.isEager()); + ASSERT_FALSE(DTU.isLazy()); + ASSERT_TRUE(DT.verify()); + ASSERT_TRUE(PDT.verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + + // Add a block as the new function entry BB. We also link it to BB0. + BasicBlock *NewEntry = + BasicBlock::Create(F->getContext(), "new_entry", F, BB0); + BranchInst::Create(BB0, NewEntry); + EXPECT_EQ(F->begin()->getName(), NewEntry->getName()); + EXPECT_TRUE(&F->getEntryBlock() == NewEntry); + + DTU.insertEdgeRelaxed(NewEntry, BB0); + + // Changing the Entry BB requires a full recalculation of DomTree. + DTU.recalculate(*F); + ASSERT_TRUE(DT.verify()); + ASSERT_TRUE(PDT.verify()); + + // CFG Change: remove new_edge -> bb0 and redirect to new_edge -> bb1. + EXPECT_EQ(NewEntry->getTerminator()->getNumSuccessors(), 1u); + NewEntry->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, NewEntry); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u); + + // Update the DTU. At this point bb0 now has no predecessors but is still a + // Child of F. + DTU.applyUpdates({{DominatorTree::Delete, NewEntry, BB0}, + {DominatorTree::Insert, NewEntry, BB1}}); + ASSERT_TRUE(DT.verify()); + ASSERT_TRUE(PDT.verify()); + + // Now remove bb0 from F. + ASSERT_FALSE(isa<UnreachableInst>(BB0->getTerminator())); + EXPECT_FALSE(DTU.isBBPendingDeletion(BB0)); + DTU.deleteBB(BB0); + ASSERT_TRUE(DT.verify()); + ASSERT_TRUE(PDT.verify()); +} + +TEST(DomTreeUpdater, LazyUpdateDTBasicOperations) { + StringRef FuncName = "f"; + StringRef ModuleString = R"( + define i32 @f(i32 %i, i32 *%p) { + bb0: + store i32 %i, i32 *%p + switch i32 %i, label %bb1 [ + i32 0, label %bb2 + i32 1, label %bb2 + i32 2, label %bb3 + ] + bb1: + ret i32 1 + bb2: + ret i32 2 + bb3: + ret i32 3 + } + )"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + + // Make the DTU. + DominatorTree DT(*F); + PostDominatorTree *PDT = nullptr; + DomTreeUpdater DTU(&DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy); + ASSERT_TRUE(DTU.hasDomTree()); + ASSERT_FALSE(DTU.hasPostDomTree()); + ASSERT_FALSE(DTU.isEager()); + ASSERT_TRUE(DTU.isLazy()); + ASSERT_TRUE(DTU.getDomTree().verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + BasicBlock *BB2 = &*FI++; + BasicBlock *BB3 = &*FI++; + + // Test discards of self-domination update. + DTU.deleteEdge(BB0, BB0); + ASSERT_FALSE(DTU.hasPendingDomTreeUpdates()); + + // Delete edge bb0 -> bb3 and push the update twice to verify duplicate + // entries are discarded. + std::vector<DominatorTree::UpdateType> Updates; + Updates.reserve(4); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + + // Invalid Insert: no edge bb1 -> bb2 after change to bb0. + Updates.push_back({DominatorTree::Insert, BB1, BB2}); + // Invalid Delete: edge exists bb0 -> bb1 after change to bb0. + Updates.push_back({DominatorTree::Delete, BB0, BB1}); + + // CFG Change: remove edge bb0 -> bb3 and one duplicate edge bb0 -> bb2. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, BB2, ConstantInt::getTrue(F->getContext()), BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + + // Verify. Updates to DTU must be applied *after* all changes to the CFG + // (including block deletion). + DTU.applyUpdates(Updates); + ASSERT_TRUE(DTU.getDomTree().verify()); + + // Deletion of a BasicBlock is an immediate event. We remove all uses to the + // contained Instructions and change the Terminator to "unreachable" when + // queued for deletion. Its parent is still F until all the pending updates + // are applied to all trees held by the DomTreeUpdater (DomTree/PostDomTree). + // We don't defer this action because it can cause problems for other + // transforms or analysis as it's part of the actual CFG. We only defer + // updates to the DominatorTrees. This code will crash if it is placed before + // the BranchInst::Create() call above. After a deletion of a BasicBlock. Only + // an explicit flush event can trigger the flushing of deleteBBs. Because some + // passes using Lazy UpdateStrategy rely on this behavior. + + ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator())); + EXPECT_FALSE(DTU.isBBPendingDeletion(BB3)); + EXPECT_FALSE(DTU.hasPendingDeletedBB()); + DTU.deleteBB(BB3); + EXPECT_TRUE(DTU.isBBPendingDeletion(BB3)); + EXPECT_TRUE(DTU.hasPendingDeletedBB()); + ASSERT_TRUE(isa<UnreachableInst>(BB3->getTerminator())); + EXPECT_EQ(BB3->getParent(), F); + DTU.recalculate(*F); + EXPECT_FALSE(DTU.hasPendingDeletedBB()); +} + +TEST(DomTreeUpdater, LazyUpdateDTInheritedPreds) { + StringRef FuncName = "f"; + StringRef ModuleString = R"( + define i32 @f(i32 %i, i32 *%p) { + bb0: + store i32 %i, i32 *%p + switch i32 %i, label %bb1 [ + i32 2, label %bb2 + i32 3, label %bb3 + ] + bb1: + br label %bb3 + bb2: + br label %bb3 + bb3: + ret i32 3 + } + )"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + + // Make the DTU. + DominatorTree DT(*F); + PostDominatorTree *PDT = nullptr; + DomTreeUpdater DTU(&DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy); + ASSERT_TRUE(DTU.hasDomTree()); + ASSERT_FALSE(DTU.hasPostDomTree()); + ASSERT_FALSE(DTU.isEager()); + ASSERT_TRUE(DTU.isLazy()); + ASSERT_TRUE(DTU.getDomTree().verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + BasicBlock *BB2 = &*FI++; + BasicBlock *BB3 = &*FI++; + + // There are several CFG locations where we have: + // + // pred1..predN + // | | + // +> curr <+ converted into: pred1..predN curr + // | | | + // v +> succ <+ + // succ + // + // There is a specific shape of this we have to be careful of: + // + // pred1..predN + // || | + // |+> curr <+ converted into: pred1..predN curr + // | | | | + // | v +> succ <+ + // +-> succ + // + // While the final CFG form is functionally identical the updates to + // DTU are not. In the first case we must have DTU.insertEdge(Pred1, Succ) + // while in the latter case we must *NOT* have DTU.insertEdge(Pred1, Succ). + + // CFG Change: bb0 now only has bb0 -> bb1 and bb0 -> bb3. We are preparing to + // remove bb2. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 3u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, BB3, ConstantInt::getTrue(F->getContext()), BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + + // Test callback utils. + std::vector<BasicBlock *> BasicBlocks; + BasicBlocks.push_back(BB1); + BasicBlocks.push_back(BB2); + auto Eraser = [&](BasicBlock *BB) { + BasicBlocks.erase( + std::remove_if(BasicBlocks.begin(), BasicBlocks.end(), + [&](const BasicBlock *i) { return i == BB; }), + BasicBlocks.end()); + }; + ASSERT_EQ(BasicBlocks.size(), static_cast<size_t>(2)); + // Remove bb2 from F. This has to happen before the call to applyUpdates() for + // DTU to detect there is no longer an edge between bb2 -> bb3. The deleteBB() + // method converts bb2's TI into "unreachable". + ASSERT_FALSE(isa<UnreachableInst>(BB2->getTerminator())); + EXPECT_FALSE(DTU.isBBPendingDeletion(BB2)); + DTU.callbackDeleteBB(BB2, Eraser); + EXPECT_TRUE(DTU.isBBPendingDeletion(BB2)); + ASSERT_TRUE(isa<UnreachableInst>(BB2->getTerminator())); + EXPECT_EQ(BB2->getParent(), F); + + // Queue up the DTU updates. + std::vector<DominatorTree::UpdateType> Updates; + Updates.reserve(4); + Updates.push_back({DominatorTree::Delete, BB0, BB2}); + Updates.push_back({DominatorTree::Delete, BB2, BB3}); + + // Handle the specific shape case next. + // CFG Change: bb0 now only branches to bb3. We are preparing to remove bb1. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB3, BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u); + + // Remove bb1 from F. This has to happen before the call to applyUpdates() for + // DTU to detect there is no longer an edge between bb1 -> bb3. The deleteBB() + // method converts bb1's TI into "unreachable". + ASSERT_FALSE(isa<UnreachableInst>(BB1->getTerminator())); + EXPECT_FALSE(DTU.isBBPendingDeletion(BB1)); + DTU.callbackDeleteBB(BB1, Eraser); + EXPECT_TRUE(DTU.isBBPendingDeletion(BB1)); + ASSERT_TRUE(isa<UnreachableInst>(BB1->getTerminator())); + EXPECT_EQ(BB1->getParent(), F); + + // Update the DTU. In this case we don't call DTU.insertEdge(BB0, BB3) because + // the edge previously existed at the start of this test when DT was first + // created. + Updates.push_back({DominatorTree::Delete, BB0, BB1}); + Updates.push_back({DominatorTree::Delete, BB1, BB3}); + + // Verify everything. + DTU.applyUpdates(Updates); + ASSERT_EQ(BasicBlocks.size(), static_cast<size_t>(2)); + DTU.flush(); + ASSERT_EQ(BasicBlocks.size(), static_cast<size_t>(0)); + ASSERT_TRUE(DT.verify()); +} + +TEST(DomTreeUpdater, LazyUpdateBasicOperations) { + StringRef FuncName = "f"; + StringRef ModuleString = R"( + define i32 @f(i32 %i, i32 *%p) { + bb0: + store i32 %i, i32 *%p + switch i32 %i, label %bb1 [ + i32 0, label %bb2 + i32 1, label %bb2 + i32 2, label %bb3 + ] + bb1: + ret i32 1 + bb2: + ret i32 2 + bb3: + ret i32 3 + } + )"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + + // Make the DTU. + DominatorTree DT(*F); + PostDominatorTree PDT(*F); + DomTreeUpdater DTU(&DT, &PDT, DomTreeUpdater::UpdateStrategy::Lazy); + ASSERT_TRUE(DTU.hasDomTree()); + ASSERT_TRUE(DTU.hasPostDomTree()); + ASSERT_FALSE(DTU.isEager()); + ASSERT_TRUE(DTU.isLazy()); + ASSERT_TRUE(DTU.getDomTree().verify()); + ASSERT_TRUE(DTU.getPostDomTree().verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + BasicBlock *BB2 = &*FI++; + BasicBlock *BB3 = &*FI++; + // Test discards of self-domination update. + DTU.deleteEdge(BB0, BB0); + + // Delete edge bb0 -> bb3 and push the update twice to verify duplicate + // entries are discarded. + std::vector<DominatorTree::UpdateType> Updates; + Updates.reserve(4); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + + // Unnecessary Insert: no edge bb1 -> bb2 after change to bb0. + Updates.push_back({DominatorTree::Insert, BB1, BB2}); + // Unnecessary Delete: edge exists bb0 -> bb1 after change to bb0. + Updates.push_back({DominatorTree::Delete, BB0, BB1}); + + // CFG Change: remove edge bb0 -> bb3 and one duplicate edge bb0 -> bb2. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u); + BB0->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, BB2, ConstantInt::getTrue(F->getContext()), BB0); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 2u); + + // Deletion of a BasicBlock is an immediate event. We remove all uses to the + // contained Instructions and change the Terminator to "unreachable" when + // queued for deletion. Its parent is still F until DTU.flushDomTree is + // called. We don't defer this action because it can cause problems for other + // transforms or analysis as it's part of the actual CFG. We only defer + // updates to the DominatorTree. This code will crash if it is placed before + // the BranchInst::Create() call above. + bool CallbackFlag = false; + ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator())); + EXPECT_FALSE(DTU.isBBPendingDeletion(BB3)); + DTU.callbackDeleteBB(BB3, [&](BasicBlock *) { CallbackFlag = true; }); + EXPECT_TRUE(DTU.isBBPendingDeletion(BB3)); + ASSERT_TRUE(isa<UnreachableInst>(BB3->getTerminator())); + EXPECT_EQ(BB3->getParent(), F); + + // Verify. Updates to DTU must be applied *after* all changes to the CFG + // (including block deletion). + DTU.applyUpdates(Updates); + ASSERT_TRUE(DTU.getDomTree().verify()); + ASSERT_TRUE(DTU.hasPendingUpdates()); + ASSERT_TRUE(DTU.hasPendingPostDomTreeUpdates()); + ASSERT_FALSE(DTU.hasPendingDomTreeUpdates()); + ASSERT_TRUE(DTU.hasPendingDeletedBB()); + ASSERT_TRUE(DTU.getPostDomTree().verify()); + ASSERT_FALSE(DTU.hasPendingUpdates()); + ASSERT_FALSE(DTU.hasPendingPostDomTreeUpdates()); + ASSERT_FALSE(DTU.hasPendingDomTreeUpdates()); + ASSERT_FALSE(DTU.hasPendingDeletedBB()); + ASSERT_EQ(CallbackFlag, true); +} + +TEST(DomTreeUpdater, LazyUpdateReplaceEntryBB) { + StringRef FuncName = "f"; + StringRef ModuleString = R"( + define i32 @f() { + bb0: + br label %bb1 + bb1: + ret i32 1 + } + )"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + + // Make the DTU. + DominatorTree DT(*F); + PostDominatorTree PDT(*F); + DomTreeUpdater DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy); + ASSERT_TRUE(DTU.hasDomTree()); + ASSERT_TRUE(DTU.hasPostDomTree()); + ASSERT_FALSE(DTU.isEager()); + ASSERT_TRUE(DTU.isLazy()); + ASSERT_TRUE(DTU.getDomTree().verify()); + ASSERT_TRUE(DTU.getPostDomTree().verify()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + BasicBlock *BB1 = &*FI++; + + // Add a block as the new function entry BB. We also link it to BB0. + BasicBlock *NewEntry = + BasicBlock::Create(F->getContext(), "new_entry", F, BB0); + BranchInst::Create(BB0, NewEntry); + EXPECT_EQ(F->begin()->getName(), NewEntry->getName()); + EXPECT_TRUE(&F->getEntryBlock() == NewEntry); + + // Insert the new edge between new_entry -> bb0. Without this the + // recalculate() call below will not actually recalculate the DT as there + // are no changes pending and no blocks deleted. + DTU.insertEdge(NewEntry, BB0); + + // Changing the Entry BB requires a full recalculation. + DTU.recalculate(*F); + ASSERT_TRUE(DTU.getDomTree().verify()); + ASSERT_TRUE(DTU.getPostDomTree().verify()); + + // CFG Change: remove new_edge -> bb0 and redirect to new_edge -> bb1. + EXPECT_EQ(NewEntry->getTerminator()->getNumSuccessors(), 1u); + NewEntry->getTerminator()->eraseFromParent(); + BranchInst::Create(BB1, NewEntry); + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 1u); + + // Update the DTU. At this point bb0 now has no predecessors but is still a + // Child of F. + DTU.applyUpdates({{DominatorTree::Delete, NewEntry, BB0}, + {DominatorTree::Insert, NewEntry, BB1}}); + DTU.flush(); + ASSERT_TRUE(DT.verify()); + ASSERT_TRUE(PDT.verify()); + + // Now remove bb0 from F. + ASSERT_FALSE(isa<UnreachableInst>(BB0->getTerminator())); + EXPECT_FALSE(DTU.isBBPendingDeletion(BB0)); + DTU.deleteBB(BB0); + EXPECT_TRUE(DTU.isBBPendingDeletion(BB0)); + ASSERT_TRUE(isa<UnreachableInst>(BB0->getTerminator())); + EXPECT_EQ(BB0->getParent(), F); + + // Perform a full recalculation of the DTU. It is not necessary here but we + // do this to test the case when there are no pending DT updates but there are + // pending deleted BBs. + ASSERT_TRUE(DTU.hasPendingDeletedBB()); + DTU.recalculate(*F); + ASSERT_FALSE(DTU.hasPendingDeletedBB()); +} + +TEST(DomTreeUpdater, LazyUpdateStepTest) { + // This test focus on testing a DTU holding both trees applying multiple + // updates and DT/PDT not flushed together. + StringRef FuncName = "f"; + StringRef ModuleString = R"( + define i32 @f(i32 %i, i32 *%p) { + bb0: + store i32 %i, i32 *%p + switch i32 %i, label %bb1 [ + i32 0, label %bb1 + i32 1, label %bb2 + i32 2, label %bb3 + i32 3, label %bb1 + ] + bb1: + ret i32 1 + bb2: + ret i32 2 + bb3: + ret i32 3 + } + )"; + // Make the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + Function *F = M->getFunction(FuncName); + + // Make the DomTreeUpdater. + DominatorTree DT(*F); + PostDominatorTree PDT(*F); + DomTreeUpdater DTU(DT, PDT, DomTreeUpdater::UpdateStrategy::Lazy); + + ASSERT_TRUE(DTU.hasDomTree()); + ASSERT_TRUE(DTU.hasPostDomTree()); + ASSERT_FALSE(DTU.isEager()); + ASSERT_TRUE(DTU.isLazy()); + ASSERT_TRUE(DTU.getDomTree().verify()); + ASSERT_TRUE(DTU.getPostDomTree().verify()); + ASSERT_FALSE(DTU.hasPendingUpdates()); + + Function::iterator FI = F->begin(); + BasicBlock *BB0 = &*FI++; + FI++; + BasicBlock *BB2 = &*FI++; + BasicBlock *BB3 = &*FI++; + SwitchInst *SI = dyn_cast<SwitchInst>(BB0->getTerminator()); + ASSERT_NE(SI, nullptr) << "Couldn't get SwitchInst."; + + // Delete edge bb0 -> bb3 and push the update twice to verify duplicate + // entries are discarded. + std::vector<DominatorTree::UpdateType> Updates; + Updates.reserve(1); + Updates.push_back({DominatorTree::Delete, BB0, BB3}); + + // CFG Change: remove edge bb0 -> bb3. + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 5u); + BB3->removePredecessor(BB0); + for (auto i = SI->case_begin(), e = SI->case_end(); i != e; ++i) { + if (i->getCaseIndex() == 2) { + SI->removeCase(i); + break; + } + } + EXPECT_EQ(BB0->getTerminator()->getNumSuccessors(), 4u); + // Deletion of a BasicBlock is an immediate event. We remove all uses to the + // contained Instructions and change the Terminator to "unreachable" when + // queued for deletion. + ASSERT_FALSE(isa<UnreachableInst>(BB3->getTerminator())); + EXPECT_FALSE(DTU.isBBPendingDeletion(BB3)); + DTU.applyUpdates(Updates); + + // Only flush DomTree. + ASSERT_TRUE(DTU.getDomTree().verify()); + ASSERT_TRUE(DTU.hasPendingPostDomTreeUpdates()); + ASSERT_FALSE(DTU.hasPendingDomTreeUpdates()); + + ASSERT_EQ(BB3->getParent(), F); + DTU.deleteBB(BB3); + + Updates.clear(); + + // Remove all case branch to BB2 to test Eager recalculation. + // Code section from llvm::ConstantFoldTerminator + for (auto i = SI->case_begin(), e = SI->case_end(); i != e;) { + if (i->getCaseSuccessor() == BB2) { + // Remove this entry. + BB2->removePredecessor(BB0); + i = SI->removeCase(i); + e = SI->case_end(); + Updates.push_back({DominatorTree::Delete, BB0, BB2}); + } else + ++i; + } + + DTU.applyUpdates(Updates); + // flush PostDomTree + ASSERT_TRUE(DTU.getPostDomTree().verify()); + ASSERT_FALSE(DTU.hasPendingPostDomTreeUpdates()); + ASSERT_TRUE(DTU.hasPendingDomTreeUpdates()); + // flush both trees + DTU.flush(); + ASSERT_TRUE(DT.verify()); +} diff --git a/unittests/IR/DominatorTreeBatchUpdatesTest.cpp b/unittests/IR/DominatorTreeBatchUpdatesTest.cpp index 4ad1f69030c1..85308685e5dd 100644 --- a/unittests/IR/DominatorTreeBatchUpdatesTest.cpp +++ b/unittests/IR/DominatorTreeBatchUpdatesTest.cpp @@ -22,13 +22,10 @@ namespace { const auto CFGInsert = CFGBuilder::ActionKind::Insert; const auto CFGDelete = CFGBuilder::ActionKind::Delete; -struct PostDomTree : PostDomTreeBase<BasicBlock> { - PostDomTree(Function &F) { recalculate(F); } -}; using DomUpdate = DominatorTree::UpdateType; static_assert( - std::is_same<DomUpdate, PostDomTree::UpdateType>::value, + std::is_same<DomUpdate, PostDominatorTree::UpdateType>::value, "Trees differing only in IsPostDom should have the same update types"); using DomSNCA = DomTreeBuilder::SemiNCAInfo<DomTreeBuilder::BBDomTree>; using PostDomSNCA = DomTreeBuilder::SemiNCAInfo<DomTreeBuilder::BBPostDomTree>; @@ -62,9 +59,9 @@ TEST(DominatorTreeBatchUpdates, LegalizeDomUpdates) { {Insert, B, D}, {Delete, C, D}, {Delete, A, B}}; SmallVector<DomUpdate, 4> Legalized; DomSNCA::LegalizeUpdates(Updates, Legalized); - DEBUG(dbgs() << "Legalized updates:\t"); - DEBUG(for (auto &U : Legalized) dbgs() << U << ", "); - DEBUG(dbgs() << "\n"); + LLVM_DEBUG(dbgs() << "Legalized updates:\t"); + LLVM_DEBUG(for (auto &U : Legalized) dbgs() << U << ", "); + LLVM_DEBUG(dbgs() << "\n"); EXPECT_EQ(Legalized.size(), 3UL); EXPECT_NE(llvm::find(Legalized, DomUpdate{Insert, B, C}), Legalized.end()); EXPECT_NE(llvm::find(Legalized, DomUpdate{Insert, B, D}), Legalized.end()); @@ -85,9 +82,9 @@ TEST(DominatorTreeBatchUpdates, LegalizePostDomUpdates) { {Insert, B, D}, {Delete, C, D}, {Delete, A, B}}; SmallVector<DomUpdate, 4> Legalized; PostDomSNCA::LegalizeUpdates(Updates, Legalized); - DEBUG(dbgs() << "Legalized postdom updates:\t"); - DEBUG(for (auto &U : Legalized) dbgs() << U << ", "); - DEBUG(dbgs() << "\n"); + LLVM_DEBUG(dbgs() << "Legalized postdom updates:\t"); + LLVM_DEBUG(for (auto &U : Legalized) dbgs() << U << ", "); + LLVM_DEBUG(dbgs() << "\n"); EXPECT_EQ(Legalized.size(), 3UL); EXPECT_NE(llvm::find(Legalized, DomUpdate{Insert, C, B}), Legalized.end()); EXPECT_NE(llvm::find(Legalized, DomUpdate{Insert, D, B}), Legalized.end()); @@ -100,8 +97,8 @@ TEST(DominatorTreeBatchUpdates, SingleInsertion) { DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); - EXPECT_TRUE(DT.verify()); + PostDominatorTree PDT(*Holder.F); + EXPECT_TRUE(PDT.verify()); BasicBlock *B = Builder.getOrAddBlock("B"); BasicBlock *C = Builder.getOrAddBlock("C"); @@ -122,8 +119,8 @@ TEST(DominatorTreeBatchUpdates, SingleDeletion) { DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); - EXPECT_TRUE(DT.verify()); + PostDominatorTree PDT(*Holder.F); + EXPECT_TRUE(PDT.verify()); BasicBlock *B = Builder.getOrAddBlock("B"); BasicBlock *C = Builder.getOrAddBlock("C"); @@ -148,7 +145,7 @@ TEST(DominatorTreeBatchUpdates, FewInsertion) { DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); BasicBlock *B = Builder.getOrAddBlock("B"); @@ -181,7 +178,7 @@ TEST(DominatorTreeBatchUpdates, FewDeletions) { DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); auto Updates = ToDomUpdates(Builder, CFGUpdates); @@ -212,7 +209,7 @@ TEST(DominatorTreeBatchUpdates, InsertDelete) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); while (B.applyUpdate()) @@ -245,7 +242,7 @@ TEST(DominatorTreeBatchUpdates, InsertDeleteExhaustive) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); while (B.applyUpdate()) @@ -258,3 +255,98 @@ TEST(DominatorTreeBatchUpdates, InsertDeleteExhaustive) { EXPECT_TRUE(PDT.verify()); } } + +// These are some odd flowgraphs, usually generated from csmith cases, +// which are difficult on post dom trees. +TEST(DominatorTreeBatchUpdates, InfiniteLoop) { + std::vector<CFGBuilder::Arc> Arcs = { + {"1", "2"}, + {"2", "3"}, + {"3", "6"}, {"3", "5"}, + {"4", "5"}, + {"5", "2"}, + {"6", "3"}, {"6", "4"}}; + + // SplitBlock on 3 -> 5 + std::vector<CFGBuilder::Update> Updates = { + {CFGInsert, {"N", "5"}}, {CFGInsert, {"3", "N"}}, {CFGDelete, {"3", "5"}}}; + + CFGHolder Holder; + CFGBuilder B(Holder.F, Arcs, Updates); + DominatorTree DT(*Holder.F); + EXPECT_TRUE(DT.verify()); + PostDominatorTree PDT(*Holder.F); + EXPECT_TRUE(PDT.verify()); + + while (B.applyUpdate()) + ; + + auto DomUpdates = ToDomUpdates(B, Updates); + DT.applyUpdates(DomUpdates); + EXPECT_TRUE(DT.verify()); + PDT.applyUpdates(DomUpdates); + EXPECT_TRUE(PDT.verify()); +} + +TEST(DominatorTreeBatchUpdates, DeadBlocks) { + std::vector<CFGBuilder::Arc> Arcs = { + {"1", "2"}, + {"2", "3"}, + {"3", "4"}, {"3", "7"}, + {"4", "4"}, + {"5", "6"}, {"5", "7"}, + {"6", "7"}, + {"7", "2"}, {"7", "8"}}; + + // Remove dead 5 and 7, + // plus SplitBlock on 7 -> 8 + std::vector<CFGBuilder::Update> Updates = { + {CFGDelete, {"6", "7"}}, {CFGDelete, {"5", "7"}}, {CFGDelete, {"5", "6"}}, + {CFGInsert, {"N", "8"}}, {CFGInsert, {"7", "N"}}, {CFGDelete, {"7", "8"}}}; + + CFGHolder Holder; + CFGBuilder B(Holder.F, Arcs, Updates); + DominatorTree DT(*Holder.F); + EXPECT_TRUE(DT.verify()); + PostDominatorTree PDT(*Holder.F); + EXPECT_TRUE(PDT.verify()); + + while (B.applyUpdate()) + ; + + auto DomUpdates = ToDomUpdates(B, Updates); + DT.applyUpdates(DomUpdates); + EXPECT_TRUE(DT.verify()); + PDT.applyUpdates(DomUpdates); + EXPECT_TRUE(PDT.verify()); +} + +TEST(DominatorTreeBatchUpdates, InfiniteLoop2) { + std::vector<CFGBuilder::Arc> Arcs = { + {"1", "2"}, + {"2", "6"}, {"2", "3"}, + {"3", "4"}, + {"4", "5"}, {"4", "6"}, + {"5", "4"}, + {"6", "2"}}; + + // SplitBlock on 4 -> 6 + std::vector<CFGBuilder::Update> Updates = { + {CFGInsert, {"N", "6"}}, {CFGInsert, {"4", "N"}}, {CFGDelete, {"4", "6"}}}; + + CFGHolder Holder; + CFGBuilder B(Holder.F, Arcs, Updates); + DominatorTree DT(*Holder.F); + EXPECT_TRUE(DT.verify()); + PostDominatorTree PDT(*Holder.F); + EXPECT_TRUE(PDT.verify()); + + while (B.applyUpdate()) + ; + + auto DomUpdates = ToDomUpdates(B, Updates); + DT.applyUpdates(DomUpdates); + EXPECT_TRUE(DT.verify()); + PDT.applyUpdates(DomUpdates); + EXPECT_TRUE(PDT.verify()); +} diff --git a/unittests/IR/DominatorTreeTest.cpp b/unittests/IR/DominatorTreeTest.cpp index bf5aced49289..cf81623d0d17 100644 --- a/unittests/IR/DominatorTreeTest.cpp +++ b/unittests/IR/DominatorTreeTest.cpp @@ -9,6 +9,7 @@ #include <random> #include "llvm/Analysis/PostDominators.h" +#include "llvm/Analysis/IteratedDominanceFrontier.h" #include "llvm/AsmParser/Parser.h" #include "llvm/IR/Constants.h" #include "llvm/IR/Dominators.h" @@ -21,19 +22,17 @@ using namespace llvm; -struct PostDomTree : PostDomTreeBase<BasicBlock> { - PostDomTree(Function &F) { recalculate(F); } -}; /// Build the dominator tree for the function and run the Test. static void runWithDomTree( Module &M, StringRef FuncName, - function_ref<void(Function &F, DominatorTree *DT, PostDomTree *PDT)> Test) { + function_ref<void(Function &F, DominatorTree *DT, PostDominatorTree *PDT)> + Test) { auto *F = M.getFunction(FuncName); ASSERT_NE(F, nullptr) << "Could not find " << FuncName; // Compute the dominator tree for the function. DominatorTree DT(*F); - PostDomTree PDT(*F); + PostDominatorTree PDT(*F); Test(*F, &DT, &PDT); } @@ -75,7 +74,7 @@ TEST(DominatorTree, Unreachable) { std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); runWithDomTree( - *M, "f", [&](Function &F, DominatorTree *DT, PostDomTree *PDT) { + *M, "f", [&](Function &F, DominatorTree *DT, PostDominatorTree *PDT) { Function::iterator FI = F.begin(); BasicBlock *BB0 = &*FI++; @@ -264,14 +263,14 @@ TEST(DominatorTree, Unreachable) { EXPECT_EQ(DT->getNode(BB4)->getLevel(), 1U); // Change root node - DT->verifyDomTree(); + EXPECT_TRUE(DT->verify()); BasicBlock *NewEntry = BasicBlock::Create(F.getContext(), "new_entry", &F, BB0); BranchInst::Create(BB0, NewEntry); EXPECT_EQ(F.begin()->getName(), NewEntry->getName()); EXPECT_TRUE(&F.getEntryBlock() == NewEntry); DT->setNewRoot(NewEntry); - DT->verifyDomTree(); + EXPECT_TRUE(DT->verify()); }); } @@ -295,7 +294,7 @@ TEST(DominatorTree, NonUniqueEdges) { std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); runWithDomTree( - *M, "f", [&](Function &F, DominatorTree *DT, PostDomTree *PDT) { + *M, "f", [&](Function &F, DominatorTree *DT, PostDominatorTree *PDT) { Function::iterator FI = F.begin(); BasicBlock *BB0 = &*FI++; @@ -359,7 +358,7 @@ TEST(DominatorTree, NonUniqueEdges) { // unreachable Exit // // Both the blocks that end with ret and with unreachable become trivial -// PostDomTree roots, as they have no successors. +// PostDominatorTree roots, as they have no successors. // TEST(DominatorTree, DeletingEdgesIntroducesUnreachables) { StringRef ModuleString = @@ -379,7 +378,7 @@ TEST(DominatorTree, DeletingEdgesIntroducesUnreachables) { std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); runWithDomTree( - *M, "f", [&](Function &F, DominatorTree *DT, PostDomTree *PDT) { + *M, "f", [&](Function &F, DominatorTree *DT, PostDominatorTree *PDT) { Function::iterator FI = F.begin(); FI++; @@ -406,7 +405,7 @@ TEST(DominatorTree, DeletingEdgesIntroducesUnreachables) { DominatorTree NDT(F); EXPECT_EQ(DT->compare(NDT), 0); - PostDomTree NPDT(F); + PostDominatorTree NPDT(F); EXPECT_EQ(PDT->compare(NPDT), 0); }); } @@ -473,7 +472,7 @@ TEST(DominatorTree, DeletingEdgesIntroducesInfiniteLoop) { std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); runWithDomTree( - *M, "f", [&](Function &F, DominatorTree *DT, PostDomTree *PDT) { + *M, "f", [&](Function &F, DominatorTree *DT, PostDominatorTree *PDT) { Function::iterator FI = F.begin(); FI++; @@ -498,7 +497,7 @@ TEST(DominatorTree, DeletingEdgesIntroducesInfiniteLoop) { DominatorTree NDT(F); EXPECT_EQ(DT->compare(NDT), 0); - PostDomTree NPDT(F); + PostDominatorTree NPDT(F); EXPECT_EQ(PDT->compare(NPDT), 0); }); } @@ -562,7 +561,7 @@ TEST(DominatorTree, DeletingEdgesIntroducesInfiniteLoop2) { std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); runWithDomTree( - *M, "f", [&](Function &F, DominatorTree *DT, PostDomTree *PDT) { + *M, "f", [&](Function &F, DominatorTree *DT, PostDominatorTree *PDT) { Function::iterator FI = F.begin(); FI++; @@ -593,11 +592,80 @@ TEST(DominatorTree, DeletingEdgesIntroducesInfiniteLoop2) { DominatorTree NDT(F); EXPECT_EQ(DT->compare(NDT), 0); - PostDomTree NPDT(F); + PostDominatorTree NPDT(F); EXPECT_EQ(PDT->compare(NPDT), 0); }); } +// Verify that the IDF returns blocks in a deterministic way. +// +// Test case: +// +// CFG +// +// (A) +// / \ +// / \ +// (B) (C) +// |\ /| +// | X | +// |/ \| +// (D) (E) +// +// IDF for block B is {D, E}, and the order of blocks in this list is defined by +// their 1) level in dom-tree and 2) DFSIn number if the level is the same. +// +TEST(DominatorTree, IDFDeterminismTest) { + StringRef ModuleString = + "define void @f() {\n" + "A:\n" + " br i1 undef, label %B, label %C\n" + "B:\n" + " br i1 undef, label %D, label %E\n" + "C:\n" + " br i1 undef, label %D, label %E\n" + "D:\n" + " ret void\n" + "E:\n" + " ret void\n" + "}\n"; + + // Parse the module. + LLVMContext Context; + std::unique_ptr<Module> M = makeLLVMModule(Context, ModuleString); + + runWithDomTree( + *M, "f", [&](Function &F, DominatorTree *DT, PostDominatorTree *PDT) { + Function::iterator FI = F.begin(); + + BasicBlock *A = &*FI++; + BasicBlock *B = &*FI++; + BasicBlock *C = &*FI++; + BasicBlock *D = &*FI++; + BasicBlock *E = &*FI++; + (void)C; + + DT->updateDFSNumbers(); + ForwardIDFCalculator IDF(*DT); + SmallPtrSet<BasicBlock *, 1> DefBlocks; + DefBlocks.insert(B); + IDF.setDefiningBlocks(DefBlocks); + + SmallVector<BasicBlock *, 32> IDFBlocks; + SmallPtrSet<BasicBlock *, 32> LiveInBlocks; + IDF.resetLiveInBlocks(); + IDF.calculate(IDFBlocks); + + + EXPECT_EQ(IDFBlocks.size(), 2UL); + EXPECT_EQ(DT->getNode(A)->getDFSNumIn(), 0UL); + EXPECT_EQ(IDFBlocks[0], D); + EXPECT_EQ(IDFBlocks[1], E); + EXPECT_TRUE(DT->getNode(IDFBlocks[0])->getDFSNumIn() < + DT->getNode(IDFBlocks[1])->getDFSNumIn()); + }); +} + namespace { const auto Insert = CFGBuilder::ActionKind::Insert; const auto Delete = CFGBuilder::ActionKind::Delete; @@ -621,7 +689,7 @@ TEST(DominatorTree, InsertReachable) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate; @@ -647,7 +715,7 @@ TEST(DominatorTree, InsertReachable2) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate = B.applyUpdate(); @@ -675,7 +743,7 @@ TEST(DominatorTree, InsertUnreachable) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate; @@ -696,7 +764,7 @@ TEST(DominatorTree, InsertFromUnreachable) { std::vector<CFGBuilder::Update> Updates = {{Insert, {"3", "5"}}}; CFGBuilder B(Holder.F, Arcs, Updates); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate = B.applyUpdate(); @@ -708,7 +776,9 @@ TEST(DominatorTree, InsertFromUnreachable) { PDT.insertEdge(From, To); EXPECT_TRUE(PDT.verify()); EXPECT_TRUE(PDT.getRoots().size() == 2); - EXPECT_NE(PDT.getNode(B.getOrAddBlock("5")), nullptr); + // Make sure we can use a const pointer with getNode. + const BasicBlock *BB5 = B.getOrAddBlock("5"); + EXPECT_NE(PDT.getNode(BB5), nullptr); } TEST(DominatorTree, InsertMixed) { @@ -724,7 +794,7 @@ TEST(DominatorTree, InsertMixed) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate; @@ -754,7 +824,7 @@ TEST(DominatorTree, InsertPermut) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate; @@ -781,7 +851,7 @@ TEST(DominatorTree, DeleteReachable) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate; @@ -807,7 +877,7 @@ TEST(DominatorTree, DeleteUnreachable) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate; @@ -822,36 +892,6 @@ TEST(DominatorTree, DeleteUnreachable) { } } -TEST(DominatorTree, DeletionsInSubtrees) { - CFGHolder Holder; - std::vector<CFGBuilder::Arc> Arcs = {{"0", "1"}, {"1", "2"}, {"1", "3"}, - {"1", "6"}, {"3", "4"}, {"2", "5"}, - {"5", "2"}}; - - // It is possible to perform multiple deletions and inform the - // DominatorTree about them at the same time, if the all of the - // deletions happen in different subtrees. - std::vector<CFGBuilder::Update> Updates = {{Delete, {"1", "2"}}, - {Delete, {"1", "3"}}}; - CFGBuilder B(Holder.F, Arcs, Updates); - DominatorTree DT(*Holder.F); - EXPECT_TRUE(DT.verify()); - - Optional<CFGBuilder::Update> LastUpdate; - while ((LastUpdate = B.applyUpdate())) - ; - - DT.deleteEdge(B.getOrAddBlock("1"), B.getOrAddBlock("2")); - DT.deleteEdge(B.getOrAddBlock("1"), B.getOrAddBlock("3")); - - EXPECT_TRUE(DT.verify()); - EXPECT_EQ(DT.getNode(B.getOrAddBlock("2")), nullptr); - EXPECT_EQ(DT.getNode(B.getOrAddBlock("3")), nullptr); - EXPECT_EQ(DT.getNode(B.getOrAddBlock("4")), nullptr); - EXPECT_EQ(DT.getNode(B.getOrAddBlock("5")), nullptr); - EXPECT_NE(DT.getNode(B.getOrAddBlock("6")), nullptr); -} - TEST(DominatorTree, InsertDelete) { std::vector<CFGBuilder::Arc> Arcs = { {"1", "2"}, {"2", "3"}, {"3", "4"}, {"4", "5"}, {"5", "6"}, {"5", "7"}, @@ -867,7 +907,7 @@ TEST(DominatorTree, InsertDelete) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate; @@ -905,7 +945,7 @@ TEST(DominatorTree, InsertDeleteExhaustive) { CFGBuilder B(Holder.F, Arcs, Updates); DominatorTree DT(*Holder.F); EXPECT_TRUE(DT.verify()); - PostDomTree PDT(*Holder.F); + PostDominatorTree PDT(*Holder.F); EXPECT_TRUE(PDT.verify()); Optional<CFGBuilder::Update> LastUpdate; @@ -925,3 +965,28 @@ TEST(DominatorTree, InsertDeleteExhaustive) { } } } + +TEST(DominatorTree, InsertIntoIrreducible) { + std::vector<CFGBuilder::Arc> Arcs = { + {"0", "1"}, + {"1", "27"}, {"1", "7"}, + {"10", "18"}, + {"13", "10"}, + {"18", "13"}, {"18", "23"}, + {"23", "13"}, {"23", "24"}, + {"24", "1"}, {"24", "18"}, + {"27", "24"}}; + + CFGHolder Holder; + CFGBuilder B(Holder.F, Arcs, {{Insert, {"7", "23"}}}); + DominatorTree DT(*Holder.F); + EXPECT_TRUE(DT.verify()); + + B.applyUpdate(); + BasicBlock *From = B.getOrAddBlock("7"); + BasicBlock *To = B.getOrAddBlock("23"); + DT.insertEdge(From, To); + + EXPECT_TRUE(DT.verify()); +} + diff --git a/unittests/IR/IRBuilderTest.cpp b/unittests/IR/IRBuilderTest.cpp index bb74756d81a9..720967cb136d 100644 --- a/unittests/IR/IRBuilderTest.cpp +++ b/unittests/IR/IRBuilderTest.cpp @@ -48,6 +48,27 @@ protected: GlobalVariable *GV; }; +TEST_F(IRBuilderTest, Intrinsics) { + IRBuilder<> Builder(BB); + Value *V; + CallInst *Call; + IntrinsicInst *II; + + V = Builder.CreateLoad(GV); + + Call = Builder.CreateMinNum(V, V); + II = cast<IntrinsicInst>(Call); + EXPECT_EQ(II->getIntrinsicID(), Intrinsic::minnum); + + Call = Builder.CreateMaxNum(V, V); + II = cast<IntrinsicInst>(Call); + EXPECT_EQ(II->getIntrinsicID(), Intrinsic::maxnum); + + Call = Builder.CreateIntrinsic(Intrinsic::readcyclecounter); + II = cast<IntrinsicInst>(Call); + EXPECT_EQ(II->getIntrinsicID(), Intrinsic::readcyclecounter); +} + TEST_F(IRBuilderTest, Lifetime) { IRBuilder<> Builder(BB); AllocaInst *Var1 = Builder.CreateAlloca(Builder.getInt8Ty()); @@ -438,6 +459,62 @@ TEST_F(IRBuilderTest, DIBuilder) { EXPECT_TRUE(verifyModule(*M)); } +TEST_F(IRBuilderTest, createArtificialSubprogram) { + IRBuilder<> Builder(BB); + DIBuilder DIB(*M); + auto File = DIB.createFile("main.c", "/"); + auto CU = DIB.createCompileUnit(dwarf::DW_LANG_C, File, "clang", + /*isOptimized=*/true, /*Flags=*/"", + /*Runtime Version=*/0); + auto Type = DIB.createSubroutineType(DIB.getOrCreateTypeArray(None)); + auto SP = DIB.createFunction(CU, "foo", /*LinkageName=*/"", File, + /*LineNo=*/1, Type, /*isLocalToUnit=*/false, + /*isDefinition=*/true, /*ScopeLine=*/2, + DINode::FlagZero, /*isOptimized=*/true); + EXPECT_TRUE(SP->isDistinct()); + + F->setSubprogram(SP); + AllocaInst *I = Builder.CreateAlloca(Builder.getInt8Ty()); + ReturnInst *R = Builder.CreateRetVoid(); + I->setDebugLoc(DebugLoc::get(3, 2, SP)); + R->setDebugLoc(DebugLoc::get(4, 2, SP)); + DIB.finalize(); + EXPECT_FALSE(verifyModule(*M)); + + Function *G = Function::Create(F->getFunctionType(), + Function::ExternalLinkage, "", M.get()); + BasicBlock *GBB = BasicBlock::Create(Ctx, "", G); + Builder.SetInsertPoint(GBB); + I->removeFromParent(); + Builder.Insert(I); + Builder.CreateRetVoid(); + EXPECT_FALSE(verifyModule(*M)); + + DISubprogram *GSP = DIBuilder::createArtificialSubprogram(F->getSubprogram()); + EXPECT_EQ(SP->getFile(), GSP->getFile()); + EXPECT_EQ(SP->getType(), GSP->getType()); + EXPECT_EQ(SP->getLine(), GSP->getLine()); + EXPECT_EQ(SP->getScopeLine(), GSP->getScopeLine()); + EXPECT_TRUE(GSP->isDistinct()); + + G->setSubprogram(GSP); + EXPECT_TRUE(verifyModule(*M)); + + auto *InlinedAtNode = + DILocation::getDistinct(Ctx, GSP->getScopeLine(), 0, GSP); + DebugLoc DL = I->getDebugLoc(); + DenseMap<const MDNode *, MDNode *> IANodes; + auto IA = DebugLoc::appendInlinedAt(DL, InlinedAtNode, Ctx, IANodes); + auto NewDL = DebugLoc::get(DL.getLine(), DL.getCol(), DL.getScope(), IA); + I->setDebugLoc(NewDL); + EXPECT_FALSE(verifyModule(*M)); + + EXPECT_EQ("foo", SP->getName()); + EXPECT_EQ("foo", GSP->getName()); + EXPECT_FALSE(SP->isArtificial()); + EXPECT_TRUE(GSP->isArtificial()); +} + TEST_F(IRBuilderTest, InsertExtractElement) { IRBuilder<> Builder(BB); diff --git a/unittests/IR/InstructionsTest.cpp b/unittests/IR/InstructionsTest.cpp index 619ddc5413df..6bac59620d9e 100644 --- a/unittests/IR/InstructionsTest.cpp +++ b/unittests/IR/InstructionsTest.cpp @@ -7,6 +7,7 @@ // //===----------------------------------------------------------------------===// +#include "llvm/AsmParser/Parser.h" #include "llvm/IR/Instructions.h" #include "llvm/ADT/STLExtras.h" #include "llvm/Analysis/ValueTracking.h" @@ -21,6 +22,7 @@ #include "llvm/IR/Module.h" #include "llvm/IR/NoFolder.h" #include "llvm/IR/Operator.h" +#include "llvm/Support/SourceMgr.h" #include "gmock/gmock-matchers.h" #include "gtest/gtest.h" #include <memory> @@ -28,6 +30,14 @@ namespace llvm { namespace { +static std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) { + SMDiagnostic Err; + std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C); + if (!Mod) + Err.print("InstructionsTests", errs()); + return Mod; +} + TEST(InstructionsTest, ReturnInst) { LLVMContext C; @@ -747,5 +757,126 @@ TEST(InstructionsTest, CommuteShuffleMask) { EXPECT_THAT(Indices, testing::ContainerEq(ArrayRef<int>({-1, 4, 3}))); } +TEST(InstructionsTest, ShuffleMaskQueries) { + // Create the elements for various constant vectors. + LLVMContext Ctx; + Type *Int32Ty = Type::getInt32Ty(Ctx); + Constant *CU = UndefValue::get(Int32Ty); + Constant *C0 = ConstantInt::get(Int32Ty, 0); + Constant *C1 = ConstantInt::get(Int32Ty, 1); + Constant *C2 = ConstantInt::get(Int32Ty, 2); + Constant *C3 = ConstantInt::get(Int32Ty, 3); + Constant *C4 = ConstantInt::get(Int32Ty, 4); + Constant *C5 = ConstantInt::get(Int32Ty, 5); + Constant *C6 = ConstantInt::get(Int32Ty, 6); + Constant *C7 = ConstantInt::get(Int32Ty, 7); + + Constant *Identity = ConstantVector::get({C0, CU, C2, C3, C4}); + EXPECT_TRUE(ShuffleVectorInst::isIdentityMask(Identity)); + EXPECT_FALSE(ShuffleVectorInst::isSelectMask(Identity)); // identity is distinguished from select + EXPECT_FALSE(ShuffleVectorInst::isReverseMask(Identity)); + EXPECT_TRUE(ShuffleVectorInst::isSingleSourceMask(Identity)); // identity is always single source + EXPECT_FALSE(ShuffleVectorInst::isZeroEltSplatMask(Identity)); + EXPECT_FALSE(ShuffleVectorInst::isTransposeMask(Identity)); + + Constant *Select = ConstantVector::get({CU, C1, C5}); + EXPECT_FALSE(ShuffleVectorInst::isIdentityMask(Select)); + EXPECT_TRUE(ShuffleVectorInst::isSelectMask(Select)); + EXPECT_FALSE(ShuffleVectorInst::isReverseMask(Select)); + EXPECT_FALSE(ShuffleVectorInst::isSingleSourceMask(Select)); + EXPECT_FALSE(ShuffleVectorInst::isZeroEltSplatMask(Select)); + EXPECT_FALSE(ShuffleVectorInst::isTransposeMask(Select)); + + Constant *Reverse = ConstantVector::get({C3, C2, C1, CU}); + EXPECT_FALSE(ShuffleVectorInst::isIdentityMask(Reverse)); + EXPECT_FALSE(ShuffleVectorInst::isSelectMask(Reverse)); + EXPECT_TRUE(ShuffleVectorInst::isReverseMask(Reverse)); + EXPECT_TRUE(ShuffleVectorInst::isSingleSourceMask(Reverse)); // reverse is always single source + EXPECT_FALSE(ShuffleVectorInst::isZeroEltSplatMask(Reverse)); + EXPECT_FALSE(ShuffleVectorInst::isTransposeMask(Reverse)); + + Constant *SingleSource = ConstantVector::get({C2, C2, C0, CU}); + EXPECT_FALSE(ShuffleVectorInst::isIdentityMask(SingleSource)); + EXPECT_FALSE(ShuffleVectorInst::isSelectMask(SingleSource)); + EXPECT_FALSE(ShuffleVectorInst::isReverseMask(SingleSource)); + EXPECT_TRUE(ShuffleVectorInst::isSingleSourceMask(SingleSource)); + EXPECT_FALSE(ShuffleVectorInst::isZeroEltSplatMask(SingleSource)); + EXPECT_FALSE(ShuffleVectorInst::isTransposeMask(SingleSource)); + + Constant *ZeroEltSplat = ConstantVector::get({C0, C0, CU, C0}); + EXPECT_FALSE(ShuffleVectorInst::isIdentityMask(ZeroEltSplat)); + EXPECT_FALSE(ShuffleVectorInst::isSelectMask(ZeroEltSplat)); + EXPECT_FALSE(ShuffleVectorInst::isReverseMask(ZeroEltSplat)); + EXPECT_TRUE(ShuffleVectorInst::isSingleSourceMask(ZeroEltSplat)); // 0-splat is always single source + EXPECT_TRUE(ShuffleVectorInst::isZeroEltSplatMask(ZeroEltSplat)); + EXPECT_FALSE(ShuffleVectorInst::isTransposeMask(ZeroEltSplat)); + + Constant *Transpose = ConstantVector::get({C0, C4, C2, C6}); + EXPECT_FALSE(ShuffleVectorInst::isIdentityMask(Transpose)); + EXPECT_FALSE(ShuffleVectorInst::isSelectMask(Transpose)); + EXPECT_FALSE(ShuffleVectorInst::isReverseMask(Transpose)); + EXPECT_FALSE(ShuffleVectorInst::isSingleSourceMask(Transpose)); + EXPECT_FALSE(ShuffleVectorInst::isZeroEltSplatMask(Transpose)); + EXPECT_TRUE(ShuffleVectorInst::isTransposeMask(Transpose)); + + // More tests to make sure the logic is/stays correct... + EXPECT_TRUE(ShuffleVectorInst::isIdentityMask(ConstantVector::get({CU, C1, CU, C3}))); + EXPECT_TRUE(ShuffleVectorInst::isIdentityMask(ConstantVector::get({C4, CU, C6, CU}))); + + EXPECT_TRUE(ShuffleVectorInst::isSelectMask(ConstantVector::get({C4, C1, C6, CU}))); + EXPECT_TRUE(ShuffleVectorInst::isSelectMask(ConstantVector::get({CU, C1, C6, C3}))); + + EXPECT_TRUE(ShuffleVectorInst::isReverseMask(ConstantVector::get({C7, C6, CU, C4}))); + EXPECT_TRUE(ShuffleVectorInst::isReverseMask(ConstantVector::get({C3, CU, C1, CU}))); + + EXPECT_TRUE(ShuffleVectorInst::isSingleSourceMask(ConstantVector::get({C7, C5, CU, C7}))); + EXPECT_TRUE(ShuffleVectorInst::isSingleSourceMask(ConstantVector::get({C3, C0, CU, C3}))); + + EXPECT_TRUE(ShuffleVectorInst::isZeroEltSplatMask(ConstantVector::get({C4, CU, CU, C4}))); + EXPECT_TRUE(ShuffleVectorInst::isZeroEltSplatMask(ConstantVector::get({CU, C0, CU, C0}))); + + EXPECT_TRUE(ShuffleVectorInst::isTransposeMask(ConstantVector::get({C1, C5, C3, C7}))); + EXPECT_TRUE(ShuffleVectorInst::isTransposeMask(ConstantVector::get({C1, C3}))); +} + +TEST(InstructionsTest, SkipDebug) { + LLVMContext C; + std::unique_ptr<Module> M = parseIR(C, + R"( + declare void @llvm.dbg.value(metadata, metadata, metadata) + + define void @f() { + entry: + call void @llvm.dbg.value(metadata i32 0, metadata !11, metadata !DIExpression()), !dbg !13 + ret void + } + + !llvm.dbg.cu = !{!0} + !llvm.module.flags = !{!3, !4} + !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 6.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) + !1 = !DIFile(filename: "t2.c", directory: "foo") + !2 = !{} + !3 = !{i32 2, !"Dwarf Version", i32 4} + !4 = !{i32 2, !"Debug Info Version", i32 3} + !8 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !9, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: false, unit: !0, retainedNodes: !2) + !9 = !DISubroutineType(types: !10) + !10 = !{null} + !11 = !DILocalVariable(name: "x", scope: !8, file: !1, line: 2, type: !12) + !12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) + !13 = !DILocation(line: 2, column: 7, scope: !8) + )"); + ASSERT_TRUE(M); + Function *F = cast<Function>(M->getNamedValue("f")); + BasicBlock &BB = F->front(); + + // The first non-debug instruction is the terminator. + auto *Term = BB.getTerminator(); + EXPECT_EQ(Term, BB.begin()->getNextNonDebugInstruction()); + EXPECT_EQ(Term->getIterator(), skipDebugIntrinsics(BB.begin())); + + // After the terminator, there are no non-debug instructions. + EXPECT_EQ(nullptr, Term->getNextNonDebugInstruction()); +} + } // end anonymous namespace } // end namespace llvm diff --git a/unittests/IR/LegacyPassManagerTest.cpp b/unittests/IR/LegacyPassManagerTest.cpp index 0ff2ec717597..9f5f431a5585 100644 --- a/unittests/IR/LegacyPassManagerTest.cpp +++ b/unittests/IR/LegacyPassManagerTest.cpp @@ -26,6 +26,7 @@ #include "llvm/IR/Instructions.h" #include "llvm/IR/LLVMContext.h" #include "llvm/IR/Module.h" +#include "llvm/IR/OptBisect.h" #include "llvm/Pass.h" #include "llvm/Support/MathExtras.h" #include "llvm/Support/raw_ostream.h" @@ -396,6 +397,67 @@ namespace llvm { delete M; } + // Skips or runs optional passes. + struct CustomOptPassGate : public OptPassGate { + bool Skip; + CustomOptPassGate(bool Skip) : Skip(Skip) { } + bool shouldRunPass(const Pass *P, const Module &U) { return !Skip; } + }; + + // Optional module pass. + struct ModuleOpt: public ModulePass { + char run = 0; + static char ID; + ModuleOpt() : ModulePass(ID) { } + bool runOnModule(Module &M) override { + if (!skipModule(M)) + run++; + return false; + } + }; + char ModuleOpt::ID=0; + + TEST(PassManager, CustomOptPassGate) { + LLVMContext Context0; + LLVMContext Context1; + LLVMContext Context2; + CustomOptPassGate SkipOptionalPasses(true); + CustomOptPassGate RunOptionalPasses(false); + + Module M0("custom-opt-bisect", Context0); + Module M1("custom-opt-bisect", Context1); + Module M2("custom-opt-bisect2", Context2); + struct ModuleOpt *mOpt0 = new ModuleOpt(); + struct ModuleOpt *mOpt1 = new ModuleOpt(); + struct ModuleOpt *mOpt2 = new ModuleOpt(); + + mOpt0->run = mOpt1->run = mOpt2->run = 0; + + legacy::PassManager Passes0; + legacy::PassManager Passes1; + legacy::PassManager Passes2; + + Passes0.add(mOpt0); + Passes1.add(mOpt1); + Passes2.add(mOpt2); + + Context1.setOptPassGate(SkipOptionalPasses); + Context2.setOptPassGate(RunOptionalPasses); + + Passes0.run(M0); + Passes1.run(M1); + Passes2.run(M2); + + // By default optional passes are run. + EXPECT_EQ(1, mOpt0->run); + + // The first context skips optional passes. + EXPECT_EQ(0, mOpt1->run); + + // The second context runs optional passes. + EXPECT_EQ(1, mOpt2->run); + } + Module *makeLLVMModule(LLVMContext &Context) { // Module Construction Module *mod = new Module("test-mem", Context); diff --git a/unittests/IR/ManglerTest.cpp b/unittests/IR/ManglerTest.cpp new file mode 100644 index 000000000000..04f1ca6cf977 --- /dev/null +++ b/unittests/IR/ManglerTest.cpp @@ -0,0 +1,140 @@ +//===- llvm/unittest/IR/ManglerTest.cpp - Mangler unit tests --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/IR/Mangler.h" +#include "llvm/IR/CallingConv.h" +#include "llvm/IR/DataLayout.h" +#include "llvm/IR/GlobalValue.h" +#include "llvm/IR/Module.h" +#include "gtest/gtest.h" + +using namespace llvm; + +static std::string mangleStr(StringRef IRName, Mangler &Mang, + const DataLayout &DL) { + std::string Mangled; + raw_string_ostream SS(Mangled); + Mang.getNameWithPrefix(SS, IRName, DL); + SS.flush(); + return Mangled; +} + +static std::string mangleFunc(StringRef IRName, + GlobalValue::LinkageTypes Linkage, + llvm::CallingConv::ID CC, Module &Mod, + Mangler &Mang) { + Type *VoidTy = Type::getVoidTy(Mod.getContext()); + Type *I32Ty = Type::getInt32Ty(Mod.getContext()); + FunctionType *FTy = + FunctionType::get(VoidTy, {I32Ty, I32Ty, I32Ty}, /*isVarArg=*/false); + Function *F = Function::Create(FTy, Linkage, IRName, &Mod); + F->setCallingConv(CC); + std::string Mangled; + raw_string_ostream SS(Mangled); + Mang.getNameWithPrefix(SS, F, false); + SS.flush(); + F->eraseFromParent(); + return Mangled; +} + +namespace { + +TEST(ManglerTest, MachO) { + LLVMContext Ctx; + DataLayout DL("m:o"); // macho + Module Mod("test", Ctx); + Mod.setDataLayout(DL); + Mangler Mang; + EXPECT_EQ(mangleStr("foo", Mang, DL), "_foo"); + EXPECT_EQ(mangleStr("\01foo", Mang, DL), "foo"); + EXPECT_EQ(mangleStr("?foo", Mang, DL), "_?foo"); + EXPECT_EQ(mangleFunc("foo", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::C, Mod, Mang), + "_foo"); + EXPECT_EQ(mangleFunc("?foo", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::C, Mod, Mang), + "_?foo"); + EXPECT_EQ(mangleFunc("foo", llvm::GlobalValue::PrivateLinkage, + llvm::CallingConv::C, Mod, Mang), + "L_foo"); +} + +TEST(ManglerTest, WindowsX86) { + LLVMContext Ctx; + DataLayout DL("m:x-p:32:32"); // 32-bit windows + Module Mod("test", Ctx); + Mod.setDataLayout(DL); + Mangler Mang; + EXPECT_EQ(mangleStr("foo", Mang, DL), "_foo"); + EXPECT_EQ(mangleStr("\01foo", Mang, DL), "foo"); + EXPECT_EQ(mangleStr("?foo", Mang, DL), "?foo"); + EXPECT_EQ(mangleFunc("foo", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::C, Mod, Mang), + "_foo"); + EXPECT_EQ(mangleFunc("?foo", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::C, Mod, Mang), + "?foo"); + EXPECT_EQ(mangleFunc("foo", llvm::GlobalValue::PrivateLinkage, + llvm::CallingConv::C, Mod, Mang), + "L_foo"); + + // Test calling conv mangling. + EXPECT_EQ(mangleFunc("stdcall", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::X86_StdCall, Mod, Mang), + "_stdcall@12"); + EXPECT_EQ(mangleFunc("fastcall", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::X86_FastCall, Mod, Mang), + "@fastcall@12"); + EXPECT_EQ(mangleFunc("vectorcall", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::X86_VectorCall, Mod, Mang), + "vectorcall@@12"); + + // Adding a '?' prefix blocks calling convention mangling. + EXPECT_EQ(mangleFunc("?fastcall", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::X86_FastCall, Mod, Mang), + "?fastcall"); +} + +TEST(ManglerTest, WindowsX64) { + LLVMContext Ctx; + DataLayout DL("m:w-p:64:64"); // windows + Module Mod("test", Ctx); + Mod.setDataLayout(DL); + Mangler Mang; + EXPECT_EQ(mangleStr("foo", Mang, DL), "foo"); + EXPECT_EQ(mangleStr("\01foo", Mang, DL), "foo"); + EXPECT_EQ(mangleStr("?foo", Mang, DL), "?foo"); + EXPECT_EQ(mangleFunc("foo", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::C, Mod, Mang), + "foo"); + EXPECT_EQ(mangleFunc("?foo", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::C, Mod, Mang), + "?foo"); + EXPECT_EQ(mangleFunc("foo", llvm::GlobalValue::PrivateLinkage, + llvm::CallingConv::C, Mod, Mang), + ".Lfoo"); + + // Test calling conv mangling. + EXPECT_EQ(mangleFunc("stdcall", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::X86_StdCall, Mod, Mang), + "stdcall"); + EXPECT_EQ(mangleFunc("fastcall", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::X86_FastCall, Mod, Mang), + "fastcall"); + EXPECT_EQ(mangleFunc("vectorcall", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::X86_VectorCall, Mod, Mang), + "vectorcall@@24"); + + // Adding a '?' prefix blocks calling convention mangling. + EXPECT_EQ(mangleFunc("?vectorcall", llvm::GlobalValue::ExternalLinkage, + llvm::CallingConv::X86_VectorCall, Mod, Mang), + "?vectorcall"); +} + +} // end anonymous namespace diff --git a/unittests/IR/MetadataTest.cpp b/unittests/IR/MetadataTest.cpp index 672de55fbdea..84a7b02fa983 100644 --- a/unittests/IR/MetadataTest.cpp +++ b/unittests/IR/MetadataTest.cpp @@ -932,8 +932,11 @@ typedef MetadataTest DISubrangeTest; TEST_F(DISubrangeTest, get) { auto *N = DISubrange::get(Context, 5, 7); + auto Count = N->getCount(); EXPECT_EQ(dwarf::DW_TAG_subrange_type, N->getTag()); - EXPECT_EQ(5, N->getCount()); + ASSERT_TRUE(Count); + ASSERT_TRUE(Count.is<ConstantInt*>()); + EXPECT_EQ(5, Count.get<ConstantInt*>()->getSExtValue()); EXPECT_EQ(7, N->getLowerBound()); EXPECT_EQ(N, DISubrange::get(Context, 5, 7)); EXPECT_EQ(DISubrange::get(Context, 5, 0), DISubrange::get(Context, 5)); @@ -944,23 +947,47 @@ TEST_F(DISubrangeTest, get) { TEST_F(DISubrangeTest, getEmptyArray) { auto *N = DISubrange::get(Context, -1, 0); + auto Count = N->getCount(); EXPECT_EQ(dwarf::DW_TAG_subrange_type, N->getTag()); - EXPECT_EQ(-1, N->getCount()); + ASSERT_TRUE(Count); + ASSERT_TRUE(Count.is<ConstantInt*>()); + EXPECT_EQ(-1, Count.get<ConstantInt*>()->getSExtValue()); EXPECT_EQ(0, N->getLowerBound()); EXPECT_EQ(N, DISubrange::get(Context, -1, 0)); } +TEST_F(DISubrangeTest, getVariableCount) { + DILocalScope *Scope = getSubprogram(); + DIFile *File = getFile(); + DIType *Type = getDerivedType(); + DINode::DIFlags Flags = static_cast<DINode::DIFlags>(7); + auto *VlaExpr = DILocalVariable::get(Context, Scope, "vla_expr", File, 8, + Type, 2, Flags, 8); + + auto *N = DISubrange::get(Context, VlaExpr, 0); + auto Count = N->getCount(); + ASSERT_TRUE(Count); + ASSERT_TRUE(Count.is<DIVariable*>()); + EXPECT_EQ(VlaExpr, Count.get<DIVariable*>()); + ASSERT_TRUE(isa<DIVariable>(N->getRawCountNode())); + EXPECT_EQ(0, N->getLowerBound()); + EXPECT_EQ("vla_expr", Count.get<DIVariable*>()->getName()); + EXPECT_EQ(N, DISubrange::get(Context, VlaExpr, 0)); +} + typedef MetadataTest DIEnumeratorTest; TEST_F(DIEnumeratorTest, get) { - auto *N = DIEnumerator::get(Context, 7, "name"); + auto *N = DIEnumerator::get(Context, 7, false, "name"); EXPECT_EQ(dwarf::DW_TAG_enumerator, N->getTag()); EXPECT_EQ(7, N->getValue()); + EXPECT_FALSE(N->isUnsigned()); EXPECT_EQ("name", N->getName()); - EXPECT_EQ(N, DIEnumerator::get(Context, 7, "name")); + EXPECT_EQ(N, DIEnumerator::get(Context, 7, false, "name")); - EXPECT_NE(N, DIEnumerator::get(Context, 8, "name")); - EXPECT_NE(N, DIEnumerator::get(Context, 7, "nam")); + EXPECT_NE(N, DIEnumerator::get(Context, 7, true, "name")); + EXPECT_NE(N, DIEnumerator::get(Context, 8, false, "name")); + EXPECT_NE(N, DIEnumerator::get(Context, 7, false, "nam")); TempDIEnumerator Temp = N->clone(); EXPECT_EQ(N, MDNode::replaceWithUniqued(std::move(Temp))); @@ -1024,7 +1051,7 @@ TEST_F(DITypeTest, clone) { EXPECT_EQ(N, MDNode::replaceWithUniqued(std::move(Temp))); } -TEST_F(DITypeTest, setFlags) { +TEST_F(DITypeTest, cloneWithFlags) { // void (void) Metadata *TypesOps[] = {nullptr}; Metadata *Types = MDTuple::get(Context, TypesOps); @@ -1032,17 +1059,15 @@ TEST_F(DITypeTest, setFlags) { DIType *D = DISubroutineType::getDistinct(Context, DINode::FlagZero, 0, Types); EXPECT_EQ(DINode::FlagZero, D->getFlags()); - D->setFlags(DINode::FlagRValueReference); - EXPECT_EQ(DINode::FlagRValueReference, D->getFlags()); - D->setFlags(DINode::FlagZero); + TempDIType D2 = D->cloneWithFlags(DINode::FlagRValueReference); + EXPECT_EQ(DINode::FlagRValueReference, D2->getFlags()); EXPECT_EQ(DINode::FlagZero, D->getFlags()); TempDIType T = DISubroutineType::getTemporary(Context, DINode::FlagZero, 0, Types); EXPECT_EQ(DINode::FlagZero, T->getFlags()); - T->setFlags(DINode::FlagRValueReference); - EXPECT_EQ(DINode::FlagRValueReference, T->getFlags()); - T->setFlags(DINode::FlagZero); + TempDIType T2 = T->cloneWithFlags(DINode::FlagRValueReference); + EXPECT_EQ(DINode::FlagRValueReference, T2->getFlags()); EXPECT_EQ(DINode::FlagZero, T->getFlags()); } @@ -1330,6 +1355,51 @@ TEST_F(DICompositeTypeTest, replaceOperands) { EXPECT_EQ(nullptr, N->getTemplateParams().get()); } +TEST_F(DICompositeTypeTest, variant_part) { + unsigned Tag = dwarf::DW_TAG_variant_part; + StringRef Name = "some name"; + DIFile *File = getFile(); + unsigned Line = 1; + DIScope *Scope = getSubprogram(); + DIType *BaseType = getCompositeType(); + uint64_t SizeInBits = 2; + uint32_t AlignInBits = 3; + uint64_t OffsetInBits = 4; + DINode::DIFlags Flags = static_cast<DINode::DIFlags>(5); + unsigned RuntimeLang = 6; + StringRef Identifier = "some id"; + DIDerivedType *Discriminator = cast<DIDerivedType>(getDerivedType()); + DIDerivedType *Discriminator2 = cast<DIDerivedType>(getDerivedType()); + + EXPECT_NE(Discriminator, Discriminator2); + + auto *N = DICompositeType::get( + Context, Tag, Name, File, Line, Scope, BaseType, SizeInBits, AlignInBits, + OffsetInBits, Flags, nullptr, RuntimeLang, nullptr, nullptr, Identifier, + Discriminator); + + // Test the hashing. + auto *Same = DICompositeType::get( + Context, Tag, Name, File, Line, Scope, BaseType, SizeInBits, AlignInBits, + OffsetInBits, Flags, nullptr, RuntimeLang, nullptr, nullptr, Identifier, + Discriminator); + auto *Other = DICompositeType::get( + Context, Tag, Name, File, Line, Scope, BaseType, SizeInBits, AlignInBits, + OffsetInBits, Flags, nullptr, RuntimeLang, nullptr, nullptr, Identifier, + Discriminator2); + auto *NoDisc = DICompositeType::get( + Context, Tag, Name, File, Line, Scope, BaseType, SizeInBits, AlignInBits, + OffsetInBits, Flags, nullptr, RuntimeLang, nullptr, nullptr, Identifier, + nullptr); + + EXPECT_EQ(N, Same); + EXPECT_NE(Same, Other); + EXPECT_NE(Same, NoDisc); + EXPECT_NE(Other, NoDisc); + + EXPECT_EQ(N->getDiscriminator(), Discriminator); +} + typedef MetadataTest DISubroutineTypeTest; TEST_F(DISubroutineTypeTest, get) { @@ -1375,21 +1445,27 @@ typedef MetadataTest DIFileTest; TEST_F(DIFileTest, get) { StringRef Filename = "file"; StringRef Directory = "dir"; - DIFile::ChecksumKind CSKind = DIFile::CSK_MD5; - StringRef Checksum = "000102030405060708090a0b0c0d0e0f"; - auto *N = DIFile::get(Context, Filename, Directory, CSKind, Checksum); + DIFile::ChecksumKind CSKind = DIFile::ChecksumKind::CSK_MD5; + StringRef ChecksumString = "000102030405060708090a0b0c0d0e0f"; + DIFile::ChecksumInfo<StringRef> Checksum(CSKind, ChecksumString); + StringRef Source = "source"; + auto *N = DIFile::get(Context, Filename, Directory, Checksum, Source); EXPECT_EQ(dwarf::DW_TAG_file_type, N->getTag()); EXPECT_EQ(Filename, N->getFilename()); EXPECT_EQ(Directory, N->getDirectory()); - EXPECT_EQ(CSKind, N->getChecksumKind()); EXPECT_EQ(Checksum, N->getChecksum()); - EXPECT_EQ(N, DIFile::get(Context, Filename, Directory, CSKind, Checksum)); + EXPECT_EQ(Source, N->getSource()); + EXPECT_EQ(N, DIFile::get(Context, Filename, Directory, Checksum, Source)); - EXPECT_NE(N, DIFile::get(Context, "other", Directory, CSKind, Checksum)); - EXPECT_NE(N, DIFile::get(Context, Filename, "other", CSKind, Checksum)); + EXPECT_NE(N, DIFile::get(Context, "other", Directory, Checksum, Source)); + EXPECT_NE(N, DIFile::get(Context, Filename, "other", Checksum, Source)); + DIFile::ChecksumInfo<StringRef> OtherChecksum(DIFile::ChecksumKind::CSK_SHA1, ChecksumString); EXPECT_NE( - N, DIFile::get(Context, Filename, Directory, DIFile::CSK_SHA1, Checksum)); + N, DIFile::get(Context, Filename, Directory, OtherChecksum)); + StringRef OtherSource = "other"; + EXPECT_NE(N, DIFile::get(Context, Filename, Directory, Checksum, OtherSource)); + EXPECT_NE(N, DIFile::get(Context, Filename, Directory, Checksum)); EXPECT_NE(N, DIFile::get(Context, Filename, Directory)); TempDIFile Temp = N->clone(); @@ -1518,7 +1594,7 @@ TEST_F(DISubprogramTest, get) { bool IsOptimized = false; MDTuple *TemplateParams = getTuple(); DISubprogram *Declaration = getSubprogram(); - MDTuple *Variables = getTuple(); + MDTuple *RetainedNodes = getTuple(); MDTuple *ThrownTypes = getTuple(); DICompileUnit *Unit = getUnit(); @@ -1526,7 +1602,7 @@ TEST_F(DISubprogramTest, get) { Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, Unit, TemplateParams, Declaration, - Variables, ThrownTypes); + RetainedNodes, ThrownTypes); EXPECT_EQ(dwarf::DW_TAG_subprogram, N->getTag()); EXPECT_EQ(Scope, N->getScope()); @@ -1547,99 +1623,99 @@ TEST_F(DISubprogramTest, get) { EXPECT_EQ(Unit, N->getUnit()); EXPECT_EQ(TemplateParams, N->getTemplateParams().get()); EXPECT_EQ(Declaration, N->getDeclaration()); - EXPECT_EQ(Variables, N->getVariables().get()); + EXPECT_EQ(RetainedNodes, N->getRetainedNodes().get()); EXPECT_EQ(ThrownTypes, N->getThrownTypes().get()); EXPECT_EQ(N, DISubprogram::get( Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, getCompositeType(), Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, "other", LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, Name, "other", File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, Name, LinkageName, getFile(), Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, Name, LinkageName, File, Line + 1, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, getSubroutineType(), IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, Unit, TemplateParams, - Declaration, Variables, ThrownTypes)); + Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, Name, LinkageName, File, Line, Type, !IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, !IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine + 1, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, getCompositeType(), Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, ThrownTypes)); + Unit, TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality + 1, VirtualIndex, ThisAdjustment, Flags, IsOptimized, Unit, - TemplateParams, Declaration, Variables, + TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex + 1, ThisAdjustment, Flags, IsOptimized, Unit, - TemplateParams, Declaration, Variables, + TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, !IsOptimized, Unit, - TemplateParams, Declaration, Variables, + TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, nullptr, - TemplateParams, Declaration, Variables, + TemplateParams, Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get( Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, getTuple(), Declaration, Variables, ThrownTypes)); + Unit, getTuple(), Declaration, RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, Unit, - TemplateParams, getSubprogram(), Variables, + TemplateParams, getSubprogram(), RetainedNodes, ThrownTypes)); EXPECT_NE(N, DISubprogram::get(Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, @@ -1650,7 +1726,7 @@ TEST_F(DISubprogramTest, get) { Context, Scope, Name, LinkageName, File, Line, Type, IsLocalToUnit, IsDefinition, ScopeLine, ContainingType, Virtuality, VirtualIndex, ThisAdjustment, Flags, IsOptimized, - Unit, TemplateParams, Declaration, Variables, getTuple())); + Unit, TemplateParams, Declaration, RetainedNodes, getTuple())); TempDISubprogram Temp = N->clone(); EXPECT_EQ(N, MDNode::replaceWithUniqued(std::move(Temp))); @@ -2035,14 +2111,20 @@ TEST_F(DIExpressionTest, get) { // Test DIExpression::prepend(). uint64_t Elts0[] = {dwarf::DW_OP_LLVM_fragment, 0, 32}; auto *N0 = DIExpression::get(Context, Elts0); - N0 = DIExpression::prepend(N0, true, 64, true, true); + auto *N0WithPrependedOps = DIExpression::prepend(N0, true, 64, true, true); uint64_t Elts1[] = {dwarf::DW_OP_deref, dwarf::DW_OP_plus_uconst, 64, dwarf::DW_OP_deref, dwarf::DW_OP_stack_value, dwarf::DW_OP_LLVM_fragment, 0, 32}; auto *N1 = DIExpression::get(Context, Elts1); - EXPECT_EQ(N0, N1); + EXPECT_EQ(N0WithPrependedOps, N1); + + // Test DIExpression::append(). + uint64_t Elts2[] = {dwarf::DW_OP_deref, dwarf::DW_OP_plus_uconst, 64, + dwarf::DW_OP_deref, dwarf::DW_OP_stack_value}; + auto *N2 = DIExpression::append(N0, Elts2); + EXPECT_EQ(N0WithPrependedOps, N2); } TEST_F(DIExpressionTest, isValid) { @@ -2436,9 +2518,20 @@ TEST_F(FunctionAttachmentTest, Verifier) { TEST_F(FunctionAttachmentTest, EntryCount) { Function *F = getFunction("foo"); EXPECT_FALSE(F->getEntryCount().hasValue()); - F->setEntryCount(12304); - EXPECT_TRUE(F->getEntryCount().hasValue()); - EXPECT_EQ(12304u, *F->getEntryCount()); + F->setEntryCount(12304, Function::PCT_Real); + auto Count = F->getEntryCount(); + EXPECT_TRUE(Count.hasValue()); + EXPECT_EQ(12304u, Count.getCount()); + EXPECT_EQ(Function::PCT_Real, Count.getType()); + + // Repeat the same for synthetic counts. + F = getFunction("bar"); + EXPECT_FALSE(F->getEntryCount().hasValue()); + F->setEntryCount(123, Function::PCT_Synthetic); + Count = F->getEntryCount(); + EXPECT_TRUE(Count.hasValue()); + EXPECT_EQ(123u, Count.getCount()); + EXPECT_EQ(Function::PCT_Synthetic, Count.getType()); } TEST_F(FunctionAttachmentTest, SubprogramAttachment) { diff --git a/unittests/IR/PassBuilderCallbacksTest.cpp b/unittests/IR/PassBuilderCallbacksTest.cpp index df0b11f6cc71..e46fc1781507 100644 --- a/unittests/IR/PassBuilderCallbacksTest.cpp +++ b/unittests/IR/PassBuilderCallbacksTest.cpp @@ -39,7 +39,7 @@ using testing::Invoke; using testing::WithArgs; using testing::_; -/// \brief A CRTP base for analysis mock handles +/// A CRTP base for analysis mock handles /// /// This class reconciles mocking with the value semantics implementation of the /// AnalysisManager. Analysis mock handles should derive from this class and @@ -110,7 +110,7 @@ protected: } }; -/// \brief A CRTP base for pass mock handles +/// A CRTP base for pass mock handles /// /// This class reconciles mocking with the value semantics implementation of the /// PassManager. Pass mock handles should derive from this class and diff --git a/unittests/IR/PassManagerTest.cpp b/unittests/IR/PassManagerTest.cpp index 0131bce3d2b2..7709453cb744 100644 --- a/unittests/IR/PassManagerTest.cpp +++ b/unittests/IR/PassManagerTest.cpp @@ -28,7 +28,7 @@ public: TestFunctionAnalysis(int &Runs) : Runs(Runs) {} - /// \brief Run the analysis pass over the function and return a result. + /// Run the analysis pass over the function and return a result. Result run(Function &F, FunctionAnalysisManager &AM) { ++Runs; int Count = 0; diff --git a/unittests/IR/PatternMatch.cpp b/unittests/IR/PatternMatch.cpp index 5c13ba6ecd90..6b5686d53c66 100644 --- a/unittests/IR/PatternMatch.cpp +++ b/unittests/IR/PatternMatch.cpp @@ -65,6 +65,56 @@ TEST_F(PatternMatchTest, OneUse) { EXPECT_FALSE(m_OneUse(m_Value()).match(Leaf)); } +TEST_F(PatternMatchTest, CommutativeDeferredValue) { + Value *X = IRB.getInt32(1); + Value *Y = IRB.getInt32(2); + + { + Value *tX = X; + EXPECT_TRUE(match(X, m_Deferred(tX))); + EXPECT_FALSE(match(Y, m_Deferred(tX))); + } + { + const Value *tX = X; + EXPECT_TRUE(match(X, m_Deferred(tX))); + EXPECT_FALSE(match(Y, m_Deferred(tX))); + } + { + Value *const tX = X; + EXPECT_TRUE(match(X, m_Deferred(tX))); + EXPECT_FALSE(match(Y, m_Deferred(tX))); + } + { + const Value *const tX = X; + EXPECT_TRUE(match(X, m_Deferred(tX))); + EXPECT_FALSE(match(Y, m_Deferred(tX))); + } + + { + Value *tX = nullptr; + EXPECT_TRUE(match(IRB.CreateAnd(X, X), m_And(m_Value(tX), m_Deferred(tX)))); + EXPECT_EQ(tX, X); + } + { + Value *tX = nullptr; + EXPECT_FALSE( + match(IRB.CreateAnd(X, Y), m_c_And(m_Value(tX), m_Deferred(tX)))); + } + + auto checkMatch = [X, Y](Value *Pattern) { + Value *tX = nullptr, *tY = nullptr; + EXPECT_TRUE(match( + Pattern, m_c_And(m_Value(tX), m_c_And(m_Deferred(tX), m_Value(tY))))); + EXPECT_EQ(tX, X); + EXPECT_EQ(tY, Y); + }; + + checkMatch(IRB.CreateAnd(X, IRB.CreateAnd(X, Y))); + checkMatch(IRB.CreateAnd(X, IRB.CreateAnd(Y, X))); + checkMatch(IRB.CreateAnd(IRB.CreateAnd(X, Y), X)); + checkMatch(IRB.CreateAnd(IRB.CreateAnd(Y, X), X)); +} + TEST_F(PatternMatchTest, FloatingPointOrderedMin) { Type *FltTy = IRB.getFloatTy(); Value *L = ConstantFP::get(FltTy, 1.0); @@ -340,6 +390,150 @@ TEST_F(PatternMatchTest, OverflowingBinOps) { EXPECT_FALSE(m_NUWShl(m_Value(), m_Value()).match(IRB.CreateNUWAdd(L, R))); } +TEST_F(PatternMatchTest, LoadStoreOps) { + // Create this load/store sequence: + // + // %p = alloca i32* + // %0 = load i32*, i32** %p + // store i32 42, i32* %0 + + Value *Alloca = IRB.CreateAlloca(IRB.getInt32Ty()); + Value *LoadInst = IRB.CreateLoad(Alloca); + Value *FourtyTwo = IRB.getInt32(42); + Value *StoreInst = IRB.CreateStore(FourtyTwo, Alloca); + Value *MatchLoad, *MatchStoreVal, *MatchStorePointer; + + EXPECT_TRUE(m_Load(m_Value(MatchLoad)).match(LoadInst)); + EXPECT_EQ(Alloca, MatchLoad); + + EXPECT_TRUE(m_Load(m_Specific(Alloca)).match(LoadInst)); + + EXPECT_FALSE(m_Load(m_Value(MatchLoad)).match(Alloca)); + + EXPECT_TRUE(m_Store(m_Value(MatchStoreVal), m_Value(MatchStorePointer)) + .match(StoreInst)); + EXPECT_EQ(FourtyTwo, MatchStoreVal); + EXPECT_EQ(Alloca, MatchStorePointer); + + EXPECT_FALSE(m_Store(m_Value(MatchStoreVal), m_Value(MatchStorePointer)) + .match(Alloca)); + + EXPECT_TRUE(m_Store(m_SpecificInt(42), m_Specific(Alloca)) + .match(StoreInst)); + EXPECT_FALSE(m_Store(m_SpecificInt(42), m_Specific(FourtyTwo)) + .match(StoreInst)); + EXPECT_FALSE(m_Store(m_SpecificInt(43), m_Specific(Alloca)) + .match(StoreInst)); +} + +TEST_F(PatternMatchTest, VectorOps) { + // Build up small tree of vector operations + // + // Val = 0 + 1 + // Val2 = Val + 3 + // VI1 = insertelement <2 x i8> undef, i8 1, i32 0 = <1, undef> + // VI2 = insertelement <2 x i8> %VI1, i8 %Val2, i8 %Val = <1, 4> + // VI3 = insertelement <2 x i8> %VI1, i8 %Val2, i32 1 = <1, 4> + // VI4 = insertelement <2 x i8> %VI1, i8 2, i8 %Val = <1, 2> + // + // SI1 = shufflevector <2 x i8> %VI1, <2 x i8> undef, zeroinitializer + // SI2 = shufflevector <2 x i8> %VI3, <2 x i8> %VI4, <2 x i8> <i8 0, i8 2> + // SI3 = shufflevector <2 x i8> %VI3, <2 x i8> undef, zeroinitializer + // SI4 = shufflevector <2 x i8> %VI4, <2 x i8> undef, zeroinitializer + // + // SP1 = VectorSplat(2, i8 2) + // SP2 = VectorSplat(2, i8 %Val) + Type *VecTy = VectorType::get(IRB.getInt8Ty(), 2); + Type *i32 = IRB.getInt32Ty(); + Type *i32VecTy = VectorType::get(i32, 2); + + Value *Val = IRB.CreateAdd(IRB.getInt8(0), IRB.getInt8(1)); + Value *Val2 = IRB.CreateAdd(Val, IRB.getInt8(3)); + + SmallVector<Constant *, 2> VecElemIdxs; + VecElemIdxs.push_back(ConstantInt::get(i32, 0)); + VecElemIdxs.push_back(ConstantInt::get(i32, 2)); + auto *IdxVec = ConstantVector::get(VecElemIdxs); + + Value *UndefVec = UndefValue::get(VecTy); + Value *VI1 = IRB.CreateInsertElement(UndefVec, IRB.getInt8(1), (uint64_t)0); + Value *VI2 = IRB.CreateInsertElement(VI1, Val2, Val); + Value *VI3 = IRB.CreateInsertElement(VI1, Val2, (uint64_t)1); + Value *VI4 = IRB.CreateInsertElement(VI1, IRB.getInt8(2), Val); + + Value *EX1 = IRB.CreateExtractElement(VI4, Val); + Value *EX2 = IRB.CreateExtractElement(VI4, (uint64_t)0); + Value *EX3 = IRB.CreateExtractElement(IdxVec, (uint64_t)1); + + Value *Zero = ConstantAggregateZero::get(i32VecTy); + Value *SI1 = IRB.CreateShuffleVector(VI1, UndefVec, Zero); + Value *SI2 = IRB.CreateShuffleVector(VI3, VI4, IdxVec); + Value *SI3 = IRB.CreateShuffleVector(VI3, UndefVec, Zero); + Value *SI4 = IRB.CreateShuffleVector(VI4, UndefVec, Zero); + + Value *SP1 = IRB.CreateVectorSplat(2, IRB.getInt8(2)); + Value *SP2 = IRB.CreateVectorSplat(2, Val); + + Value *A = nullptr, *B = nullptr, *C = nullptr; + + // Test matching insertelement + EXPECT_TRUE(match(VI1, m_InsertElement(m_Value(), m_Value(), m_Value()))); + EXPECT_TRUE( + match(VI1, m_InsertElement(m_Undef(), m_ConstantInt(), m_ConstantInt()))); + EXPECT_TRUE( + match(VI1, m_InsertElement(m_Undef(), m_ConstantInt(), m_Zero()))); + EXPECT_TRUE( + match(VI1, m_InsertElement(m_Undef(), m_SpecificInt(1), m_Zero()))); + EXPECT_TRUE(match(VI2, m_InsertElement(m_Value(), m_Value(), m_Value()))); + EXPECT_FALSE( + match(VI2, m_InsertElement(m_Value(), m_Value(), m_ConstantInt()))); + EXPECT_FALSE( + match(VI2, m_InsertElement(m_Value(), m_ConstantInt(), m_Value()))); + EXPECT_FALSE(match(VI2, m_InsertElement(m_Constant(), m_Value(), m_Value()))); + EXPECT_TRUE(match(VI3, m_InsertElement(m_Value(A), m_Value(B), m_Value(C)))); + EXPECT_TRUE(A == VI1); + EXPECT_TRUE(B == Val2); + EXPECT_TRUE(isa<ConstantInt>(C)); + A = B = C = nullptr; // reset + + // Test matching extractelement + EXPECT_TRUE(match(EX1, m_ExtractElement(m_Value(A), m_Value(B)))); + EXPECT_TRUE(A == VI4); + EXPECT_TRUE(B == Val); + A = B = C = nullptr; // reset + EXPECT_FALSE(match(EX1, m_ExtractElement(m_Value(), m_ConstantInt()))); + EXPECT_TRUE(match(EX2, m_ExtractElement(m_Value(), m_ConstantInt()))); + EXPECT_TRUE(match(EX3, m_ExtractElement(m_Constant(), m_ConstantInt()))); + + // Test matching shufflevector + EXPECT_TRUE(match(SI1, m_ShuffleVector(m_Value(), m_Undef(), m_Zero()))); + EXPECT_TRUE(match(SI2, m_ShuffleVector(m_Value(A), m_Value(B), m_Value(C)))); + EXPECT_TRUE(A == VI3); + EXPECT_TRUE(B == VI4); + EXPECT_TRUE(C == IdxVec); + A = B = C = nullptr; // reset + + // Test matching the vector splat pattern + EXPECT_TRUE(match( + SI1, + m_ShuffleVector(m_InsertElement(m_Undef(), m_SpecificInt(1), m_Zero()), + m_Undef(), m_Zero()))); + EXPECT_FALSE(match( + SI3, m_ShuffleVector(m_InsertElement(m_Undef(), m_Value(), m_Zero()), + m_Undef(), m_Zero()))); + EXPECT_FALSE(match( + SI4, m_ShuffleVector(m_InsertElement(m_Undef(), m_Value(), m_Zero()), + m_Undef(), m_Zero()))); + EXPECT_TRUE(match( + SP1, + m_ShuffleVector(m_InsertElement(m_Undef(), m_SpecificInt(2), m_Zero()), + m_Undef(), m_Zero()))); + EXPECT_TRUE(match( + SP2, m_ShuffleVector(m_InsertElement(m_Undef(), m_Value(A), m_Zero()), + m_Undef(), m_Zero()))); + EXPECT_TRUE(A == Val); +} + template <typename T> struct MutableConstTest : PatternMatchTest { }; typedef ::testing::Types<std::tuple<Value*, Instruction*>, diff --git a/unittests/IR/ValueMapTest.cpp b/unittests/IR/ValueMapTest.cpp index 28633b44b11d..cbcd6f6e53f4 100644 --- a/unittests/IR/ValueMapTest.cpp +++ b/unittests/IR/ValueMapTest.cpp @@ -195,7 +195,7 @@ struct LockMutex : ValueMapConfig<KeyT, MutexT> { static MutexT *getMutex(const ExtraData &Data) { return Data.M; } }; // FIXME: These tests started failing on Windows. -#if LLVM_ENABLE_THREADS && !defined(LLVM_ON_WIN32) +#if LLVM_ENABLE_THREADS && !defined(_WIN32) TYPED_TEST(ValueMapTest, LocksMutex) { sys::Mutex M(false); // Not recursive. bool CalledRAUW = false, CalledDeleted = false; diff --git a/unittests/IR/ValueTest.cpp b/unittests/IR/ValueTest.cpp index 0087cb2fa82c..90466b2505ec 100644 --- a/unittests/IR/ValueTest.cpp +++ b/unittests/IR/ValueTest.cpp @@ -112,7 +112,13 @@ TEST(ValueTest, printSlots) { // without a slot tracker. LLVMContext C; - const char *ModuleString = "define void @f(i32 %x, i32 %y) {\n" + const char *ModuleString = "@g0 = external global %500\n" + "@g1 = external global %900\n" + "\n" + "%900 = type { i32, i32 }\n" + "%500 = type { i32 }\n" + "\n" + "define void @f(i32 %x, i32 %y) {\n" "entry:\n" " %0 = add i32 %y, 1\n" " %1 = add i32 %y, 1\n" @@ -132,6 +138,11 @@ TEST(ValueTest, printSlots) { Instruction *I1 = &*++BB.begin(); ASSERT_TRUE(I1); + GlobalVariable *G0 = M->getGlobalVariable("g0"); + ASSERT_TRUE(G0); + GlobalVariable *G1 = M->getGlobalVariable("g1"); + ASSERT_TRUE(G1); + ModuleSlotTracker MST(M.get()); #define CHECK_PRINT(INST, STR) \ @@ -172,6 +183,8 @@ TEST(ValueTest, printSlots) { CHECK_PRINT_AS_OPERAND(I1, false, "%1"); CHECK_PRINT_AS_OPERAND(I0, true, "i32 %0"); CHECK_PRINT_AS_OPERAND(I1, true, "i32 %1"); + CHECK_PRINT_AS_OPERAND(G0, true, "%0* @g0"); + CHECK_PRINT_AS_OPERAND(G1, true, "%1* @g1"); #undef CHECK_PRINT_AS_OPERAND } diff --git a/unittests/Linker/CMakeLists.txt b/unittests/Linker/CMakeLists.txt index 05f45c0a8ce8..e94f4be8a2e9 100644 --- a/unittests/Linker/CMakeLists.txt +++ b/unittests/Linker/CMakeLists.txt @@ -4,10 +4,6 @@ set(LLVM_LINK_COMPONENTS linker ) -set(LinkerSources - LinkModulesTest.cpp - ) - add_llvm_unittest(LinkerTests - ${LinkerSources} + LinkModulesTest.cpp ) diff --git a/unittests/MC/Disassembler.cpp b/unittests/MC/Disassembler.cpp index dd0f1ef9ace7..0a6871f6f857 100644 --- a/unittests/MC/Disassembler.cpp +++ b/unittests/MC/Disassembler.cpp @@ -21,7 +21,7 @@ static const char *symbolLookupCallback(void *DisInfo, uint64_t ReferenceValue, return nullptr; } -TEST(Disassembler, Test1) { +TEST(Disassembler, X86Test) { llvm::InitializeAllTargetInfos(); llvm::InitializeAllTargetMCs(); llvm::InitializeAllDisassemblers(); @@ -62,3 +62,46 @@ TEST(Disassembler, Test1) { LLVMDisasmDispose(DCR); } + +TEST(Disassembler, WebAssemblyTest) { + llvm::InitializeAllTargetInfos(); + llvm::InitializeAllTargetMCs(); + llvm::InitializeAllDisassemblers(); + + uint8_t Bytes[] = {0x6a, 0x42, 0x7F, 0x35, 0x01, 0x10}; + uint8_t *BytesP = Bytes; + const char OutStringSize = 100; + char OutString[OutStringSize]; + LLVMDisasmContextRef DCR = LLVMCreateDisasm("wasm32-unknown-unknown", nullptr, + 0, nullptr, symbolLookupCallback); + if (!DCR) + return; + + size_t InstSize; + unsigned NumBytes = sizeof(Bytes); + unsigned PC = 0; + + InstSize = LLVMDisasmInstruction(DCR, BytesP, NumBytes, PC, OutString, + OutStringSize); + EXPECT_EQ(InstSize, 1U); + EXPECT_EQ(StringRef(OutString), "\ti32.add "); + PC += InstSize; + BytesP += InstSize; + NumBytes -= InstSize; + + InstSize = LLVMDisasmInstruction(DCR, BytesP, NumBytes, PC, OutString, + OutStringSize); + EXPECT_EQ(InstSize, 2U); + EXPECT_EQ(StringRef(OutString), "\ti64.const\t-1"); + + PC += InstSize; + BytesP += InstSize; + NumBytes -= InstSize; + + InstSize = LLVMDisasmInstruction(DCR, BytesP, NumBytes, PC, OutString, + OutStringSize); + EXPECT_EQ(InstSize, 3U); + EXPECT_EQ(StringRef(OutString), "\ti64.load32_u\t16, :p2align=1"); + + LLVMDisasmDispose(DCR); +} diff --git a/unittests/MI/LiveIntervalTest.cpp b/unittests/MI/LiveIntervalTest.cpp index 93d41d5daf13..a39fd7f73cf4 100644 --- a/unittests/MI/LiveIntervalTest.cpp +++ b/unittests/MI/LiveIntervalTest.cpp @@ -313,7 +313,7 @@ TEST(LiveIntervalTest, MoveUpValNos) { liveIntervalTest(R"MIR( successors: %bb.1, %bb.2 %0 = IMPLICIT_DEF - S_CBRANCH_VCCNZ %bb.2, implicit undef %vcc + S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc S_BRANCH %bb.1 bb.2: S_NOP 0, implicit %0 @@ -343,10 +343,10 @@ TEST(LiveIntervalTest, MoveOverUndefUse0) { TEST(LiveIntervalTest, MoveOverUndefUse1) { // findLastUseBefore() used by handleMoveUp() must ignore undef operands. liveIntervalTest(R"MIR( - %sgpr0 = IMPLICIT_DEF + $sgpr0 = IMPLICIT_DEF S_NOP 0 - S_NOP 0, implicit undef %sgpr0 - %sgpr0 = IMPLICIT_DEF implicit %sgpr0(tied-def 0) + S_NOP 0, implicit undef $sgpr0 + $sgpr0 = IMPLICIT_DEF implicit $sgpr0(tied-def 0) )MIR", [](MachineFunction &MF, LiveIntervals &LIS) { testHandleMove(MF, LIS, 3, 1); }); @@ -358,7 +358,7 @@ TEST(LiveIntervalTest, SubRegMoveDown) { liveIntervalTest(R"MIR( successors: %bb.1, %bb.2 %0 = IMPLICIT_DEF - S_CBRANCH_VCCNZ %bb.2, implicit undef %vcc + S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc S_BRANCH %bb.1 bb.2: successors: %bb.1 @@ -384,7 +384,7 @@ TEST(LiveIntervalTest, SubRegMoveUp) { successors: %bb.1, %bb.2 undef %0.sub0 = IMPLICIT_DEF %0.sub1 = IMPLICIT_DEF - S_CBRANCH_VCCNZ %bb.2, implicit undef %vcc + S_CBRANCH_VCCNZ %bb.2, implicit undef $vcc S_BRANCH %bb.1 bb.1: S_NOP 0, implicit %0.sub1 @@ -395,6 +395,31 @@ TEST(LiveIntervalTest, SubRegMoveUp) { }); } +TEST(LiveIntervalTest, DeadSubRegMoveUp) { + // handleMoveUp had a bug where moving a dead subreg def into the middle of + // an earlier segment resulted in an invalid live range. + liveIntervalTest(R"MIR( + undef %125.sub0:vreg_128 = V_MOV_B32_e32 0, implicit $exec + %125.sub1:vreg_128 = COPY %125.sub0 + %125.sub2:vreg_128 = COPY %125.sub0 + undef %51.sub0:vreg_128 = V_MOV_B32_e32 898625526, implicit $exec + %51.sub1:vreg_128 = COPY %51.sub0 + %51.sub2:vreg_128 = COPY %51.sub0 + %52:vgpr_32 = V_MOV_B32_e32 986714345, implicit $exec + %54:vgpr_32 = V_MOV_B32_e32 1742342378, implicit $exec + %57:vgpr_32 = V_MOV_B32_e32 3168768712, implicit $exec + %59:vgpr_32 = V_MOV_B32_e32 1039972644, implicit $exec + %60:vgpr_32 = V_MAD_F32 0, %52, 0, undef %61:vgpr_32, 0, %59, 0, 0, implicit $exec + %63:vgpr_32 = V_ADD_F32_e32 %51.sub3, undef %64:vgpr_32, implicit $exec + dead %66:vgpr_32 = V_MAD_F32 0, %60, 0, undef %67:vgpr_32, 0, %125.sub2, 0, 0, implicit $exec + undef %124.sub1:vreg_128 = V_MAD_F32 0, %57, 0, undef %70:vgpr_32, 0, %125.sub1, 0, 0, implicit $exec + %124.sub0:vreg_128 = V_MAD_F32 0, %54, 0, undef %73:vgpr_32, 0, %125.sub0, 0, 0, implicit $exec + dead undef %125.sub3:vreg_128 = V_MAC_F32_e32 %63, undef %76:vgpr_32, %125.sub3, implicit $exec +)MIR", [](MachineFunction &MF, LiveIntervals &LIS) { + testHandleMove(MF, LIS, 15, 12); + }); +} + int main(int argc, char **argv) { ::testing::InitGoogleTest(&argc, argv); initLLVM(); diff --git a/unittests/Option/OptionParsingTest.cpp b/unittests/Option/OptionParsingTest.cpp index 6ac6283327bb..eef21ab51209 100644 --- a/unittests/Option/OptionParsingTest.cpp +++ b/unittests/Option/OptionParsingTest.cpp @@ -266,3 +266,48 @@ TEST(Option, FlagAliasToJoined) { EXPECT_EQ(1U, AL.getAllArgValues(OPT_B).size()); EXPECT_EQ("", AL.getAllArgValues(OPT_B)[0]); } + +TEST(Option, FindNearest) { + TestOptTable T; + std::string Nearest; + + // Options that are too short should not be considered + // "near" other short options. + EXPECT_GT(T.findNearest("-A", Nearest), 4U); + EXPECT_GT(T.findNearest("/C", Nearest), 4U); + EXPECT_GT(T.findNearest("--C=foo", Nearest), 4U); + + // The nearest candidate should mirror the amount of prefix + // characters used in the original string. + EXPECT_EQ(1U, T.findNearest("-blorb", Nearest)); + EXPECT_EQ(Nearest, "-blorp"); + EXPECT_EQ(1U, T.findNearest("--blorm", Nearest)); + EXPECT_EQ(Nearest, "--blorp"); + EXPECT_EQ(1U, T.findNearest("-fjormp", Nearest)); + EXPECT_EQ(Nearest, "--fjormp"); + + // The nearest candidate respects the prefix and value delimiter + // of the original string. + EXPECT_EQ(1U, T.findNearest("/framb:foo", Nearest)); + EXPECT_EQ(Nearest, "/cramb:foo"); + + // Flags should be included and excluded as specified. + EXPECT_EQ(1U, T.findNearest("-doopf", Nearest, /*FlagsToInclude=*/OptFlag2)); + EXPECT_EQ(Nearest, "-doopf2"); + EXPECT_EQ(1U, T.findNearest("-doopf", Nearest, + /*FlagsToInclude=*/0, + /*FlagsToExclude=*/OptFlag2)); + EXPECT_EQ(Nearest, "-doopf1"); +} + +TEST(DISABLED_Option, FindNearestFIXME) { + TestOptTable T; + std::string Nearest; + + // FIXME: Options with joined values should not have those values considered + // when calculating distance. The test below would fail if run, but it should + // succeed. + EXPECT_EQ(1U, T.findNearest("--erbghFoo", Nearest)); + EXPECT_EQ(Nearest, "--ermghFoo"); + +} diff --git a/unittests/Option/Opts.td b/unittests/Option/Opts.td index 25c98c6f6015..c4544b5b3f9b 100644 --- a/unittests/Option/Opts.td +++ b/unittests/Option/Opts.td @@ -28,3 +28,11 @@ def K : Flag<["-"], "K">, Alias<B>; def Slurp : Option<["-"], "slurp", KIND_REMAINING_ARGS>; def SlurpJoined : Option<["-"], "slurpjoined", KIND_REMAINING_ARGS_JOINED>; + +def Blorp : Flag<["-", "--"], "blorp">, HelpText<"The blorp option">, Flags<[OptFlag1]>; +def Cramb : Joined<["/"], "cramb:">, HelpText<"The cramb option">, MetaVarName<"CRAMB">, Flags<[OptFlag1]>; +def Doopf1 : Flag<["-"], "doopf1">, HelpText<"The doopf1 option">, Flags<[OptFlag1]>; +def Doopf2 : Flag<["-"], "doopf2">, HelpText<"The doopf2 option">, Flags<[OptFlag2]>; +def Ermgh : Joined<["--"], "ermgh">, HelpText<"The ermgh option">, MetaVarName<"ERMGH">, Flags<[OptFlag1]>; +def Fjormp : Flag<["--"], "fjormp">, HelpText<"The fjormp option">, Flags<[OptFlag1]>; +def DashDash : Option<["--"], "", KIND_REMAINING_ARGS>; diff --git a/unittests/Passes/CMakeLists.txt b/unittests/Passes/CMakeLists.txt new file mode 100644 index 000000000000..d90df209d4ec --- /dev/null +++ b/unittests/Passes/CMakeLists.txt @@ -0,0 +1,29 @@ +# Needed by LLVM's CMake checks because this file defines multiple targets. +set(LLVM_OPTIONAL_SOURCES PluginsTest.cpp TestPlugin.cpp) + +# If plugins are disabled, this test will disable itself at runtime. Otherwise, +# reconfiguring with plugins disabled will leave behind a stale executable. +if (LLVM_ENABLE_PLUGINS) + add_definitions(-DLLVM_ENABLE_PLUGINS) +endif() + +set(LLVM_LINK_COMPONENTS Support Passes Core) +add_llvm_unittest(PluginsTests + PluginsTest.cpp + ) +export_executable_symbols(PluginsTests) + +set(LLVM_LINK_COMPONENTS) +add_llvm_loadable_module(TestPlugin + TestPlugin.cpp + ) + +# Put plugin next to the unit test executable. +set_output_directory(TestPlugin + BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR} + LIBRARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR} + ) +set_target_properties(TestPlugin PROPERTIES FOLDER "Tests") + +add_dependencies(TestPlugin intrinsics_gen) +add_dependencies(PluginsTests TestPlugin) diff --git a/unittests/Passes/PluginsTest.cpp b/unittests/Passes/PluginsTest.cpp new file mode 100644 index 000000000000..726978714e87 --- /dev/null +++ b/unittests/Passes/PluginsTest.cpp @@ -0,0 +1,61 @@ +//===- unittests/Passes/Plugins/PluginsTest.cpp ---------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Analysis/CGSCCPassManager.h" +#include "llvm/Config/config.h" +#include "llvm/IR/PassManager.h" +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/ManagedStatic.h" +#include "llvm/Support/Path.h" +#include "llvm/Transforms/Scalar/LoopPassManager.h" +#include "gtest/gtest.h" + +#include "TestPlugin.h" + +#include <cstdint> + +using namespace llvm; + +void anchor() {} + +static std::string LibPath(const std::string Name = "TestPlugin") { + const std::vector<testing::internal::string> &Argvs = + testing::internal::GetArgvs(); + const char *Argv0 = Argvs.size() > 0 ? Argvs[0].c_str() : "PluginsTests"; + void *Ptr = (void *)(intptr_t)anchor; + std::string Path = sys::fs::getMainExecutable(Argv0, Ptr); + llvm::SmallString<256> Buf{sys::path::parent_path(Path)}; + sys::path::append(Buf, (Name + LTDL_SHLIB_EXT).c_str()); + return Buf.str(); +} + +TEST(PluginsTests, LoadPlugin) { +#if !defined(LLVM_ENABLE_PLUGINS) + // Disable the test if plugins are disabled. + return; +#endif + + auto PluginPath = LibPath(); + ASSERT_NE("", PluginPath); + + Expected<PassPlugin> Plugin = PassPlugin::Load(PluginPath); + ASSERT_TRUE(!!Plugin) << "Plugin path: " << PluginPath; + + ASSERT_EQ(TEST_PLUGIN_NAME, Plugin->getPluginName()); + ASSERT_EQ(TEST_PLUGIN_VERSION, Plugin->getPluginVersion()); + + PassBuilder PB; + ModulePassManager PM; + ASSERT_FALSE(PB.parsePassPipeline(PM, "plugin-pass")); + + Plugin->registerPassBuilderCallbacks(PB); + ASSERT_TRUE(PB.parsePassPipeline(PM, "plugin-pass")); +} diff --git a/unittests/Passes/TestPlugin.cpp b/unittests/Passes/TestPlugin.cpp new file mode 100644 index 000000000000..3dc5cdc84768 --- /dev/null +++ b/unittests/Passes/TestPlugin.cpp @@ -0,0 +1,39 @@ +//===- unittests/Passes/Plugins/Plugin.cpp --------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Passes/PassBuilder.h" +#include "llvm/Passes/PassPlugin.h" + +#include "TestPlugin.h" + +using namespace llvm; + +struct TestModulePass : public PassInfoMixin<TestModulePass> { + PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM) { + return PreservedAnalyses::all(); + } +}; + +void registerCallbacks(PassBuilder &PB) { + PB.registerPipelineParsingCallback( + [](StringRef Name, ModulePassManager &PM, + ArrayRef<PassBuilder::PipelineElement> InnerPipeline) { + if (Name == "plugin-pass") { + PM.addPass(TestModulePass()); + return true; + } + return false; + }); +} + +extern "C" ::llvm::PassPluginLibraryInfo LLVM_ATTRIBUTE_WEAK +llvmGetPassPluginInfo() { + return {LLVM_PLUGIN_API_VERSION, TEST_PLUGIN_NAME, TEST_PLUGIN_VERSION, + registerCallbacks}; +} diff --git a/unittests/Passes/TestPlugin.h b/unittests/Passes/TestPlugin.h new file mode 100644 index 000000000000..801a89065cda --- /dev/null +++ b/unittests/Passes/TestPlugin.h @@ -0,0 +1,2 @@ +#define TEST_PLUGIN_NAME "TestPlugin" +#define TEST_PLUGIN_VERSION "0.1-unit" diff --git a/unittests/ProfileData/CoverageMappingTest.cpp b/unittests/ProfileData/CoverageMappingTest.cpp index 4d0f852da982..987f67699585 100644 --- a/unittests/ProfileData/CoverageMappingTest.cpp +++ b/unittests/ProfileData/CoverageMappingTest.cpp @@ -859,17 +859,34 @@ TEST_P(CoverageMappingTest, load_coverage_for_expanded_file) { TEST_P(CoverageMappingTest, skip_duplicate_function_record) { ProfileWriter.addRecord({"func", 0x1234, {1}}, Err); + // This record should be loaded. startFunction("func", 0x1234); addCMR(Counter::getCounter(0), "file1", 1, 1, 9, 9); + // This record should be loaded. startFunction("func", 0x1234); addCMR(Counter::getCounter(0), "file1", 1, 1, 9, 9); + addCMR(Counter::getCounter(0), "file2", 1, 1, 9, 9); + + // This record should be skipped. + startFunction("func", 0x1234); + addCMR(Counter::getCounter(0), "file1", 1, 1, 9, 9); + + // This record should be loaded. + startFunction("func", 0x1234); + addCMR(Counter::getCounter(0), "file2", 1, 1, 9, 9); + addCMR(Counter::getCounter(0), "file1", 1, 1, 9, 9); + + // This record should be skipped. + startFunction("func", 0x1234); + addCMR(Counter::getCounter(0), "file1", 1, 1, 9, 9); + addCMR(Counter::getCounter(0), "file2", 1, 1, 9, 9); EXPECT_THAT_ERROR(loadCoverageMapping(), Succeeded()); auto Funcs = LoadedCoverage->getCoveredFunctions(); unsigned NumFuncs = std::distance(Funcs.begin(), Funcs.end()); - ASSERT_EQ(1U, NumFuncs); + ASSERT_EQ(3U, NumFuncs); } // FIXME: Use ::testing::Combine() when llvm updates its copy of googletest. diff --git a/unittests/ProfileData/InstrProfTest.cpp b/unittests/ProfileData/InstrProfTest.cpp index 79f880e475c6..0c99f7fde654 100644 --- a/unittests/ProfileData/InstrProfTest.cpp +++ b/unittests/ProfileData/InstrProfTest.cpp @@ -712,7 +712,7 @@ TEST_P(MaybeSparseInstrProfTest, value_prof_data_read_write) { }; std::unique_ptr<InstrProfValueData[]> VD_0( Record.getValueForSite(IPVK_IndirectCallTarget, 0)); - std::sort(&VD_0[0], &VD_0[5], Cmp); + llvm::sort(&VD_0[0], &VD_0[5], Cmp); ASSERT_EQ(StringRef((const char *)VD_0[0].Value, 7), StringRef("callee2")); ASSERT_EQ(1000U, VD_0[0].Count); ASSERT_EQ(StringRef((const char *)VD_0[1].Value, 7), StringRef("callee3")); @@ -726,7 +726,7 @@ TEST_P(MaybeSparseInstrProfTest, value_prof_data_read_write) { std::unique_ptr<InstrProfValueData[]> VD_1( Record.getValueForSite(IPVK_IndirectCallTarget, 1)); - std::sort(&VD_1[0], &VD_1[4], Cmp); + llvm::sort(&VD_1[0], &VD_1[4], Cmp); ASSERT_EQ(StringRef((const char *)VD_1[0].Value, 7), StringRef("callee2")); ASSERT_EQ(2500U, VD_1[0].Count); ASSERT_EQ(StringRef((const char *)VD_1[1].Value, 7), StringRef("callee1")); @@ -738,7 +738,7 @@ TEST_P(MaybeSparseInstrProfTest, value_prof_data_read_write) { std::unique_ptr<InstrProfValueData[]> VD_2( Record.getValueForSite(IPVK_IndirectCallTarget, 2)); - std::sort(&VD_2[0], &VD_2[3], Cmp); + llvm::sort(&VD_2[0], &VD_2[3], Cmp); ASSERT_EQ(StringRef((const char *)VD_2[0].Value, 7), StringRef("callee4")); ASSERT_EQ(5500U, VD_2[0].Count); ASSERT_EQ(StringRef((const char *)VD_2[1].Value, 7), StringRef("callee3")); @@ -748,7 +748,7 @@ TEST_P(MaybeSparseInstrProfTest, value_prof_data_read_write) { std::unique_ptr<InstrProfValueData[]> VD_3( Record.getValueForSite(IPVK_IndirectCallTarget, 3)); - std::sort(&VD_3[0], &VD_3[2], Cmp); + llvm::sort(&VD_3[0], &VD_3[2], Cmp); ASSERT_EQ(StringRef((const char *)VD_3[0].Value, 7), StringRef("callee3")); ASSERT_EQ(2000U, VD_3[0].Count); ASSERT_EQ(StringRef((const char *)VD_3[1].Value, 7), StringRef("callee2")); @@ -769,9 +769,8 @@ TEST_P(MaybeSparseInstrProfTest, value_prof_data_read_write_mapping) { Symtab.mapAddress(uint64_t(callee3), 0x3000ULL); Symtab.mapAddress(uint64_t(callee4), 0x4000ULL); // Missing mapping for callee5 - Symtab.finalizeSymtab(); - VPData->deserializeTo(Record, &Symtab.getAddrHashMap()); + VPData->deserializeTo(Record, &Symtab); // Now read data from Record and sanity check the data ASSERT_EQ(5U, Record.getNumValueSites(IPVK_IndirectCallTarget)); @@ -782,7 +781,7 @@ TEST_P(MaybeSparseInstrProfTest, value_prof_data_read_write_mapping) { }; std::unique_ptr<InstrProfValueData[]> VD_0( Record.getValueForSite(IPVK_IndirectCallTarget, 0)); - std::sort(&VD_0[0], &VD_0[5], Cmp); + llvm::sort(&VD_0[0], &VD_0[5], Cmp); ASSERT_EQ(VD_0[0].Value, 0x2000ULL); ASSERT_EQ(1000U, VD_0[0].Count); ASSERT_EQ(VD_0[1].Value, 0x3000ULL); @@ -858,8 +857,6 @@ TEST_P(MaybeSparseInstrProfTest, instr_prof_symtab_test) { EXPECT_THAT_ERROR(Symtab.addFuncName("blah_1"), Succeeded()); EXPECT_THAT_ERROR(Symtab.addFuncName("blah_2"), Succeeded()); EXPECT_THAT_ERROR(Symtab.addFuncName("blah_3"), Succeeded()); - // Finalize it - Symtab.finalizeSymtab(); // Check again R = Symtab.getFuncName(IndexedInstrProf::ComputeHash("blah_1")); diff --git a/unittests/ProfileData/SampleProfTest.cpp b/unittests/ProfileData/SampleProfTest.cpp index 764bded2f030..3ebfd0e500fe 100644 --- a/unittests/ProfileData/SampleProfTest.cpp +++ b/unittests/ProfileData/SampleProfTest.cpp @@ -77,6 +77,11 @@ struct SampleProfTest : ::testing::Test { BarSamples.addTotalSamples(20301); BarSamples.addHeadSamples(1437); BarSamples.addBodySamples(1, 0, 1437); + // Test how reader/writer handles unmangled names. + StringRef MconstructName("_M_construct<char *>"); + StringRef StringviewName("string_view<std::allocator<char> >"); + BarSamples.addCalledTargetSamples(1, 0, MconstructName, 1000); + BarSamples.addCalledTargetSamples(1, 0, StringviewName, 437); StringMap<FunctionSamples> Profiles; Profiles[FooName] = std::move(FooSamples); @@ -97,13 +102,29 @@ struct SampleProfTest : ::testing::Test { StringMap<FunctionSamples> &ReadProfiles = Reader->getProfiles(); ASSERT_EQ(2u, ReadProfiles.size()); - FunctionSamples &ReadFooSamples = ReadProfiles[FooName]; + std::string FooGUID; + StringRef FooRep = getRepInFormat(FooName, Format, FooGUID); + FunctionSamples &ReadFooSamples = ReadProfiles[FooRep]; ASSERT_EQ(7711u, ReadFooSamples.getTotalSamples()); ASSERT_EQ(610u, ReadFooSamples.getHeadSamples()); - FunctionSamples &ReadBarSamples = ReadProfiles[BarName]; + std::string BarGUID; + StringRef BarRep = getRepInFormat(BarName, Format, BarGUID); + FunctionSamples &ReadBarSamples = ReadProfiles[BarRep]; ASSERT_EQ(20301u, ReadBarSamples.getTotalSamples()); ASSERT_EQ(1437u, ReadBarSamples.getHeadSamples()); + ErrorOr<SampleRecord::CallTargetMap> CTMap = + ReadBarSamples.findCallTargetMapAt(1, 0); + ASSERT_FALSE(CTMap.getError()); + + std::string MconstructGUID; + StringRef MconstructRep = + getRepInFormat(MconstructName, Format, MconstructGUID); + std::string StringviewGUID; + StringRef StringviewRep = + getRepInFormat(StringviewName, Format, StringviewGUID); + ASSERT_EQ(1000u, CTMap.get()[MconstructRep]); + ASSERT_EQ(437u, CTMap.get()[StringviewRep]); auto VerifySummary = [](ProfileSummary &Summary) mutable { ASSERT_EQ(ProfileSummary::PSK_Sample, Summary.getKind()); @@ -158,10 +179,14 @@ TEST_F(SampleProfTest, roundtrip_text_profile) { testRoundTrip(SampleProfileFormat::SPF_Text); } -TEST_F(SampleProfTest, roundtrip_binary_profile) { +TEST_F(SampleProfTest, roundtrip_raw_binary_profile) { testRoundTrip(SampleProfileFormat::SPF_Binary); } +TEST_F(SampleProfTest, roundtrip_compact_binary_profile) { + testRoundTrip(SampleProfileFormat::SPF_Compact_Binary); +} + TEST_F(SampleProfTest, sample_overflow_saturation) { const uint64_t Max = std::numeric_limits<uint64_t>::max(); sampleprof_error Result; diff --git a/unittests/Support/AllocatorTest.cpp b/unittests/Support/AllocatorTest.cpp index 4897c47eb28b..74b394f1b176 100644 --- a/unittests/Support/AllocatorTest.cpp +++ b/unittests/Support/AllocatorTest.cpp @@ -147,7 +147,7 @@ public: // Allocate space for the alignment, the slab, and a void* that goes right // before the slab. size_t Alignment = 4096; - void *MemBase = malloc(Size + Alignment - 1 + sizeof(void*)); + void *MemBase = safe_malloc(Size + Alignment - 1 + sizeof(void*)); // Find the slab start. void *Slab = (void *)alignAddr((char*)MemBase + sizeof(void *), Alignment); diff --git a/unittests/Support/CMakeLists.txt b/unittests/Support/CMakeLists.txt index 299106e0dbf7..4e08d7df7ade 100644 --- a/unittests/Support/CMakeLists.txt +++ b/unittests/Support/CMakeLists.txt @@ -13,12 +13,15 @@ add_llvm_unittest(SupportTests CachePruningTest.cpp CrashRecoveryTest.cpp Casting.cpp + CheckedArithmeticTest.cpp Chrono.cpp CommandLineTest.cpp CompressionTest.cpp ConvertUTFTest.cpp DataExtractorTest.cpp DebugTest.cpp + DebugCounterTest.cpp + DJBTest.cpp EndianStreamTest.cpp EndianTest.cpp ErrnoTest.cpp @@ -28,6 +31,7 @@ add_llvm_unittest(SupportTests FormatVariadicTest.cpp GlobPatternTest.cpp Host.cpp + JSONTest.cpp LEB128Test.cpp LineIteratorTest.cpp LockFileManagerTest.cpp @@ -51,14 +55,17 @@ add_llvm_unittest(SupportTests SwapByteOrderTest.cpp TarWriterTest.cpp TargetParserTest.cpp + TaskQueueTest.cpp ThreadLocalTest.cpp ThreadPool.cpp Threading.cpp TimerTest.cpp TypeNameTest.cpp + TypeTraitsTest.cpp TrailingObjectsTest.cpp TrigramIndexTest.cpp UnicodeTest.cpp + VersionTupleTest.cpp YAMLIOTest.cpp YAMLParserTest.cpp formatted_raw_ostream_test.cpp diff --git a/unittests/Support/CheckedArithmeticTest.cpp b/unittests/Support/CheckedArithmeticTest.cpp new file mode 100644 index 000000000000..18eee4c447e4 --- /dev/null +++ b/unittests/Support/CheckedArithmeticTest.cpp @@ -0,0 +1,84 @@ +#include "llvm/Support/CheckedArithmetic.h" +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +TEST(CheckedArithmetic, CheckedAdd) { + const int64_t Max = std::numeric_limits<int64_t>::max(); + const int64_t Min = std::numeric_limits<int64_t>::min(); + EXPECT_EQ(checkedAdd<int64_t>(Max, Max), None); + EXPECT_EQ(checkedAdd<int64_t>(Min, -1), None); + EXPECT_EQ(checkedAdd<int64_t>(Max, 1), None); + EXPECT_EQ(checkedAdd<int64_t>(10, 1), Optional<int64_t>(11)); +} + +TEST(CheckedArithmetic, CheckedAddSmall) { + const int16_t Max = std::numeric_limits<int16_t>::max(); + const int16_t Min = std::numeric_limits<int16_t>::min(); + EXPECT_EQ(checkedAdd<int16_t>(Max, Max), None); + EXPECT_EQ(checkedAdd<int16_t>(Min, -1), None); + EXPECT_EQ(checkedAdd<int16_t>(Max, 1), None); + EXPECT_EQ(checkedAdd<int16_t>(10, 1), Optional<int64_t>(11)); +} + +TEST(CheckedArithmetic, CheckedMul) { + const int64_t Max = std::numeric_limits<int64_t>::max(); + const int64_t Min = std::numeric_limits<int64_t>::min(); + EXPECT_EQ(checkedMul<int64_t>(Max, 2), None); + EXPECT_EQ(checkedMul<int64_t>(Max, Max), None); + EXPECT_EQ(checkedMul<int64_t>(Min, 2), None); + EXPECT_EQ(checkedMul<int64_t>(10, 2), Optional<int64_t>(20)); +} + +TEST(CheckedArithmetic, CheckedMulAdd) { + const int64_t Max = std::numeric_limits<int64_t>::max(); + const int64_t Min = std::numeric_limits<int64_t>::min(); + EXPECT_EQ(checkedMulAdd<int64_t>(Max, 1, 2), None); + EXPECT_EQ(checkedMulAdd<int64_t>(1, 1, Max), None); + EXPECT_EQ(checkedMulAdd<int64_t>(1, -1, Min), None); + EXPECT_EQ(checkedMulAdd<int64_t>(10, 2, 3), Optional<int64_t>(23)); +} + +TEST(CheckedArithmetic, CheckedMulSmall) { + const int16_t Max = std::numeric_limits<int16_t>::max(); + const int16_t Min = std::numeric_limits<int16_t>::min(); + EXPECT_EQ(checkedMul<int16_t>(Max, 2), None); + EXPECT_EQ(checkedMul<int16_t>(Max, Max), None); + EXPECT_EQ(checkedMul<int16_t>(Min, 2), None); + EXPECT_EQ(checkedMul<int16_t>(10, 2), Optional<int16_t>(20)); +} + +TEST(CheckedArithmetic, CheckedMulAddSmall) { + const int16_t Max = std::numeric_limits<int16_t>::max(); + const int16_t Min = std::numeric_limits<int16_t>::min(); + EXPECT_EQ(checkedMulAdd<int16_t>(Max, 1, 2), None); + EXPECT_EQ(checkedMulAdd<int16_t>(1, 1, Max), None); + EXPECT_EQ(checkedMulAdd<int16_t>(1, -1, Min), None); + EXPECT_EQ(checkedMulAdd<int16_t>(10, 2, 3), Optional<int16_t>(23)); +} + +TEST(CheckedArithmetic, CheckedAddUnsigned) { + const uint64_t Max = std::numeric_limits<uint64_t>::max(); + EXPECT_EQ(checkedAddUnsigned<uint64_t>(Max, Max), None); + EXPECT_EQ(checkedAddUnsigned<uint64_t>(Max, 1), None); + EXPECT_EQ(checkedAddUnsigned<uint64_t>(10, 1), Optional<uint64_t>(11)); +} + +TEST(CheckedArithmetic, CheckedMulUnsigned) { + const uint64_t Max = std::numeric_limits<uint64_t>::max(); + EXPECT_EQ(checkedMulUnsigned<uint64_t>(Max, 2), llvm::None); + EXPECT_EQ(checkedMulUnsigned<uint64_t>(Max, Max), llvm::None); + EXPECT_EQ(checkedMulUnsigned<uint64_t>(10, 2), Optional<uint64_t>(20)); +} + +TEST(CheckedArithmetic, CheckedMulAddUnsigned) { + const uint64_t Max = std::numeric_limits<uint64_t>::max(); + EXPECT_EQ(checkedMulAddUnsigned<uint64_t>(Max, 1, 2), None); + EXPECT_EQ(checkedMulAddUnsigned<uint64_t>(1, 1, Max), None); + EXPECT_EQ(checkedMulAddUnsigned<uint64_t>(10, 2, 3), Optional<uint64_t>(23)); +} + + +} // namespace diff --git a/unittests/Support/CommandLineTest.cpp b/unittests/Support/CommandLineTest.cpp index 1fb0213b4d18..a296912a305e 100644 --- a/unittests/Support/CommandLineTest.cpp +++ b/unittests/Support/CommandLineTest.cpp @@ -10,8 +10,10 @@ #include "llvm/Support/CommandLine.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/SmallString.h" +#include "llvm/ADT/Triple.h" #include "llvm/Config/config.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" #include "llvm/Support/Program.h" #include "llvm/Support/StringSaver.h" @@ -50,26 +52,11 @@ class TempEnvVar { const char *const name; }; -template <typename T> -class StackOption : public cl::opt<T> { - typedef cl::opt<T> Base; +template <typename T, typename Base = cl::opt<T>> +class StackOption : public Base { public: - // One option... - template<class M0t> - explicit StackOption(const M0t &M0) : Base(M0) {} - - // Two options... - template<class M0t, class M1t> - StackOption(const M0t &M0, const M1t &M1) : Base(M0, M1) {} - - // Three options... - template<class M0t, class M1t, class M2t> - StackOption(const M0t &M0, const M1t &M1, const M2t &M2) : Base(M0, M1, M2) {} - - // Four options... - template<class M0t, class M1t, class M2t, class M3t> - StackOption(const M0t &M0, const M1t &M1, const M2t &M2, const M3t &M3) - : Base(M0, M1, M2, M3) {} + template <class... Ts> + explicit StackOption(Ts &&... Ms) : Base(std::forward<Ts>(Ms)...) {} ~StackOption() override { this->removeArgument(); } @@ -95,9 +82,9 @@ cl::OptionCategory TestCategory("Test Options", "Description"); TEST(CommandLineTest, ModifyExisitingOption) { StackOption<int> TestOption("test-option", cl::desc("old description")); - const char Description[] = "New description"; - const char ArgString[] = "new-test-option"; - const char ValueString[] = "Integer"; + static const char Description[] = "New description"; + static const char ArgString[] = "new-test-option"; + static const char ValueString[] = "Integer"; StringMap<cl::Option *> &Map = cl::getRegisteredOptions(*cl::TopLevelSubCommand); @@ -207,6 +194,85 @@ TEST(CommandLineTest, TokenizeWindowsCommandLine) { array_lengthof(Output)); } +TEST(CommandLineTest, TokenizeConfigFile1) { + const char *Input = "\\"; + const char *const Output[] = { "\\" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile2) { + const char *Input = "\\abc"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile3) { + const char *Input = "abc\\"; + const char *const Output[] = { "abc\\" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile4) { + const char *Input = "abc\\\n123"; + const char *const Output[] = { "abc123" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile5) { + const char *Input = "abc\\\r\n123"; + const char *const Output[] = { "abc123" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile6) { + const char *Input = "abc\\\n"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile7) { + const char *Input = "abc\\\r\n"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile8) { + SmallVector<const char *, 0> Actual; + BumpPtrAllocator A; + StringSaver Saver(A); + cl::tokenizeConfigFile("\\\n", Saver, Actual, /*MarkEOLs=*/false); + EXPECT_TRUE(Actual.empty()); +} + +TEST(CommandLineTest, TokenizeConfigFile9) { + SmallVector<const char *, 0> Actual; + BumpPtrAllocator A; + StringSaver Saver(A); + cl::tokenizeConfigFile("\\\r\n", Saver, Actual, /*MarkEOLs=*/false); + EXPECT_TRUE(Actual.empty()); +} + +TEST(CommandLineTest, TokenizeConfigFile10) { + const char *Input = "\\\nabc"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + +TEST(CommandLineTest, TokenizeConfigFile11) { + const char *Input = "\\\r\nabc"; + const char *const Output[] = { "abc" }; + testCommandLineTokenizer(cl::tokenizeConfigFile, Input, Output, + array_lengthof(Output)); +} + TEST(CommandLineTest, AliasesWithArguments) { static const size_t ARGC = 3; const char *const Inputs[][ARGC] = { @@ -552,6 +618,40 @@ TEST(CommandLineTest, ArgumentLimit) { EXPECT_FALSE(llvm::sys::commandLineFitsWithinSystemLimits("cl", args.data())); } +TEST(CommandLineTest, ResponseFileWindows) { + if (!Triple(sys::getProcessTriple()).isOSWindows()) + return; + + StackOption<std::string, cl::list<std::string>> InputFilenames( + cl::Positional, cl::desc("<input files>"), cl::ZeroOrMore); + StackOption<bool> TopLevelOpt("top-level", cl::init(false)); + + // Create response file. + int FileDescriptor; + SmallString<64> TempPath; + std::error_code EC = + llvm::sys::fs::createTemporaryFile("resp-", ".txt", FileDescriptor, TempPath); + EXPECT_TRUE(!EC); + + std::ofstream RspFile(TempPath.c_str()); + EXPECT_TRUE(RspFile.is_open()); + RspFile << "-top-level\npath\\dir\\file1\npath/dir/file2"; + RspFile.close(); + + llvm::SmallString<128> RspOpt; + RspOpt.append(1, '@'); + RspOpt.append(TempPath.c_str()); + const char *args[] = {"prog", RspOpt.c_str()}; + EXPECT_FALSE(TopLevelOpt); + EXPECT_TRUE( + cl::ParseCommandLineOptions(2, args, StringRef(), &llvm::nulls())); + EXPECT_TRUE(TopLevelOpt); + EXPECT_TRUE(InputFilenames[0] == "path\\dir\\file1"); + EXPECT_TRUE(InputFilenames[1] == "path/dir/file2"); + + llvm::sys::fs::remove(TempPath.c_str()); +} + TEST(CommandLineTest, ResponseFiles) { llvm::SmallString<128> TestDir; std::error_code EC = @@ -646,6 +746,98 @@ TEST(CommandLineTest, SetDefautValue) { EXPECT_TRUE(Opt1 == "true"); EXPECT_TRUE(Opt2); EXPECT_TRUE(Opt3 == 3); + Alias.removeArgument(); } +TEST(CommandLineTest, ReadConfigFile) { + llvm::SmallVector<const char *, 1> Argv; + + llvm::SmallString<128> TestDir; + std::error_code EC = + llvm::sys::fs::createUniqueDirectory("unittest", TestDir); + EXPECT_TRUE(!EC); + + llvm::SmallString<128> TestCfg; + llvm::sys::path::append(TestCfg, TestDir, "foo"); + std::ofstream ConfigFile(TestCfg.c_str()); + EXPECT_TRUE(ConfigFile.is_open()); + ConfigFile << "# Comment\n" + "-option_1\n" + "@subconfig\n" + "-option_3=abcd\n" + "-option_4=\\\n" + "cdef\n"; + ConfigFile.close(); + + llvm::SmallString<128> TestCfg2; + llvm::sys::path::append(TestCfg2, TestDir, "subconfig"); + std::ofstream ConfigFile2(TestCfg2.c_str()); + EXPECT_TRUE(ConfigFile2.is_open()); + ConfigFile2 << "-option_2\n" + "\n" + " # comment\n"; + ConfigFile2.close(); + + // Make sure the current directory is not the directory where config files + // resides. In this case the code that expands response files will not find + // 'subconfig' unless it resolves nested inclusions relative to the including + // file. + llvm::SmallString<128> CurrDir; + EC = llvm::sys::fs::current_path(CurrDir); + EXPECT_TRUE(!EC); + EXPECT_TRUE(StringRef(CurrDir) != StringRef(TestDir)); + + llvm::BumpPtrAllocator A; + llvm::StringSaver Saver(A); + bool Result = llvm::cl::readConfigFile(TestCfg, Saver, Argv); + + EXPECT_TRUE(Result); + EXPECT_EQ(Argv.size(), 4U); + EXPECT_STREQ(Argv[0], "-option_1"); + EXPECT_STREQ(Argv[1], "-option_2"); + EXPECT_STREQ(Argv[2], "-option_3=abcd"); + EXPECT_STREQ(Argv[3], "-option_4=cdef"); + + llvm::sys::fs::remove(TestCfg2); + llvm::sys::fs::remove(TestCfg); + llvm::sys::fs::remove(TestDir); +} + +TEST(CommandLineTest, PositionalEatArgsError) { + StackOption<std::string, cl::list<std::string>> PosEatArgs( + "positional-eat-args", cl::Positional, cl::desc("<arguments>..."), + cl::ZeroOrMore, cl::PositionalEatsArgs); + + const char *args[] = {"prog", "-positional-eat-args=XXXX"}; + const char *args2[] = {"prog", "-positional-eat-args=XXXX", "-foo"}; + const char *args3[] = {"prog", "-positional-eat-args", "-foo"}; + + std::string Errs; + raw_string_ostream OS(Errs); + EXPECT_FALSE(cl::ParseCommandLineOptions(2, args, StringRef(), &OS)); OS.flush(); + EXPECT_FALSE(Errs.empty()); Errs.clear(); + EXPECT_FALSE(cl::ParseCommandLineOptions(3, args2, StringRef(), &OS)); OS.flush(); + EXPECT_FALSE(Errs.empty()); Errs.clear(); + EXPECT_TRUE(cl::ParseCommandLineOptions(3, args3, StringRef(), &OS)); OS.flush(); + EXPECT_TRUE(Errs.empty()); +} + +#ifdef _WIN32 +TEST(CommandLineTest, GetCommandLineArguments) { + int argc = __argc; + char **argv = __argv; + + // GetCommandLineArguments is called in InitLLVM. + llvm::InitLLVM X(argc, argv); + + EXPECT_EQ(llvm::sys::path::is_absolute(argv[0]), + llvm::sys::path::is_absolute(__argv[0])); + + EXPECT_TRUE(llvm::sys::path::filename(argv[0]) + .equals_lower("supporttests.exe")) + << "Filename of test executable is " + << llvm::sys::path::filename(argv[0]); +} +#endif + } // anonymous namespace diff --git a/unittests/Support/CrashRecoveryTest.cpp b/unittests/Support/CrashRecoveryTest.cpp index 3f13693632db..ac531b246c6e 100644 --- a/unittests/Support/CrashRecoveryTest.cpp +++ b/unittests/Support/CrashRecoveryTest.cpp @@ -11,7 +11,7 @@ #include "llvm/Support/CrashRecoveryContext.h" #include "gtest/gtest.h" -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 #define WIN32_LEAN_AND_MEAN #define NOGDI #include <windows.h> @@ -61,7 +61,7 @@ TEST(CrashRecoveryTest, Cleanup) { EXPECT_EQ(1, GlobalInt); } -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 static void raiseIt() { RaiseException(123, EXCEPTION_NONCONTINUABLE, 0, NULL); } diff --git a/unittests/Support/DJBTest.cpp b/unittests/Support/DJBTest.cpp new file mode 100644 index 000000000000..b157b96e6b11 --- /dev/null +++ b/unittests/Support/DJBTest.cpp @@ -0,0 +1,96 @@ +//===---------- llvm/unittest/Support/DJBTest.cpp -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/DJB.h" +#include "llvm/ADT/Twine.h" +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(DJBTest, caseFolding) { + struct TestCase { + StringLiteral One; + StringLiteral Two; + }; + + static constexpr TestCase Tests[] = { + {{"ASDF"}, {"asdf"}}, + {{"qWeR"}, {"QwEr"}}, + {{"qqqqqqqqqqqqqqqqqqqq"}, {"QQQQQQQQQQQQQQQQQQQQ"}}, + + {{"I"}, {"i"}}, + // Latin Small Letter Dotless I + {{u8"\u0130"}, {"i"}}, + // Latin Capital Letter I With Dot Above + {{u8"\u0131"}, {"i"}}, + + // Latin Capital Letter A With Grave + {{u8"\u00c0"}, {u8"\u00e0"}}, + // Latin Capital Letter A With Macron + {{u8"\u0100"}, {u8"\u0101"}}, + // Latin Capital Letter L With Acute + {{u8"\u0139"}, {u8"\u013a"}}, + // Cyrillic Capital Letter Ie + {{u8"\u0415"}, {u8"\u0435"}}, + // Latin Capital Letter A With Circumflex And Grave + {{u8"\u1ea6"}, {u8"\u1ea7"}}, + // Kelvin Sign + {{u8"\u212a"}, {u8"\u006b"}}, + // Glagolitic Capital Letter Chrivi + {{u8"\u2c1d"}, {u8"\u2c4d"}}, + // Fullwidth Latin Capital Letter M + {{u8"\uff2d"}, {u8"\uff4d"}}, + // Old Hungarian Capital Letter Ej + {{u8"\U00010c92"}, {u8"\U00010cd2"}}, + }; + + for (const TestCase &T : Tests) { + SCOPED_TRACE("Comparing '" + T.One + "' and '" + T.Two + "'"); + EXPECT_EQ(caseFoldingDjbHash(T.One), caseFoldingDjbHash(T.Two)); + } +} + +TEST(DJBTest, knownValuesLowerCase) { + struct TestCase { + StringLiteral Text; + uint32_t Hash; + }; + static constexpr TestCase Tests[] = { + {{""}, 5381u}, + {{"f"}, 177675u}, + {{"fo"}, 5863386u}, + {{"foo"}, 193491849u}, + {{"foob"}, 2090263819u}, + {{"fooba"}, 259229388u}, + {{"foobar"}, 4259602622u}, + {{"pneumonoultramicroscopicsilicovolcanoconiosis"}, 3999417781u}, + }; + + for (const TestCase &T : Tests) { + SCOPED_TRACE("Text: '" + T.Text + "'"); + EXPECT_EQ(T.Hash, djbHash(T.Text)); + EXPECT_EQ(T.Hash, caseFoldingDjbHash(T.Text)); + EXPECT_EQ(T.Hash, caseFoldingDjbHash(T.Text.upper())); + } +} + +TEST(DJBTest, knownValuesUnicode) { + EXPECT_EQ(5866553u, djbHash(u8"\u0130")); + EXPECT_EQ(177678u, caseFoldingDjbHash(u8"\u0130")); + EXPECT_EQ( + 1302161417u, + djbHash( + u8"\u0130\u0131\u00c0\u00e0\u0100\u0101\u0139\u013a\u0415\u0435\u1ea6" + u8"\u1ea7\u212a\u006b\u2c1d\u2c4d\uff2d\uff4d\U00010c92\U00010cd2")); + EXPECT_EQ( + 1145571043u, + caseFoldingDjbHash( + u8"\u0130\u0131\u00c0\u00e0\u0100\u0101\u0139\u013a\u0415\u0435\u1ea6" + u8"\u1ea7\u212a\u006b\u2c1d\u2c4d\uff2d\uff4d\U00010c92\U00010cd2")); +} diff --git a/unittests/Support/DebugCounterTest.cpp b/unittests/Support/DebugCounterTest.cpp new file mode 100644 index 000000000000..f3c2817a22e1 --- /dev/null +++ b/unittests/Support/DebugCounterTest.cpp @@ -0,0 +1,42 @@ +//===- llvm/unittest/Support/DebugCounterTest.cpp -------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/DebugCounter.h" +#include "gtest/gtest.h" + +#include <string> +using namespace llvm; + +#ifndef NDEBUG +DEBUG_COUNTER(TestCounter, "test-counter", + "Counter used for unit test"); + +TEST(DebugCounterTest, CounterCheck) { + EXPECT_FALSE(DebugCounter::isCounterSet(TestCounter)); + + auto DC = &DebugCounter::instance(); + DC->push_back("test-counter-skip=1"); + DC->push_back("test-counter-count=3"); + + EXPECT_TRUE(DebugCounter::isCounterSet(TestCounter)); + + EXPECT_EQ(0, DebugCounter::getCounterValue(TestCounter)); + EXPECT_FALSE(DebugCounter::shouldExecute(TestCounter)); + + EXPECT_EQ(1, DebugCounter::getCounterValue(TestCounter)); + EXPECT_TRUE(DebugCounter::shouldExecute(TestCounter)); + + DebugCounter::setCounterValue(TestCounter, 3); + EXPECT_TRUE(DebugCounter::shouldExecute(TestCounter)); + EXPECT_FALSE(DebugCounter::shouldExecute(TestCounter)); + + DebugCounter::setCounterValue(TestCounter, 100); + EXPECT_FALSE(DebugCounter::shouldExecute(TestCounter)); +} +#endif diff --git a/unittests/Support/DynamicLibrary/CMakeLists.txt b/unittests/Support/DynamicLibrary/CMakeLists.txt index 4f060e4020d1..ec202295ac15 100644 --- a/unittests/Support/DynamicLibrary/CMakeLists.txt +++ b/unittests/Support/DynamicLibrary/CMakeLists.txt @@ -1,14 +1,23 @@ +# Needed by LLVM's CMake checks because this file defines multiple targets. +set(LLVM_OPTIONAL_SOURCES ExportedFuncs.cpp PipSqueak.cpp) + set(LLVM_LINK_COMPONENTS Support) -add_library(DynamicLibraryLib STATIC ExportedFuncs.cxx) +add_library(DynamicLibraryLib STATIC + ExportedFuncs.cpp + ) set_target_properties(DynamicLibraryLib PROPERTIES FOLDER "Tests") -add_llvm_unittest(DynamicLibraryTests DynamicLibraryTest.cpp) +add_llvm_unittest(DynamicLibraryTests + DynamicLibraryTest.cpp + ) target_link_libraries(DynamicLibraryTests PRIVATE DynamicLibraryLib) export_executable_symbols(DynamicLibraryTests) function(dynlib_add_module NAME) - add_library(${NAME} SHARED PipSqueak.cxx) + add_library(${NAME} SHARED + PipSqueak.cpp + ) set_target_properties(${NAME} PROPERTIES FOLDER "Tests") set_output_directory(${NAME} @@ -18,7 +27,7 @@ function(dynlib_add_module NAME) set_target_properties(${NAME} PROPERTIES PREFIX "" - SUFFIX ".so" + SUFFIX ${LTDL_SHLIB_EXT} ) add_dependencies(DynamicLibraryTests ${NAME}) diff --git a/unittests/Support/DynamicLibrary/DynamicLibraryTest.cpp b/unittests/Support/DynamicLibrary/DynamicLibraryTest.cpp index 370e1c5ed5e8..50a0f1e621fb 100644 --- a/unittests/Support/DynamicLibrary/DynamicLibraryTest.cpp +++ b/unittests/Support/DynamicLibrary/DynamicLibraryTest.cpp @@ -20,12 +20,14 @@ using namespace llvm; using namespace llvm::sys; std::string LibPath(const std::string Name = "PipSqueak") { - const std::vector<testing::internal::string>& Argvs = testing::internal::GetArgvs(); - const char *Argv0 = Argvs.size() > 0 ? Argvs[0].c_str() : "DynamicLibraryTests"; + const std::vector<testing::internal::string> &Argvs = + testing::internal::GetArgvs(); + const char *Argv0 = + Argvs.size() > 0 ? Argvs[0].c_str() : "DynamicLibraryTests"; void *Ptr = (void*)(intptr_t)TestA; std::string Path = fs::getMainExecutable(Argv0, Ptr); llvm::SmallString<256> Buf(path::parent_path(Path)); - path::append(Buf, (Name+".so").c_str()); + path::append(Buf, (Name + LTDL_SHLIB_EXT).c_str()); return Buf.str(); } diff --git a/unittests/Support/DynamicLibrary/ExportedFuncs.cxx b/unittests/Support/DynamicLibrary/ExportedFuncs.cpp index 97f190b0b9bc..370c8cb471d1 100644 --- a/unittests/Support/DynamicLibrary/ExportedFuncs.cxx +++ b/unittests/Support/DynamicLibrary/ExportedFuncs.cpp @@ -1,4 +1,4 @@ -//===- llvm/unittest/Support/DynamicLibrary/DynamicLibraryLib.cpp ---------===// +//===- llvm/unittest/Support/DynamicLibrary/ExportedFuncs.cpp -------------===// // // The LLVM Compiler Infrastructure // diff --git a/unittests/Support/DynamicLibrary/PipSqueak.cxx b/unittests/Support/DynamicLibrary/PipSqueak.cpp index 375d72c0b535..e2f1cf7de082 100644 --- a/unittests/Support/DynamicLibrary/PipSqueak.cxx +++ b/unittests/Support/DynamicLibrary/PipSqueak.cpp @@ -1,4 +1,4 @@ -//===- llvm/unittest/Support/DynamicLibrary/PipSqueak.cxx -----------------===// +//===- llvm/unittest/Support/DynamicLibrary/PipSqueak.cpp -----------------===// // // The LLVM Compiler Infrastructure // @@ -46,4 +46,4 @@ extern "C" PIPSQUEAK_EXPORT void TestOrder(std::vector<std::string> &V) { } #define PIPSQUEAK_TESTA_RETURN "LibCall" -#include "ExportedFuncs.cxx" +#include "ExportedFuncs.cpp" diff --git a/unittests/Support/EndianStreamTest.cpp b/unittests/Support/EndianStreamTest.cpp index 48c5c3bc8175..9f938eef8791 100644 --- a/unittests/Support/EndianStreamTest.cpp +++ b/unittests/Support/EndianStreamTest.cpp @@ -21,7 +21,7 @@ TEST(EndianStream, WriteInt32LE) { { raw_svector_ostream OS(data); - endian::Writer<little> LE(OS); + endian::Writer LE(OS, little); LE.write(static_cast<int32_t>(-1362446643)); } @@ -36,7 +36,7 @@ TEST(EndianStream, WriteInt32BE) { { raw_svector_ostream OS(data); - endian::Writer<big> BE(OS); + endian::Writer BE(OS, big); BE.write(static_cast<int32_t>(-1362446643)); } @@ -52,7 +52,7 @@ TEST(EndianStream, WriteFloatLE) { { raw_svector_ostream OS(data); - endian::Writer<little> LE(OS); + endian::Writer LE(OS, little); LE.write(12345.0f); } @@ -67,7 +67,7 @@ TEST(EndianStream, WriteFloatBE) { { raw_svector_ostream OS(data); - endian::Writer<big> BE(OS); + endian::Writer BE(OS, big); BE.write(12345.0f); } @@ -82,7 +82,7 @@ TEST(EndianStream, WriteInt64LE) { { raw_svector_ostream OS(data); - endian::Writer<little> LE(OS); + endian::Writer LE(OS, little); LE.write(static_cast<int64_t>(-136244664332342323)); } @@ -101,7 +101,7 @@ TEST(EndianStream, WriteInt64BE) { { raw_svector_ostream OS(data); - endian::Writer<big> BE(OS); + endian::Writer BE(OS, big); BE.write(static_cast<int64_t>(-136244664332342323)); } @@ -120,7 +120,7 @@ TEST(EndianStream, WriteDoubleLE) { { raw_svector_ostream OS(data); - endian::Writer<little> LE(OS); + endian::Writer LE(OS, little); LE.write(-2349214918.58107); } @@ -139,7 +139,7 @@ TEST(EndianStream, WriteDoubleBE) { { raw_svector_ostream OS(data); - endian::Writer<big> BE(OS); + endian::Writer BE(OS, big); BE.write(-2349214918.58107); } @@ -158,7 +158,7 @@ TEST(EndianStream, WriteArrayLE) { { raw_svector_ostream OS(Data); - endian::Writer<little> LE(OS); + endian::Writer LE(OS, little); LE.write<uint16_t>({0x1234, 0x5678}); } @@ -173,7 +173,7 @@ TEST(EndianStream, WriteVectorLE) { { raw_svector_ostream OS(Data); - endian::Writer<little> LE(OS); + endian::Writer LE(OS, little); std::vector<uint16_t> Vec{0x1234, 0x5678}; LE.write<uint16_t>(Vec); } @@ -189,7 +189,7 @@ TEST(EndianStream, WriteFloatArrayLE) { { raw_svector_ostream OS(Data); - endian::Writer<little> LE(OS); + endian::Writer LE(OS, little); LE.write<float>({12345.0f, 12346.0f}); } diff --git a/unittests/Support/ErrnoTest.cpp b/unittests/Support/ErrnoTest.cpp index 67f834a938da..701ac9608251 100644 --- a/unittests/Support/ErrnoTest.cpp +++ b/unittests/Support/ErrnoTest.cpp @@ -33,4 +33,7 @@ TEST(ErrnoTest, RetryAfterSignal) { std::unique_ptr<int> P(RetryAfterSignal(nullptr, [] { return new int(47); })); EXPECT_EQ(47, *P); + + errno = EINTR; + EXPECT_EQ(-1, RetryAfterSignal(-1, [] { return -1; })); } diff --git a/unittests/Support/ErrorOrTest.cpp b/unittests/Support/ErrorOrTest.cpp index 87dcab7b181e..2ffc6e5b7940 100644 --- a/unittests/Support/ErrorOrTest.cpp +++ b/unittests/Support/ErrorOrTest.cpp @@ -128,13 +128,13 @@ static_assert( // ErrorOr<int*> x(nullptr); // ErrorOr<std::unique_ptr<int>> y; // y = x; // invalid conversion -static_assert(!std::is_assignable<ErrorOr<std::unique_ptr<int>>, +static_assert(!std::is_assignable<ErrorOr<std::unique_ptr<int>>&, const ErrorOr<int *> &>::value, "do not invoke explicit ctors in assignment"); // ErrorOr<std::unique_ptr<int>> x; // x = ErrorOr<int*>(nullptr); // invalid conversion -static_assert(!std::is_assignable<ErrorOr<std::unique_ptr<int>>, +static_assert(!std::is_assignable<ErrorOr<std::unique_ptr<int>>&, ErrorOr<int *> &&>::value, "do not invoke explicit ctors in assignment"); } // end anon namespace diff --git a/unittests/Support/ErrorTest.cpp b/unittests/Support/ErrorTest.cpp index 2629e640f79c..66ffd23f8174 100644 --- a/unittests/Support/ErrorTest.cpp +++ b/unittests/Support/ErrorTest.cpp @@ -32,7 +32,7 @@ public: // Log this error to a stream. void log(raw_ostream &OS) const override { - OS << "CustomError { " << getInfo() << "}"; + OS << "CustomError {" << getInfo() << "}"; } std::error_code convertToErrorCode() const override { @@ -443,6 +443,29 @@ TEST(Error, StringError) { << "Failed to convert StringError to error_code."; } +TEST(Error, createStringError) { + static const char *Bar = "bar"; + static const std::error_code EC = errc::invalid_argument; + std::string Msg; + raw_string_ostream S(Msg); + logAllUnhandledErrors(createStringError(EC, "foo%s%d0x%" PRIx8, Bar, 1, 0xff), + S, ""); + EXPECT_EQ(S.str(), "foobar10xff\n") + << "Unexpected createStringError() log result"; + + S.flush(); + Msg.clear(); + logAllUnhandledErrors(createStringError(EC, Bar), S, ""); + EXPECT_EQ(S.str(), "bar\n") + << "Unexpected createStringError() (overloaded) log result"; + + S.flush(); + Msg.clear(); + auto Res = errorToErrorCode(createStringError(EC, "foo%s", Bar)); + EXPECT_EQ(Res, EC) + << "Failed to convert createStringError() result to error_code."; +} + // Test that the ExitOnError utility works as expected. TEST(Error, ExitOnError) { ExitOnError ExitOnErr; @@ -702,35 +725,79 @@ TEST(Error, ErrorMessage) { EXPECT_EQ(toString(Error::success()).compare(""), 0); Error E1 = make_error<CustomError>(0); - EXPECT_EQ(toString(std::move(E1)).compare("CustomError { 0}"), 0); + EXPECT_EQ(toString(std::move(E1)).compare("CustomError {0}"), 0); Error E2 = make_error<CustomError>(0); handleAllErrors(std::move(E2), [](const CustomError &CE) { - EXPECT_EQ(CE.message().compare("CustomError { 0}"), 0); + EXPECT_EQ(CE.message().compare("CustomError {0}"), 0); }); Error E3 = joinErrors(make_error<CustomError>(0), make_error<CustomError>(1)); EXPECT_EQ(toString(std::move(E3)) - .compare("CustomError { 0}\n" - "CustomError { 1}"), + .compare("CustomError {0}\n" + "CustomError {1}"), 0); } +TEST(Error, Stream) { + { + Error OK = Error::success(); + std::string Buf; + llvm::raw_string_ostream S(Buf); + S << OK; + EXPECT_EQ("success", S.str()); + consumeError(std::move(OK)); + } + { + Error E1 = make_error<CustomError>(0); + std::string Buf; + llvm::raw_string_ostream S(Buf); + S << E1; + EXPECT_EQ("CustomError {0}", S.str()); + consumeError(std::move(E1)); + } +} + TEST(Error, ErrorMatchers) { EXPECT_THAT_ERROR(Error::success(), Succeeded()); EXPECT_NONFATAL_FAILURE( EXPECT_THAT_ERROR(make_error<CustomError>(0), Succeeded()), - "Expected: succeeded\n Actual: failed (CustomError { 0})"); + "Expected: succeeded\n Actual: failed (CustomError {0})"); EXPECT_THAT_ERROR(make_error<CustomError>(0), Failed()); EXPECT_NONFATAL_FAILURE(EXPECT_THAT_ERROR(Error::success(), Failed()), "Expected: failed\n Actual: succeeded"); + EXPECT_THAT_ERROR(make_error<CustomError>(0), Failed<CustomError>()); + EXPECT_NONFATAL_FAILURE( + EXPECT_THAT_ERROR(Error::success(), Failed<CustomError>()), + "Expected: failed with Error of given type\n Actual: succeeded"); + EXPECT_NONFATAL_FAILURE( + EXPECT_THAT_ERROR(make_error<CustomError>(0), Failed<CustomSubError>()), + "Error was not of given type"); + EXPECT_NONFATAL_FAILURE( + EXPECT_THAT_ERROR( + joinErrors(make_error<CustomError>(0), make_error<CustomError>(1)), + Failed<CustomError>()), + "multiple errors"); + + EXPECT_THAT_ERROR( + make_error<CustomError>(0), + Failed<CustomError>(testing::Property(&CustomError::getInfo, 0))); + EXPECT_NONFATAL_FAILURE( + EXPECT_THAT_ERROR( + make_error<CustomError>(0), + Failed<CustomError>(testing::Property(&CustomError::getInfo, 1))), + "Expected: failed with Error of given type and the error is an object " + "whose given property is equal to 1\n" + " Actual: failed (CustomError {0})"); + EXPECT_THAT_ERROR(make_error<CustomError>(0), Failed<ErrorInfoBase>()); + EXPECT_THAT_EXPECTED(Expected<int>(0), Succeeded()); EXPECT_NONFATAL_FAILURE( EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), Succeeded()), - "Expected: succeeded\n Actual: failed (CustomError { 0})"); + "Expected: succeeded\n Actual: failed (CustomError {0})"); EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), Failed()); EXPECT_NONFATAL_FAILURE( @@ -742,7 +809,7 @@ TEST(Error, ErrorMatchers) { EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), HasValue(0)), "Expected: succeeded with value (is equal to 0)\n" - " Actual: failed (CustomError { 0})"); + " Actual: failed (CustomError {0})"); EXPECT_NONFATAL_FAILURE( EXPECT_THAT_EXPECTED(Expected<int>(1), HasValue(0)), "Expected: succeeded with value (is equal to 0)\n" @@ -762,7 +829,7 @@ TEST(Error, ErrorMatchers) { EXPECT_THAT_EXPECTED(Expected<int>(make_error<CustomError>(0)), HasValue(testing::Gt(1))), "Expected: succeeded with value (is > 1)\n" - " Actual: failed (CustomError { 0})"); + " Actual: failed (CustomError {0})"); } } // end anon namespace diff --git a/unittests/Support/FileOutputBufferTest.cpp b/unittests/Support/FileOutputBufferTest.cpp index e7f1fd765bde..554859587d45 100644 --- a/unittests/Support/FileOutputBufferTest.cpp +++ b/unittests/Support/FileOutputBufferTest.cpp @@ -11,6 +11,7 @@ #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorHandling.h" #include "llvm/Support/FileSystem.h" +#include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Path.h" #include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" @@ -121,4 +122,53 @@ TEST(FileOutputBuffer, Test) { // Clean up. ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); } + +TEST(FileOutputBuffer, TestModify) { + // Create unique temporary directory for these tests + SmallString<128> TestDirectory; + { + ASSERT_NO_ERROR( + fs::createUniqueDirectory("FileOutputBuffer-modify", TestDirectory)); + } + + SmallString<128> File1(TestDirectory); + File1.append("/file"); + // First write some data. + { + Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr = + FileOutputBuffer::create(File1, 10); + ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError())); + std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr; + memcpy(Buffer->getBufferStart(), "AAAAAAAAAA", 10); + ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit())); + } + + // Then re-open the file for modify and change only some bytes. + { + Expected<std::unique_ptr<FileOutputBuffer>> BufferOrErr = + FileOutputBuffer::create(File1, size_t(-1), FileOutputBuffer::F_modify); + ASSERT_NO_ERROR(errorToErrorCode(BufferOrErr.takeError())); + std::unique_ptr<FileOutputBuffer> &Buffer = *BufferOrErr; + ASSERT_EQ(10U, Buffer->getBufferSize()); + uint8_t *Data = Buffer->getBufferStart(); + Data[0] = 'X'; + Data[9] = 'X'; + ASSERT_NO_ERROR(errorToErrorCode(Buffer->commit())); + } + + // Finally, re-open the file for read and verify that it has the modified + // contents. + { + ErrorOr<std::unique_ptr<MemoryBuffer>> BufferOrErr = MemoryBuffer::getFile(File1); + ASSERT_NO_ERROR(BufferOrErr.getError()); + std::unique_ptr<MemoryBuffer> Buffer = std::move(*BufferOrErr); + ASSERT_EQ(10U, Buffer->getBufferSize()); + EXPECT_EQ(StringRef("XAAAAAAAAX"), Buffer->getBuffer()); + } + + // Clean up. + ASSERT_NO_ERROR(fs::remove(File1)); + ASSERT_NO_ERROR(fs::remove(TestDirectory)); +} + } // anonymous namespace diff --git a/unittests/Support/FormatVariadicTest.cpp b/unittests/Support/FormatVariadicTest.cpp index ddecffdeed1d..91a44bae3a9d 100644 --- a/unittests/Support/FormatVariadicTest.cpp +++ b/unittests/Support/FormatVariadicTest.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "llvm/Support/FormatVariadic.h" +#include "llvm/Support/Error.h" #include "llvm/Support/FormatAdapters.h" #include "gtest/gtest.h" @@ -143,6 +144,58 @@ TEST(FormatVariadicTest, ValidReplacementSequence) { EXPECT_EQ(0u, Replacements[0].Align); EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); EXPECT_EQ("0:1", Replacements[0].Options); + + // 9. Custom padding character + Replacements = formatv_object_base::parseFormatString("{0,p+4:foo}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ("0,p+4:foo", Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(4u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); + EXPECT_EQ('p', Replacements[0].Pad); + EXPECT_EQ("foo", Replacements[0].Options); + + // Format string special characters are allowed as padding character + Replacements = formatv_object_base::parseFormatString("{0,-+4:foo}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ("0,-+4:foo", Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(4u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Right, Replacements[0].Where); + EXPECT_EQ('-', Replacements[0].Pad); + EXPECT_EQ("foo", Replacements[0].Options); + + Replacements = formatv_object_base::parseFormatString("{0,+-4:foo}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ("0,+-4:foo", Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(4u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Left, Replacements[0].Where); + EXPECT_EQ('+', Replacements[0].Pad); + EXPECT_EQ("foo", Replacements[0].Options); + + Replacements = formatv_object_base::parseFormatString("{0,==4:foo}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ("0,==4:foo", Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(4u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Center, Replacements[0].Where); + EXPECT_EQ('=', Replacements[0].Pad); + EXPECT_EQ("foo", Replacements[0].Options); + + Replacements = formatv_object_base::parseFormatString("{0,:=4:foo}"); + ASSERT_EQ(1u, Replacements.size()); + EXPECT_EQ("0,:=4:foo", Replacements[0].Spec); + EXPECT_EQ(ReplacementType::Format, Replacements[0].Type); + EXPECT_EQ(0u, Replacements[0].Index); + EXPECT_EQ(4u, Replacements[0].Align); + EXPECT_EQ(AlignStyle::Center, Replacements[0].Where); + EXPECT_EQ(':', Replacements[0].Pad); + EXPECT_EQ("foo", Replacements[0].Options); } TEST(FormatVariadicTest, DefaultReplacementValues) { @@ -421,6 +474,16 @@ TEST(FormatVariadicTest, DoubleFormatting) { EXPECT_EQ(" -0.001", formatv("{0,8:F3}", -.0012345678).str()); } +TEST(FormatVariadicTest, CustomPaddingCharacter) { + // 1. Padding with custom character + EXPECT_EQ("==123", formatv("{0,=+5}", 123).str()); + EXPECT_EQ("=123=", formatv("{0,==5}", 123).str()); + EXPECT_EQ("123==", formatv("{0,=-5}", 123).str()); + + // 2. Combined with zero padding + EXPECT_EQ("=00123=", formatv("{0,==7:5}", 123).str()); +} + struct format_tuple { const char *Fmt; explicit format_tuple(const char *Fmt) : Fmt(Fmt) {} @@ -609,3 +672,20 @@ TEST(FormatVariadicTest, CopiesAndMoves) { EXPECT_EQ(0, R.Copied); EXPECT_EQ(0, R.Moved); } + +namespace adl { +struct X {}; +raw_ostream &operator<<(raw_ostream &OS, const X &) { return OS << "X"; } +} // namespace adl +TEST(FormatVariadicTest, FormatStreamable) { + adl::X X; + EXPECT_EQ("X", formatv("{0}", X).str()); +} + +TEST(FormatVariadicTest, FormatError) { + auto E1 = make_error<StringError>("X", inconvertibleErrorCode()); + EXPECT_EQ("X", formatv("{0}", E1).str()); + EXPECT_TRUE(E1.isA<StringError>()); // not consumed + EXPECT_EQ("X", formatv("{0}", fmt_consume(std::move(E1))).str()); + EXPECT_FALSE(E1.isA<StringError>()); // consumed +} diff --git a/unittests/Support/Host.cpp b/unittests/Support/Host.cpp index 736b04c2049c..e07d4151bb65 100644 --- a/unittests/Support/Host.cpp +++ b/unittests/Support/Host.cpp @@ -185,12 +185,12 @@ TEST_F(HostTest, getMacOSHostVersion) { path::append(OutputFile, "out"); const char *SwVersPath = "/usr/bin/sw_vers"; - const char *argv[] = {SwVersPath, "-productVersion", nullptr}; + StringRef argv[] = {SwVersPath, "-productVersion"}; StringRef OutputPath = OutputFile.str(); const Optional<StringRef> Redirects[] = {/*STDIN=*/None, /*STDOUT=*/OutputPath, /*STDERR=*/None}; - int RetCode = ExecuteAndWait(SwVersPath, argv, /*env=*/nullptr, Redirects); + int RetCode = ExecuteAndWait(SwVersPath, argv, /*env=*/llvm::None, Redirects); ASSERT_EQ(0, RetCode); int FD = 0; diff --git a/unittests/Support/JSONTest.cpp b/unittests/Support/JSONTest.cpp new file mode 100644 index 000000000000..64a2bb97bd8b --- /dev/null +++ b/unittests/Support/JSONTest.cpp @@ -0,0 +1,387 @@ +//===-- JSONTest.cpp - JSON unit tests --------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/JSON.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace llvm { +namespace json { + +namespace { + +std::string s(const Value &E) { return llvm::formatv("{0}", E).str(); } +std::string sp(const Value &E) { return llvm::formatv("{0:2}", E).str(); } + +TEST(JSONTest, Types) { + EXPECT_EQ("true", s(true)); + EXPECT_EQ("null", s(nullptr)); + EXPECT_EQ("2.5", s(2.5)); + EXPECT_EQ(R"("foo")", s("foo")); + EXPECT_EQ("[1,2,3]", s({1, 2, 3})); + EXPECT_EQ(R"({"x":10,"y":20})", s(Object{{"x", 10}, {"y", 20}})); + +#ifdef NDEBUG + EXPECT_EQ(R"("��")", s("\xC0\x80")); + EXPECT_EQ(R"({"��":0})", s(Object{{"\xC0\x80", 0}})); +#else + EXPECT_DEATH(s("\xC0\x80"), "Invalid UTF-8"); + EXPECT_DEATH(s(Object{{"\xC0\x80", 0}}), "Invalid UTF-8"); +#endif +} + +TEST(JSONTest, Constructors) { + // Lots of edge cases around empty and singleton init lists. + EXPECT_EQ("[[[3]]]", s({{{3}}})); + EXPECT_EQ("[[[]]]", s({{{}}})); + EXPECT_EQ("[[{}]]", s({{Object{}}})); + EXPECT_EQ(R"({"A":{"B":{}}})", s(Object{{"A", Object{{"B", Object{}}}}})); + EXPECT_EQ(R"({"A":{"B":{"X":"Y"}}})", + s(Object{{"A", Object{{"B", Object{{"X", "Y"}}}}}})); + EXPECT_EQ("null", s(llvm::Optional<double>())); + EXPECT_EQ("2.5", s(llvm::Optional<double>(2.5))); +} + +TEST(JSONTest, StringOwnership) { + char X[] = "Hello"; + Value Alias = static_cast<const char *>(X); + X[1] = 'a'; + EXPECT_EQ(R"("Hallo")", s(Alias)); + + std::string Y = "Hello"; + Value Copy = Y; + Y[1] = 'a'; + EXPECT_EQ(R"("Hello")", s(Copy)); +} + +TEST(JSONTest, CanonicalOutput) { + // Objects are sorted (but arrays aren't)! + EXPECT_EQ(R"({"a":1,"b":2,"c":3})", s(Object{{"a", 1}, {"c", 3}, {"b", 2}})); + EXPECT_EQ(R"(["a","c","b"])", s({"a", "c", "b"})); + EXPECT_EQ("3", s(3.0)); +} + +TEST(JSONTest, Escaping) { + std::string test = { + 0, // Strings may contain nulls. + '\b', '\f', // Have mnemonics, but we escape numerically. + '\r', '\n', '\t', // Escaped with mnemonics. + 'S', '\"', '\\', // Printable ASCII characters. + '\x7f', // Delete is not escaped. + '\xce', '\x94', // Non-ASCII UTF-8 is not escaped. + }; + + std::string teststring = R"("\u0000\u0008\u000c\r\n\tS\"\\)" + "\x7f\xCE\x94\""; + + EXPECT_EQ(teststring, s(test)); + + EXPECT_EQ(R"({"object keys are\nescaped":true})", + s(Object{{"object keys are\nescaped", true}})); +} + +TEST(JSONTest, PrettyPrinting) { + const char str[] = R"({ + "empty_array": [], + "empty_object": {}, + "full_array": [ + 1, + null + ], + "full_object": { + "nested_array": [ + { + "property": "value" + } + ] + } +})"; + + EXPECT_EQ(str, sp(Object{ + {"empty_object", Object{}}, + {"empty_array", {}}, + {"full_array", {1, nullptr}}, + {"full_object", + Object{ + {"nested_array", + {Object{ + {"property", "value"}, + }}}, + }}, + })); +} + +TEST(JSONTest, Parse) { + auto Compare = [](llvm::StringRef S, Value Expected) { + if (auto E = parse(S)) { + // Compare both string forms and with operator==, in case we have bugs. + EXPECT_EQ(*E, Expected); + EXPECT_EQ(sp(*E), sp(Expected)); + } else { + handleAllErrors(E.takeError(), [S](const llvm::ErrorInfoBase &E) { + FAIL() << "Failed to parse JSON >>> " << S << " <<<: " << E.message(); + }); + } + }; + + Compare(R"(true)", true); + Compare(R"(false)", false); + Compare(R"(null)", nullptr); + + Compare(R"(42)", 42); + Compare(R"(2.5)", 2.5); + Compare(R"(2e50)", 2e50); + Compare(R"(1.2e3456789)", std::numeric_limits<double>::infinity()); + + Compare(R"("foo")", "foo"); + Compare(R"("\"\\\b\f\n\r\t")", "\"\\\b\f\n\r\t"); + Compare(R"("\u0000")", llvm::StringRef("\0", 1)); + Compare("\"\x7f\"", "\x7f"); + Compare(R"("\ud801\udc37")", u8"\U00010437"); // UTF16 surrogate pair escape. + Compare("\"\xE2\x82\xAC\xF0\x9D\x84\x9E\"", u8"\u20ac\U0001d11e"); // UTF8 + Compare( + R"("LoneLeading=\ud801, LoneTrailing=\udc01, LeadingLeadingTrailing=\ud801\ud801\udc37")", + u8"LoneLeading=\ufffd, LoneTrailing=\ufffd, " + u8"LeadingLeadingTrailing=\ufffd\U00010437"); // Invalid unicode. + + Compare(R"({"":0,"":0})", Object{{"", 0}}); + Compare(R"({"obj":{},"arr":[]})", Object{{"obj", Object{}}, {"arr", {}}}); + Compare(R"({"\n":{"\u0000":[[[[]]]]}})", + Object{{"\n", Object{ + {llvm::StringRef("\0", 1), {{{{}}}}}, + }}}); + Compare("\r[\n\t] ", {}); +} + +TEST(JSONTest, ParseErrors) { + auto ExpectErr = [](llvm::StringRef Msg, llvm::StringRef S) { + if (auto E = parse(S)) { + // Compare both string forms and with operator==, in case we have bugs. + FAIL() << "Parsed JSON >>> " << S << " <<< but wanted error: " << Msg; + } else { + handleAllErrors(E.takeError(), [S, Msg](const llvm::ErrorInfoBase &E) { + EXPECT_THAT(E.message(), testing::HasSubstr(Msg)) << S; + }); + } + }; + ExpectErr("Unexpected EOF", ""); + ExpectErr("Unexpected EOF", "["); + ExpectErr("Text after end of document", "[][]"); + ExpectErr("Invalid JSON value (false?)", "fuzzy"); + ExpectErr("Expected , or ]", "[2?]"); + ExpectErr("Expected object key", "{a:2}"); + ExpectErr("Expected : after object key", R"({"a",2})"); + ExpectErr("Expected , or } after object property", R"({"a":2 "b":3})"); + ExpectErr("Invalid JSON value", R"([&%!])"); + ExpectErr("Invalid JSON value (number?)", "1e1.0"); + ExpectErr("Unterminated string", R"("abc\"def)"); + ExpectErr("Control character in string", "\"abc\ndef\""); + ExpectErr("Invalid escape sequence", R"("\030")"); + ExpectErr("Invalid \\u escape sequence", R"("\usuck")"); + ExpectErr("[3:3, byte=19]", R"({ + "valid": 1, + invalid: 2 +})"); + ExpectErr("Invalid UTF-8 sequence", "\"\xC0\x80\""); // WTF-8 null +} + +// Direct tests of isUTF8 and fixUTF8. Internal uses are also tested elsewhere. +TEST(JSONTest, UTF8) { + for (const char *Valid : { + "this is ASCII text", + "thïs tëxt häs BMP chäräctërs", + "𐌶𐌰L𐌾𐍈 C𐍈𐌼𐌴𐍃", + }) { + EXPECT_TRUE(isUTF8(Valid)) << Valid; + EXPECT_EQ(fixUTF8(Valid), Valid); + } + for (auto Invalid : std::vector<std::pair<const char *, const char *>>{ + {"lone trailing \x81\x82 bytes", "lone trailing �� bytes"}, + {"missing trailing \xD0 bytes", "missing trailing � bytes"}, + {"truncated character \xD0", "truncated character �"}, + {"not \xC1\x80 the \xE0\x9f\xBF shortest \xF0\x83\x83\x83 encoding", + "not �� the ��� shortest ���� encoding"}, + {"too \xF9\x80\x80\x80\x80 long", "too ����� long"}, + {"surrogate \xED\xA0\x80 invalid \xF4\x90\x80\x80", + "surrogate ��� invalid ����"}}) { + EXPECT_FALSE(isUTF8(Invalid.first)) << Invalid.first; + EXPECT_EQ(fixUTF8(Invalid.first), Invalid.second); + } +} + +TEST(JSONTest, Inspection) { + llvm::Expected<Value> Doc = parse(R"( + { + "null": null, + "boolean": false, + "number": 2.78, + "string": "json", + "array": [null, true, 3.14, "hello", [1,2,3], {"time": "arrow"}], + "object": {"fruit": "banana"} + } + )"); + EXPECT_TRUE(!!Doc); + + Object *O = Doc->getAsObject(); + ASSERT_TRUE(O); + + EXPECT_FALSE(O->getNull("missing")); + EXPECT_FALSE(O->getNull("boolean")); + EXPECT_TRUE(O->getNull("null")); + + EXPECT_EQ(O->getNumber("number"), llvm::Optional<double>(2.78)); + EXPECT_FALSE(O->getInteger("number")); + EXPECT_EQ(O->getString("string"), llvm::Optional<llvm::StringRef>("json")); + ASSERT_FALSE(O->getObject("missing")); + ASSERT_FALSE(O->getObject("array")); + ASSERT_TRUE(O->getObject("object")); + EXPECT_EQ(*O->getObject("object"), (Object{{"fruit", "banana"}})); + + Array *A = O->getArray("array"); + ASSERT_TRUE(A); + EXPECT_EQ((*A)[1].getAsBoolean(), llvm::Optional<bool>(true)); + ASSERT_TRUE((*A)[4].getAsArray()); + EXPECT_EQ(*(*A)[4].getAsArray(), (Array{1, 2, 3})); + EXPECT_EQ((*(*A)[4].getAsArray())[1].getAsInteger(), + llvm::Optional<int64_t>(2)); + int I = 0; + for (Value &E : *A) { + if (I++ == 5) { + ASSERT_TRUE(E.getAsObject()); + EXPECT_EQ(E.getAsObject()->getString("time"), + llvm::Optional<llvm::StringRef>("arrow")); + } else + EXPECT_FALSE(E.getAsObject()); + } +} + +// Verify special integer handling - we try to preserve exact int64 values. +TEST(JSONTest, Integers) { + struct { + const char *Desc; + Value Val; + const char *Str; + llvm::Optional<int64_t> AsInt; + llvm::Optional<double> AsNumber; + } TestCases[] = { + { + "Non-integer. Stored as double, not convertible.", + double{1.5}, + "1.5", + llvm::None, + 1.5, + }, + + { + "Integer, not exact double. Stored as int64, convertible.", + int64_t{0x4000000000000001}, + "4611686018427387905", + int64_t{0x4000000000000001}, + double{0x4000000000000000}, + }, + + { + "Negative integer, not exact double. Stored as int64, convertible.", + int64_t{-0x4000000000000001}, + "-4611686018427387905", + int64_t{-0x4000000000000001}, + double{-0x4000000000000000}, + }, + + { + "Dynamically exact integer. Stored as double, convertible.", + double{0x6000000000000000}, + "6.9175290276410819e+18", + int64_t{0x6000000000000000}, + double{0x6000000000000000}, + }, + + { + "Dynamically integer, >64 bits. Stored as double, not convertible.", + 1.5 * double{0x8000000000000000}, + "1.3835058055282164e+19", + llvm::None, + 1.5 * double{0x8000000000000000}, + }, + }; + for (const auto &T : TestCases) { + EXPECT_EQ(T.Str, s(T.Val)) << T.Desc; + llvm::Expected<Value> Doc = parse(T.Str); + EXPECT_TRUE(!!Doc) << T.Desc; + EXPECT_EQ(Doc->getAsInteger(), T.AsInt) << T.Desc; + EXPECT_EQ(Doc->getAsNumber(), T.AsNumber) << T.Desc; + EXPECT_EQ(T.Val, *Doc) << T.Desc; + EXPECT_EQ(T.Str, s(*Doc)) << T.Desc; + } +} + +// Sample struct with typical JSON-mapping rules. +struct CustomStruct { + CustomStruct() : B(false) {} + CustomStruct(std::string S, llvm::Optional<int> I, bool B) + : S(S), I(I), B(B) {} + std::string S; + llvm::Optional<int> I; + bool B; +}; +inline bool operator==(const CustomStruct &L, const CustomStruct &R) { + return L.S == R.S && L.I == R.I && L.B == R.B; +} +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + const CustomStruct &S) { + return OS << "(" << S.S << ", " << (S.I ? std::to_string(*S.I) : "None") + << ", " << S.B << ")"; +} +bool fromJSON(const Value &E, CustomStruct &R) { + ObjectMapper O(E); + if (!O || !O.map("str", R.S) || !O.map("int", R.I)) + return false; + O.map("bool", R.B); + return true; +} + +TEST(JSONTest, Deserialize) { + std::map<std::string, std::vector<CustomStruct>> R; + CustomStruct ExpectedStruct = {"foo", 42, true}; + std::map<std::string, std::vector<CustomStruct>> Expected; + Value J = Object{ + {"foo", + Array{ + Object{ + {"str", "foo"}, + {"int", 42}, + {"bool", true}, + {"unknown", "ignored"}, + }, + Object{{"str", "bar"}}, + Object{ + {"str", "baz"}, {"bool", "string"}, // OK, deserialize ignores. + }, + }}}; + Expected["foo"] = { + CustomStruct("foo", 42, true), + CustomStruct("bar", llvm::None, false), + CustomStruct("baz", llvm::None, false), + }; + ASSERT_TRUE(fromJSON(J, R)); + EXPECT_EQ(R, Expected); + + CustomStruct V; + EXPECT_FALSE(fromJSON(nullptr, V)) << "Not an object " << V; + EXPECT_FALSE(fromJSON(Object{}, V)) << "Missing required field " << V; + EXPECT_FALSE(fromJSON(Object{{"str", 1}}, V)) << "Wrong type " << V; + // Optional<T> must parse as the correct type if present. + EXPECT_FALSE(fromJSON(Object{{"str", 1}, {"int", "string"}}, V)) + << "Wrong type for Optional<T> " << V; +} + +} // namespace +} // namespace json +} // namespace llvm diff --git a/unittests/Support/LockFileManagerTest.cpp b/unittests/Support/LockFileManagerTest.cpp index efe3c3088b33..1775d05e44db 100644 --- a/unittests/Support/LockFileManagerTest.cpp +++ b/unittests/Support/LockFileManagerTest.cpp @@ -60,7 +60,7 @@ TEST(LockFileManagerTest, LinkLockExists) { sys::path::append(TmpFileLock, "file.lock-000"); int FD; - EC = sys::fs::openFileForWrite(StringRef(TmpFileLock), FD, sys::fs::F_None); + EC = sys::fs::openFileForWrite(StringRef(TmpFileLock), FD); ASSERT_FALSE(EC); int Ret = close(FD); diff --git a/unittests/Support/MD5Test.cpp b/unittests/Support/MD5Test.cpp index 8b151827a7bd..bac1ec2f2b90 100644 --- a/unittests/Support/MD5Test.cpp +++ b/unittests/Support/MD5Test.cpp @@ -19,7 +19,7 @@ using namespace llvm; namespace { -/// \brief Tests an arbitrary set of bytes passed as \p Input. +/// Tests an arbitrary set of bytes passed as \p Input. void TestMD5Sum(ArrayRef<uint8_t> Input, StringRef Final) { MD5 Hash; Hash.update(Input); diff --git a/unittests/Support/ManagedStatic.cpp b/unittests/Support/ManagedStatic.cpp index 07e324cdfb65..d3cc80cf5e92 100644 --- a/unittests/Support/ManagedStatic.cpp +++ b/unittests/Support/ManagedStatic.cpp @@ -6,6 +6,8 @@ // License. See LICENSE.TXT for details. // //===----------------------------------------------------------------------===// + +#include "llvm/Support/Allocator.h" #include "llvm/Support/ManagedStatic.h" #include "llvm/Config/config.h" #ifdef HAVE_PTHREAD_H @@ -30,7 +32,7 @@ namespace test1 { // Valgrind's leak checker complains glibc's stack allocation. // To appease valgrind, we provide our own stack for each thread. void *allocate_stack(pthread_attr_t &a, size_t n = 65536) { - void *stack = malloc(n); + void *stack = safe_malloc(n); pthread_attr_init(&a); #if defined(__linux__) pthread_attr_setstack(&a, stack, n); @@ -83,7 +85,7 @@ TEST(ManagedStaticTest, NestedStatics) { namespace CustomCreatorDeletor { struct CustomCreate { static void *call() { - void *Mem = std::malloc(sizeof(int)); + void *Mem = safe_malloc(sizeof(int)); *((int *)Mem) = 42; return Mem; } diff --git a/unittests/Support/MemoryBufferTest.cpp b/unittests/Support/MemoryBufferTest.cpp index 5e3c8db02791..034b2ea9495d 100644 --- a/unittests/Support/MemoryBufferTest.cpp +++ b/unittests/Support/MemoryBufferTest.cpp @@ -116,13 +116,13 @@ TEST_F(MemoryBufferTest, make_new) { EXPECT_TRUE(nullptr != Two.get()); // 0-initialized buffer with no name - OwningBuffer Three(MemoryBuffer::getNewMemBuffer(321, data)); + OwningBuffer Three(WritableMemoryBuffer::getNewMemBuffer(321, data)); EXPECT_TRUE(nullptr != Three.get()); for (size_t i = 0; i < 321; ++i) EXPECT_EQ(0, Three->getBufferStart()[0]); // 0-initialized buffer with name - OwningBuffer Four(MemoryBuffer::getNewMemBuffer(123, "zeros")); + OwningBuffer Four(WritableMemoryBuffer::getNewMemBuffer(123, "zeros")); EXPECT_TRUE(nullptr != Four.get()); for (size_t i = 0; i < 123; ++i) EXPECT_EQ(0, Four->getBufferStart()[0]); @@ -260,4 +260,33 @@ TEST_F(MemoryBufferTest, writableSlice) { for (size_t i = 0; i < MB.getBufferSize(); i += 0x10) EXPECT_EQ("0123456789abcdef", MB.getBuffer().substr(i, 0x10)) << "i: " << i; } + +TEST_F(MemoryBufferTest, writeThroughFile) { + // Create a file initialized with some data + int FD; + SmallString<64> TestPath; + sys::fs::createTemporaryFile("MemoryBufferTest_WriteThrough", "temp", FD, + TestPath); + FileRemover Cleanup(TestPath); + raw_fd_ostream OF(FD, true); + OF << "0123456789abcdef"; + OF.close(); + { + auto MBOrError = WriteThroughMemoryBuffer::getFile(TestPath); + ASSERT_FALSE(MBOrError.getError()); + // Write some data. It should be mapped readwrite, so that upon completion + // the original file contents are modified. + WriteThroughMemoryBuffer &MB = **MBOrError; + ASSERT_EQ(16u, MB.getBufferSize()); + char *Start = MB.getBufferStart(); + ASSERT_EQ(MB.getBufferEnd(), MB.getBufferStart() + MB.getBufferSize()); + ::memset(Start, 'x', MB.getBufferSize()); + } + + auto MBOrError = MemoryBuffer::getFile(TestPath); + ASSERT_FALSE(MBOrError.getError()); + auto &MB = **MBOrError; + ASSERT_EQ(16u, MB.getBufferSize()); + EXPECT_EQ("xxxxxxxxxxxxxxxx", MB.getBuffer()); +} } diff --git a/unittests/Support/ParallelTest.cpp b/unittests/Support/ParallelTest.cpp index d734e0dd8586..8779a61b1853 100644 --- a/unittests/Support/ParallelTest.cpp +++ b/unittests/Support/ParallelTest.cpp @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// /// /// \file -/// \brief Parallel.h unit tests. +/// Parallel.h unit tests. /// //===----------------------------------------------------------------------===// diff --git a/unittests/Support/Path.cpp b/unittests/Support/Path.cpp index f624626f5e53..dc5bfb8c7bd5 100644 --- a/unittests/Support/Path.cpp +++ b/unittests/Support/Path.cpp @@ -12,6 +12,7 @@ #include "llvm/ADT/SmallVector.h" #include "llvm/ADT/Triple.h" #include "llvm/BinaryFormat/Magic.h" +#include "llvm/Config/llvm-config.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Errc.h" #include "llvm/Support/ErrorHandling.h" @@ -21,9 +22,11 @@ #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/raw_ostream.h" #include "gtest/gtest.h" +#include "gmock/gmock.h" -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 #include "llvm/ADT/ArrayRef.h" +#include "llvm/Support/Chrono.h" #include <windows.h> #include <winerror.h> #endif @@ -47,8 +50,22 @@ using namespace llvm::sys; } else { \ } +#define ASSERT_ERROR(x) \ + if (!x) { \ + SmallString<128> MessageStorage; \ + raw_svector_ostream Message(MessageStorage); \ + Message << #x ": did not return a failure error code.\n"; \ + GTEST_FATAL_FAILURE_(MessageStorage.c_str()); \ + } + namespace { +struct FileDescriptorCloser { + explicit FileDescriptorCloser(int FD) : FD(FD) {} + ~FileDescriptorCloser() { ::close(FD); } + int FD; +}; + TEST(is_separator, Works) { EXPECT_TRUE(path::is_separator('/')); EXPECT_FALSE(path::is_separator('\0')); @@ -58,7 +75,7 @@ TEST(is_separator, Works) { EXPECT_TRUE(path::is_separator('\\', path::Style::windows)); EXPECT_FALSE(path::is_separator('\\', path::Style::posix)); -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 EXPECT_TRUE(path::is_separator('\\')); #else EXPECT_FALSE(path::is_separator('\\')); @@ -78,6 +95,7 @@ TEST(Support, Path) { paths.push_back("foo/bar"); paths.push_back("/foo/bar"); paths.push_back("//net"); + paths.push_back("//net/"); paths.push_back("//net/foo"); paths.push_back("///foo///"); paths.push_back("///foo///bar"); @@ -108,27 +126,30 @@ TEST(Support, Path) { paths.push_back("c:\\foo/"); paths.push_back("c:/foo\\bar"); - SmallVector<StringRef, 5> ComponentStack; for (SmallVector<StringRef, 40>::const_iterator i = paths.begin(), e = paths.end(); i != e; ++i) { + SCOPED_TRACE(*i); + SmallVector<StringRef, 5> ComponentStack; for (sys::path::const_iterator ci = sys::path::begin(*i), ce = sys::path::end(*i); ci != ce; ++ci) { - ASSERT_FALSE(ci->empty()); + EXPECT_FALSE(ci->empty()); ComponentStack.push_back(*ci); } + SmallVector<StringRef, 5> ReverseComponentStack; for (sys::path::reverse_iterator ci = sys::path::rbegin(*i), ce = sys::path::rend(*i); ci != ce; ++ci) { - ASSERT_TRUE(*ci == ComponentStack.back()); - ComponentStack.pop_back(); + EXPECT_FALSE(ci->empty()); + ReverseComponentStack.push_back(*ci); } - ASSERT_TRUE(ComponentStack.empty()); + std::reverse(ReverseComponentStack.begin(), ReverseComponentStack.end()); + EXPECT_THAT(ComponentStack, testing::ContainerEq(ReverseComponentStack)); // Crash test most of the API - since we're iterating over all of our paths // here there isn't really anything reasonable to assert on in the results. @@ -171,115 +192,77 @@ TEST(Support, Path) { ASSERT_EQ("/root/foo.cpp", Relative); } -TEST(Support, RelativePathIterator) { - SmallString<64> Path(StringRef("c/d/e/foo.txt")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; +TEST(Support, FilenameParent) { + EXPECT_EQ("/", path::filename("/")); + EXPECT_EQ("", path::parent_path("/")); - StringRef(Path).split(ExpectedPathComponents, '/'); + EXPECT_EQ("\\", path::filename("c:\\", path::Style::windows)); + EXPECT_EQ("c:", path::parent_path("c:\\", path::Style::windows)); - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } + EXPECT_EQ("/", path::filename("///")); + EXPECT_EQ("", path::parent_path("///")); - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); + EXPECT_EQ("\\", path::filename("c:\\\\", path::Style::windows)); + EXPECT_EQ("c:", path::parent_path("c:\\\\", path::Style::windows)); - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } -} - -TEST(Support, RelativePathDotIterator) { - SmallString<64> Path(StringRef(".c/.d/../.")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; - - StringRef(Path).split(ExpectedPathComponents, '/'); + EXPECT_EQ("bar", path::filename("/foo/bar")); + EXPECT_EQ("/foo", path::parent_path("/foo/bar")); - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } + EXPECT_EQ("foo", path::filename("/foo")); + EXPECT_EQ("/", path::parent_path("/foo")); - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); + EXPECT_EQ("foo", path::filename("foo")); + EXPECT_EQ("", path::parent_path("foo")); - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } -} + EXPECT_EQ(".", path::filename("foo/")); + EXPECT_EQ("foo", path::parent_path("foo/")); -TEST(Support, AbsolutePathIterator) { - SmallString<64> Path(StringRef("/c/d/e/foo.txt")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; + EXPECT_EQ("//net", path::filename("//net")); + EXPECT_EQ("", path::parent_path("//net")); - StringRef(Path).split(ExpectedPathComponents, '/'); + EXPECT_EQ("/", path::filename("//net/")); + EXPECT_EQ("//net", path::parent_path("//net/")); - // The root path will also be a component when iterating - ExpectedPathComponents[0] = "/"; + EXPECT_EQ("foo", path::filename("//net/foo")); + EXPECT_EQ("//net/", path::parent_path("//net/foo")); - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } + // These checks are just to make sure we do something reasonable with the + // paths below. They are not meant to prescribe the one true interpretation of + // these paths. Other decompositions (e.g. "//" -> "" + "//") are also + // possible. + EXPECT_EQ("/", path::filename("//")); + EXPECT_EQ("", path::parent_path("//")); - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); + EXPECT_EQ("\\", path::filename("\\\\", path::Style::windows)); + EXPECT_EQ("", path::parent_path("\\\\", path::Style::windows)); - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } + EXPECT_EQ("\\", path::filename("\\\\\\", path::Style::windows)); + EXPECT_EQ("", path::parent_path("\\\\\\", path::Style::windows)); } -TEST(Support, AbsolutePathDotIterator) { - SmallString<64> Path(StringRef("/.c/.d/../.")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; - - StringRef(Path).split(ExpectedPathComponents, '/'); - - // The root path will also be a component when iterating - ExpectedPathComponents[0] = "/"; - - for (path::const_iterator I = path::begin(Path), E = path::end(Path); I != E; - ++I) { - ActualPathComponents.push_back(*I); - } - - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); - - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } +static std::vector<StringRef> +GetComponents(StringRef Path, path::Style S = path::Style::native) { + return {path::begin(Path, S), path::end(Path)}; } -TEST(Support, AbsolutePathIteratorWin32) { - SmallString<64> Path(StringRef("c:\\c\\e\\foo.txt")); - typedef SmallVector<StringRef, 4> PathComponents; - PathComponents ExpectedPathComponents; - PathComponents ActualPathComponents; - - StringRef(Path).split(ExpectedPathComponents, "\\"); - - // The root path (which comes after the drive name) will also be a component - // when iterating. - ExpectedPathComponents.insert(ExpectedPathComponents.begin()+1, "\\"); - - for (path::const_iterator I = path::begin(Path, path::Style::windows), - E = path::end(Path); - I != E; ++I) { - ActualPathComponents.push_back(*I); - } - - ASSERT_EQ(ExpectedPathComponents.size(), ActualPathComponents.size()); - - for (size_t i = 0; i <ExpectedPathComponents.size(); ++i) { - EXPECT_EQ(ExpectedPathComponents[i].str(), ActualPathComponents[i].str()); - } +TEST(Support, PathIterator) { + EXPECT_THAT(GetComponents("/foo"), testing::ElementsAre("/", "foo")); + EXPECT_THAT(GetComponents("/"), testing::ElementsAre("/")); + EXPECT_THAT(GetComponents("//"), testing::ElementsAre("/")); + EXPECT_THAT(GetComponents("///"), testing::ElementsAre("/")); + EXPECT_THAT(GetComponents("c/d/e/foo.txt"), + testing::ElementsAre("c", "d", "e", "foo.txt")); + EXPECT_THAT(GetComponents(".c/.d/../."), + testing::ElementsAre(".c", ".d", "..", ".")); + EXPECT_THAT(GetComponents("/c/d/e/foo.txt"), + testing::ElementsAre("/", "c", "d", "e", "foo.txt")); + EXPECT_THAT(GetComponents("/.c/.d/../."), + testing::ElementsAre("/", ".c", ".d", "..", ".")); + EXPECT_THAT(GetComponents("c:\\c\\e\\foo.txt", path::Style::windows), + testing::ElementsAre("c:", "\\", "c", "e", "foo.txt")); + EXPECT_THAT(GetComponents("//net/"), testing::ElementsAre("//net", "/")); + EXPECT_THAT(GetComponents("//net/c/foo.txt"), + testing::ElementsAre("//net", "/", "c", "foo.txt")); } TEST(Support, AbsolutePathIteratorEnd) { @@ -287,10 +270,11 @@ TEST(Support, AbsolutePathIteratorEnd) { SmallVector<std::pair<StringRef, path::Style>, 4> Paths; Paths.emplace_back("/foo/", path::Style::native); Paths.emplace_back("/foo//", path::Style::native); - Paths.emplace_back("//net//", path::Style::native); - Paths.emplace_back("c:\\\\", path::Style::windows); + Paths.emplace_back("//net/foo/", path::Style::native); + Paths.emplace_back("c:\\foo\\", path::Style::windows); for (auto &Path : Paths) { + SCOPED_TRACE(Path.first); StringRef LastComponent = *path::rbegin(Path.first, Path.second); EXPECT_EQ(".", LastComponent); } @@ -299,8 +283,11 @@ TEST(Support, AbsolutePathIteratorEnd) { RootPaths.emplace_back("/", path::Style::native); RootPaths.emplace_back("//net/", path::Style::native); RootPaths.emplace_back("c:\\", path::Style::windows); + RootPaths.emplace_back("//net//", path::Style::native); + RootPaths.emplace_back("c:\\\\", path::Style::windows); for (auto &Path : RootPaths) { + SCOPED_TRACE(Path.first); StringRef LastComponent = *path::rbegin(Path.first, Path.second); EXPECT_EQ(1u, LastComponent.size()); EXPECT_TRUE(path::is_separator(LastComponent[0], Path.second)); @@ -309,7 +296,7 @@ TEST(Support, AbsolutePathIteratorEnd) { TEST(Support, HomeDirectory) { std::string expected; -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 if (wchar_t const *path = ::_wgetenv(L"USERPROFILE")) { auto pathLen = ::wcslen(path); ArrayRef<char> ref{reinterpret_cast<char const *>(path), @@ -398,7 +385,7 @@ TEST(Support, TempDirectory) { EXPECT_TRUE(!TempDir.empty()); } -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 static std::string path2regex(std::string Path) { size_t Pos = 0; while ((Pos = Path.find('\\', Pos)) != std::string::npos) { @@ -464,6 +451,7 @@ protected: /// Unique temporary directory in which all created filesystem entities must /// be placed. It is removed at the end of each test (must be empty). SmallString<128> TestDirectory; + SmallString<128> NonExistantFile; void SetUp() override { ASSERT_NO_ERROR( @@ -471,6 +459,11 @@ protected: // We don't care about this specific file. errs() << "Test Directory: " << TestDirectory << '\n'; errs().flush(); + NonExistantFile = TestDirectory; + + // Even though this value is hardcoded, is a 128-bit GUID, so we should be + // guaranteed that this file will never exist. + sys::path::append(NonExistantFile, "1B28B495C16344CB9822E588CD4C3EF0"); } void TearDown() override { ASSERT_NO_ERROR(fs::remove(TestDirectory.str())); } @@ -564,6 +557,25 @@ TEST_F(FileSystemTest, RealPath) { ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/test1")); } +#ifdef LLVM_ON_UNIX +TEST_F(FileSystemTest, RealPathNoReadPerm) { + SmallString<64> Expanded; + + ASSERT_NO_ERROR( + fs::create_directories(Twine(TestDirectory) + "/noreadperm")); + ASSERT_TRUE(fs::exists(Twine(TestDirectory) + "/noreadperm")); + + fs::setPermissions(Twine(TestDirectory) + "/noreadperm", fs::no_perms); + fs::setPermissions(Twine(TestDirectory) + "/noreadperm", fs::all_exe); + + ASSERT_NO_ERROR(fs::real_path(Twine(TestDirectory) + "/noreadperm", Expanded, + false)); + + ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/noreadperm")); +} +#endif + + TEST_F(FileSystemTest, TempFileKeepDiscard) { // We can keep then discard. auto TempFileOrError = fs::TempFile::create(TestDirectory + "/test-%%%%"); @@ -648,7 +660,7 @@ TEST_F(FileSystemTest, TempFiles) { ASSERT_EQ(fs::access(Twine(TempPath), sys::fs::AccessMode::Exist), errc::no_such_file_or_directory); -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 // Path name > 260 chars should get an error. const char *Path270 = "abcdefghijklmnopqrstuvwxyz9abcdefghijklmnopqrstuvwxyz8" @@ -696,7 +708,7 @@ TEST_F(FileSystemTest, CreateDir) { ::umask(OldUmask); #endif -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 // Prove that create_directories() can handle a pathname > 248 characters, // which is the documented limit for CreateDirectory(). // (248 is MAX_PATH subtracting room for an 8.3 filename.) @@ -866,58 +878,91 @@ TEST_F(FileSystemTest, BrokenSymlinkDirectoryIteration) { fs::create_link("no_such_file", Twine(TestDirectory) + "/symlink/e")); typedef std::vector<std::string> v_t; - v_t visited; - - // The directory iterator doesn't stat the file, so we should be able to - // iterate over the whole directory. + v_t VisitedNonBrokenSymlinks; + v_t VisitedBrokenSymlinks; std::error_code ec; + + // Broken symbol links are expected to throw an error. for (fs::directory_iterator i(Twine(TestDirectory) + "/symlink", ec), e; i != e; i.increment(ec)) { + if (ec == std::make_error_code(std::errc::no_such_file_or_directory)) { + VisitedBrokenSymlinks.push_back(path::filename(i->path())); + continue; + } + ASSERT_NO_ERROR(ec); - visited.push_back(path::filename(i->path())); + VisitedNonBrokenSymlinks.push_back(path::filename(i->path())); } - std::sort(visited.begin(), visited.end()); - v_t expected = {"a", "b", "c", "d", "e"}; - ASSERT_TRUE(visited.size() == expected.size()); - ASSERT_TRUE(std::equal(visited.begin(), visited.end(), expected.begin())); - visited.clear(); - - // The recursive directory iterator has to stat the file, so we need to skip - // the broken symlinks. - for (fs::recursive_directory_iterator - i(Twine(TestDirectory) + "/symlink", ec), - e; - i != e; i.increment(ec)) { - ASSERT_NO_ERROR(ec); - - ErrorOr<fs::basic_file_status> status = i->status(); - if (status.getError() == - std::make_error_code(std::errc::no_such_file_or_directory)) { - i.no_push(); + llvm::sort(VisitedNonBrokenSymlinks.begin(), VisitedNonBrokenSymlinks.end()); + llvm::sort(VisitedBrokenSymlinks.begin(), VisitedBrokenSymlinks.end()); + v_t ExpectedNonBrokenSymlinks = {"b", "d"}; + ASSERT_EQ(ExpectedNonBrokenSymlinks.size(), VisitedNonBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedNonBrokenSymlinks.begin(), + VisitedNonBrokenSymlinks.end(), + ExpectedNonBrokenSymlinks.begin())); + VisitedNonBrokenSymlinks.clear(); + + v_t ExpectedBrokenSymlinks = {"a", "c", "e"}; + ASSERT_EQ(ExpectedBrokenSymlinks.size(), VisitedBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedBrokenSymlinks.begin(), + VisitedBrokenSymlinks.end(), + ExpectedBrokenSymlinks.begin())); + VisitedBrokenSymlinks.clear(); + + // Broken symbol links are expected to throw an error. + for (fs::recursive_directory_iterator i( + Twine(TestDirectory) + "/symlink", ec), e; i != e; i.increment(ec)) { + if (ec == std::make_error_code(std::errc::no_such_file_or_directory)) { + VisitedBrokenSymlinks.push_back(path::filename(i->path())); continue; } - visited.push_back(path::filename(i->path())); + ASSERT_NO_ERROR(ec); + VisitedNonBrokenSymlinks.push_back(path::filename(i->path())); } - std::sort(visited.begin(), visited.end()); - expected = {"b", "bb", "d", "da", "dd", "ddd", "ddd"}; - ASSERT_TRUE(visited.size() == expected.size()); - ASSERT_TRUE(std::equal(visited.begin(), visited.end(), expected.begin())); - visited.clear(); - - // This recursive directory iterator doesn't follow symlinks, so we don't need - // to skip them. - for (fs::recursive_directory_iterator - i(Twine(TestDirectory) + "/symlink", ec, /*follow_symlinks=*/false), - e; + llvm::sort(VisitedNonBrokenSymlinks.begin(), VisitedNonBrokenSymlinks.end()); + llvm::sort(VisitedBrokenSymlinks.begin(), VisitedBrokenSymlinks.end()); + ExpectedNonBrokenSymlinks = {"b", "bb", "d", "da", "dd", "ddd", "ddd"}; + ASSERT_EQ(ExpectedNonBrokenSymlinks.size(), VisitedNonBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedNonBrokenSymlinks.begin(), + VisitedNonBrokenSymlinks.end(), + ExpectedNonBrokenSymlinks.begin())); + VisitedNonBrokenSymlinks.clear(); + + ExpectedBrokenSymlinks = {"a", "ba", "bc", "c", "e"}; + ASSERT_EQ(ExpectedBrokenSymlinks.size(), VisitedBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedBrokenSymlinks.begin(), + VisitedBrokenSymlinks.end(), + ExpectedBrokenSymlinks.begin())); + VisitedBrokenSymlinks.clear(); + + for (fs::recursive_directory_iterator i( + Twine(TestDirectory) + "/symlink", ec, /*follow_symlinks=*/false), e; i != e; i.increment(ec)) { + if (ec == std::make_error_code(std::errc::no_such_file_or_directory)) { + VisitedBrokenSymlinks.push_back(path::filename(i->path())); + continue; + } + ASSERT_NO_ERROR(ec); - visited.push_back(path::filename(i->path())); + VisitedNonBrokenSymlinks.push_back(path::filename(i->path())); } - std::sort(visited.begin(), visited.end()); - expected = {"a", "b", "ba", "bb", "bc", "c", "d", "da", "dd", "ddd", "e"}; - ASSERT_TRUE(visited.size() == expected.size()); - ASSERT_TRUE(std::equal(visited.begin(), visited.end(), expected.begin())); + llvm::sort(VisitedNonBrokenSymlinks.begin(), VisitedNonBrokenSymlinks.end()); + llvm::sort(VisitedBrokenSymlinks.begin(), VisitedBrokenSymlinks.end()); + ExpectedNonBrokenSymlinks = {"a", "b", "ba", "bb", "bc", "c", "d", "da", "dd", + "ddd", "e"}; + ASSERT_EQ(ExpectedNonBrokenSymlinks.size(), VisitedNonBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedNonBrokenSymlinks.begin(), + VisitedNonBrokenSymlinks.end(), + ExpectedNonBrokenSymlinks.begin())); + VisitedNonBrokenSymlinks.clear(); + + ExpectedBrokenSymlinks = {}; + ASSERT_EQ(ExpectedBrokenSymlinks.size(), VisitedBrokenSymlinks.size()); + ASSERT_TRUE(std::equal(VisitedBrokenSymlinks.begin(), + VisitedBrokenSymlinks.end(), + ExpectedBrokenSymlinks.begin())); + VisitedBrokenSymlinks.clear(); ASSERT_NO_ERROR(fs::remove_directories(Twine(TestDirectory) + "/symlink")); } @@ -956,7 +1001,7 @@ TEST_F(FileSystemTest, Remove) { ASSERT_FALSE(fs::exists(BaseDir)); } -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 TEST_F(FileSystemTest, CarriageReturn) { SmallString<128> FilePathname(TestDirectory); std::error_code EC; @@ -1074,7 +1119,7 @@ TEST(Support, NormalizePath) { EXPECT_EQ(std::get<2>(T), Posix); } -#if defined(LLVM_ON_WIN32) +#if defined(_WIN32) SmallString<64> PathHome; path::home_directory(PathHome); @@ -1195,8 +1240,8 @@ TEST_F(FileSystemTest, OpenFileForRead) { // Open the file for read int FileDescriptor2; SmallString<64> ResultPath; - ASSERT_NO_ERROR( - fs::openFileForRead(Twine(TempPath), FileDescriptor2, &ResultPath)) + ASSERT_NO_ERROR(fs::openFileForRead(Twine(TempPath), FileDescriptor2, + fs::OF_None, &ResultPath)) // If we succeeded, check that the paths are the same (modulo case): if (!ResultPath.empty()) { @@ -1207,8 +1252,241 @@ TEST_F(FileSystemTest, OpenFileForRead) { ASSERT_NO_ERROR(fs::getUniqueID(Twine(ResultPath), D2)); ASSERT_EQ(D1, D2); } + ::close(FileDescriptor); + ::close(FileDescriptor2); + +#ifdef _WIN32 + // Since Windows Vista, file access time is not updated by default. + // This is instead updated manually by openFileForRead. + // https://blogs.technet.microsoft.com/filecab/2006/11/07/disabling-last-access-time-in-windows-vista-to-improve-ntfs-performance/ + // This part of the unit test is Windows specific as the updating of + // access times can be disabled on Linux using /etc/fstab. + + // Set access time to UNIX epoch. + ASSERT_NO_ERROR(sys::fs::openFileForWrite(Twine(TempPath), FileDescriptor, + fs::CD_OpenExisting)); + TimePoint<> Epoch(std::chrono::milliseconds(0)); + ASSERT_NO_ERROR(fs::setLastModificationAndAccessTime(FileDescriptor, Epoch)); + ::close(FileDescriptor); + + // Open the file and ensure access time is updated, when forced. + ASSERT_NO_ERROR(fs::openFileForRead(Twine(TempPath), FileDescriptor, + fs::OF_UpdateAtime, &ResultPath)); + + sys::fs::file_status Status; + ASSERT_NO_ERROR(sys::fs::status(FileDescriptor, Status)); + auto FileAccessTime = Status.getLastAccessedTime(); + ASSERT_NE(Epoch, FileAccessTime); ::close(FileDescriptor); + + // Ideally this test would include a case when ATime is not forced to update, + // however the expected behaviour will differ depending on the configuration + // of the Windows file system. +#endif +} + +static void createFileWithData(const Twine &Path, bool ShouldExistBefore, + fs::CreationDisposition Disp, StringRef Data) { + int FD; + ASSERT_EQ(ShouldExistBefore, fs::exists(Path)); + ASSERT_NO_ERROR(fs::openFileForWrite(Path, FD, Disp)); + FileDescriptorCloser Closer(FD); + ASSERT_TRUE(fs::exists(Path)); + + ASSERT_EQ(Data.size(), (size_t)write(FD, Data.data(), Data.size())); +} + +static void verifyFileContents(const Twine &Path, StringRef Contents) { + auto Buffer = MemoryBuffer::getFile(Path); + ASSERT_TRUE((bool)Buffer); + StringRef Data = Buffer.get()->getBuffer(); + ASSERT_EQ(Data, Contents); +} + +TEST_F(FileSystemTest, CreateNew) { + int FD; + Optional<FileDescriptorCloser> Closer; + + // Succeeds if the file does not exist. + ASSERT_FALSE(fs::exists(NonExistantFile)); + ASSERT_NO_ERROR(fs::openFileForWrite(NonExistantFile, FD, fs::CD_CreateNew)); + ASSERT_TRUE(fs::exists(NonExistantFile)); + + FileRemover Cleanup(NonExistantFile); + Closer.emplace(FD); + + // And creates a file of size 0. + sys::fs::file_status Status; + ASSERT_NO_ERROR(sys::fs::status(FD, Status)); + EXPECT_EQ(0ULL, Status.getSize()); + + // Close this first, before trying to re-open the file. + Closer.reset(); + + // But fails if the file does exist. + ASSERT_ERROR(fs::openFileForWrite(NonExistantFile, FD, fs::CD_CreateNew)); +} + +TEST_F(FileSystemTest, CreateAlways) { + int FD; + Optional<FileDescriptorCloser> Closer; + + // Succeeds if the file does not exist. + ASSERT_FALSE(fs::exists(NonExistantFile)); + ASSERT_NO_ERROR( + fs::openFileForWrite(NonExistantFile, FD, fs::CD_CreateAlways)); + + Closer.emplace(FD); + + ASSERT_TRUE(fs::exists(NonExistantFile)); + + FileRemover Cleanup(NonExistantFile); + + // And creates a file of size 0. + uint64_t FileSize; + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(0ULL, FileSize); + + // If we write some data to it re-create it with CreateAlways, it succeeds and + // truncates to 0 bytes. + ASSERT_EQ(4, write(FD, "Test", 4)); + + Closer.reset(); + + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + + ASSERT_NO_ERROR( + fs::openFileForWrite(NonExistantFile, FD, fs::CD_CreateAlways)); + Closer.emplace(FD); + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(0ULL, FileSize); +} + +TEST_F(FileSystemTest, OpenExisting) { + int FD; + + // Fails if the file does not exist. + ASSERT_FALSE(fs::exists(NonExistantFile)); + ASSERT_ERROR(fs::openFileForWrite(NonExistantFile, FD, fs::CD_OpenExisting)); + ASSERT_FALSE(fs::exists(NonExistantFile)); + + // Make a dummy file now so that we can try again when the file does exist. + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + FileRemover Cleanup(NonExistantFile); + uint64_t FileSize; + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + + // If we re-create it with different data, it overwrites rather than + // appending. + createFileWithData(NonExistantFile, true, fs::CD_OpenExisting, "Buzz"); + verifyFileContents(NonExistantFile, "Buzz"); +} + +TEST_F(FileSystemTest, OpenAlways) { + // Succeeds if the file does not exist. + createFileWithData(NonExistantFile, false, fs::CD_OpenAlways, "Fizz"); + FileRemover Cleanup(NonExistantFile); + uint64_t FileSize; + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + + // Now re-open it and write again, verifying the contents get over-written. + createFileWithData(NonExistantFile, true, fs::CD_OpenAlways, "Bu"); + verifyFileContents(NonExistantFile, "Buzz"); +} + +TEST_F(FileSystemTest, AppendSetsCorrectFileOffset) { + fs::CreationDisposition Disps[] = {fs::CD_CreateAlways, fs::CD_OpenAlways, + fs::CD_OpenExisting}; + + // Write some data and re-open it with every possible disposition (this is a + // hack that shouldn't work, but is left for compatibility. F_Append + // overrides + // the specified disposition. + for (fs::CreationDisposition Disp : Disps) { + int FD; + Optional<FileDescriptorCloser> Closer; + + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + + FileRemover Cleanup(NonExistantFile); + + uint64_t FileSize; + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + ASSERT_NO_ERROR( + fs::openFileForWrite(NonExistantFile, FD, Disp, fs::OF_Append)); + Closer.emplace(FD); + ASSERT_NO_ERROR(sys::fs::file_size(NonExistantFile, FileSize)); + ASSERT_EQ(4ULL, FileSize); + + ASSERT_EQ(4, write(FD, "Buzz", 4)); + Closer.reset(); + + verifyFileContents(NonExistantFile, "FizzBuzz"); + } +} + +static void verifyRead(int FD, StringRef Data, bool ShouldSucceed) { + std::vector<char> Buffer; + Buffer.resize(Data.size()); + int Result = ::read(FD, Buffer.data(), Buffer.size()); + if (ShouldSucceed) { + ASSERT_EQ((size_t)Result, Data.size()); + ASSERT_EQ(Data, StringRef(Buffer.data(), Buffer.size())); + } else { + ASSERT_EQ(-1, Result); + ASSERT_EQ(EBADF, errno); + } +} + +static void verifyWrite(int FD, StringRef Data, bool ShouldSucceed) { + int Result = ::write(FD, Data.data(), Data.size()); + if (ShouldSucceed) + ASSERT_EQ((size_t)Result, Data.size()); + else { + ASSERT_EQ(-1, Result); + ASSERT_EQ(EBADF, errno); + } +} + +TEST_F(FileSystemTest, ReadOnlyFileCantWrite) { + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + FileRemover Cleanup(NonExistantFile); + + int FD; + ASSERT_NO_ERROR(fs::openFileForRead(NonExistantFile, FD)); + FileDescriptorCloser Closer(FD); + + verifyWrite(FD, "Buzz", false); + verifyRead(FD, "Fizz", true); +} + +TEST_F(FileSystemTest, WriteOnlyFileCantRead) { + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + FileRemover Cleanup(NonExistantFile); + + int FD; + ASSERT_NO_ERROR( + fs::openFileForWrite(NonExistantFile, FD, fs::CD_OpenExisting)); + FileDescriptorCloser Closer(FD); + verifyRead(FD, "Fizz", false); + verifyWrite(FD, "Buzz", true); +} + +TEST_F(FileSystemTest, ReadWriteFileCanReadOrWrite) { + createFileWithData(NonExistantFile, false, fs::CD_CreateNew, "Fizz"); + FileRemover Cleanup(NonExistantFile); + + int FD; + ASSERT_NO_ERROR(fs::openFileForReadWrite(NonExistantFile, FD, + fs::CD_OpenExisting, fs::OF_None)); + FileDescriptorCloser Closer(FD); + verifyRead(FD, "Fizz", true); + verifyWrite(FD, "Buzz", true); } TEST_F(FileSystemTest, set_current_path) { @@ -1254,7 +1532,7 @@ TEST_F(FileSystemTest, permissions) { EXPECT_EQ(fs::setPermissions(TempPath, fs::all_read | fs::all_exe), NoError); EXPECT_TRUE(CheckPermissions(fs::all_read | fs::all_exe)); -#if defined(LLVM_ON_WIN32) +#if defined(_WIN32) fs::perms ReadOnly = fs::all_read | fs::all_exe; EXPECT_EQ(fs::setPermissions(TempPath, fs::no_perms), NoError); EXPECT_TRUE(CheckPermissions(ReadOnly)); diff --git a/unittests/Support/ProcessTest.cpp b/unittests/Support/ProcessTest.cpp index 37587bf47996..c6ba35fd9415 100644 --- a/unittests/Support/ProcessTest.cpp +++ b/unittests/Support/ProcessTest.cpp @@ -10,7 +10,7 @@ #include "llvm/Support/Process.h" #include "gtest/gtest.h" -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 #include <windows.h> #endif @@ -45,7 +45,7 @@ TEST(ProcessTest, None) { } #endif -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 TEST(ProcessTest, EmptyVal) { SetEnvironmentVariableA("__LLVM_TEST_ENVIRON_VAR__", ""); diff --git a/unittests/Support/ProgramTest.cpp b/unittests/Support/ProgramTest.cpp index 3c272bb980c5..ec1c85a9f25a 100644 --- a/unittests/Support/ProgramTest.cpp +++ b/unittests/Support/ProgramTest.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "llvm/Support/Program.h" +#include "llvm/Config/llvm-config.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/FileSystem.h" @@ -26,7 +27,7 @@ extern char **environ; void sleep_for(unsigned int seconds) { sleep(seconds); } -#elif defined(LLVM_ON_WIN32) +#elif defined(_WIN32) #include <windows.h> void sleep_for(unsigned int seconds) { Sleep(seconds * 1000); @@ -59,13 +60,13 @@ static cl::opt<std::string> ProgramTestStringArg2("program-test-string-arg2"); class ProgramEnvTest : public testing::Test { - std::vector<const char *> EnvTable; + std::vector<StringRef> EnvTable; std::vector<std::string> EnvStorage; protected: void SetUp() override { auto EnvP = [] { -#if defined(LLVM_ON_WIN32) +#if defined(_WIN32) _wgetenv(L"TMP"); // Populate _wenviron, initially is null return _wenviron; #elif defined(__APPLE__) @@ -76,8 +77,8 @@ protected: }(); ASSERT_TRUE(EnvP); - auto prepareEnvVar = [this](decltype(*EnvP) Var) { -#if defined(LLVM_ON_WIN32) + auto prepareEnvVar = [this](decltype(*EnvP) Var) -> StringRef { +#if defined(_WIN32) // On Windows convert UTF16 encoded variable to UTF8 auto Len = wcslen(Var); ArrayRef<char> Ref{reinterpret_cast<char const *>(Var), @@ -85,10 +86,10 @@ protected: EnvStorage.emplace_back(); auto convStatus = convertUTF16ToUTF8String(Ref, EnvStorage.back()); EXPECT_TRUE(convStatus); - return EnvStorage.back().c_str(); + return EnvStorage.back(); #else (void)this; - return Var; + return StringRef(Var); #endif }; @@ -103,19 +104,12 @@ protected: EnvStorage.clear(); } - void addEnvVar(const char *Var) { - ASSERT_TRUE(EnvTable.empty() || EnvTable.back()) << "Env table sealed"; - EnvTable.emplace_back(Var); - } + void addEnvVar(StringRef Var) { EnvTable.emplace_back(Var); } - const char **getEnviron() { - if (EnvTable.back() != nullptr) - EnvTable.emplace_back(nullptr); // Seal table. - return &EnvTable[0]; - } + ArrayRef<StringRef> getEnviron() const { return EnvTable; } }; -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 TEST_F(ProgramEnvTest, CreateProcessLongPath) { if (getenv("LLVM_PROGRAM_TEST_LONG_PATH")) exit(0); @@ -128,11 +122,8 @@ TEST_F(ProgramEnvTest, CreateProcessLongPath) { MyExe.append("\\\\?\\"); MyExe.append(MyAbsExe); - const char *ArgV[] = { - MyExe.c_str(), - "--gtest_filter=ProgramEnvTest.CreateProcessLongPath", - nullptr - }; + StringRef ArgV[] = {MyExe, + "--gtest_filter=ProgramEnvTest.CreateProcessLongPath"}; // Add LLVM_PROGRAM_TEST_LONG_PATH to the environment of the child. addEnvVar("LLVM_PROGRAM_TEST_LONG_PATH=1"); @@ -172,13 +163,13 @@ TEST_F(ProgramEnvTest, CreateProcessTrailingSlash) { std::string my_exe = sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); - const char *argv[] = { - my_exe.c_str(), - "--gtest_filter=ProgramEnvTest.CreateProcessTrailingSlash", - "-program-test-string-arg1", "has\\\\ trailing\\", - "-program-test-string-arg2", "has\\\\ trailing\\", - nullptr - }; + StringRef argv[] = { + my_exe, + "--gtest_filter=ProgramEnvTest.CreateProcessTrailingSlash", + "-program-test-string-arg1", + "has\\\\ trailing\\", + "-program-test-string-arg2", + "has\\\\ trailing\\"}; // Add LLVM_PROGRAM_TEST_CHILD to the environment of the child. addEnvVar("LLVM_PROGRAM_TEST_CHILD=1"); @@ -186,7 +177,7 @@ TEST_F(ProgramEnvTest, CreateProcessTrailingSlash) { std::string error; bool ExecutionFailed; // Redirect stdout and stdin to NUL, but let stderr through. -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 StringRef nul("NUL"); #else StringRef nul("/dev/null"); @@ -209,11 +200,8 @@ TEST_F(ProgramEnvTest, TestExecuteNoWait) { std::string Executable = sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); - const char *argv[] = { - Executable.c_str(), - "--gtest_filter=ProgramEnvTest.TestExecuteNoWait", - nullptr - }; + StringRef argv[] = {Executable, + "--gtest_filter=ProgramEnvTest.TestExecuteNoWait"}; // Add LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT to the environment of the child. addEnvVar("LLVM_PROGRAM_TEST_EXECUTE_NO_WAIT=1"); @@ -267,11 +255,8 @@ TEST_F(ProgramEnvTest, TestExecuteAndWaitTimeout) { std::string Executable = sys::fs::getMainExecutable(TestMainArgv0, &ProgramTestStringArg1); - const char *argv[] = { - Executable.c_str(), - "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitTimeout", - nullptr - }; + StringRef argv[] = { + Executable, "--gtest_filter=ProgramEnvTest.TestExecuteAndWaitTimeout"}; // Add LLVM_PROGRAM_TEST_TIMEOUT to the environment of the child. addEnvVar("LLVM_PROGRAM_TEST_TIMEOUT=1"); @@ -286,12 +271,12 @@ TEST_F(ProgramEnvTest, TestExecuteAndWaitTimeout) { TEST(ProgramTest, TestExecuteNegative) { std::string Executable = "i_dont_exist"; - const char *argv[] = { Executable.c_str(), nullptr }; + StringRef argv[] = {Executable}; { std::string Error; bool ExecutionFailed; - int RetCode = ExecuteAndWait(Executable, argv, nullptr, {}, 0, 0, &Error, + int RetCode = ExecuteAndWait(Executable, argv, llvm::None, {}, 0, 0, &Error, &ExecutionFailed); ASSERT_TRUE(RetCode < 0) << "On error ExecuteAndWait should return 0 or " "positive value indicating the result code"; @@ -302,7 +287,7 @@ TEST(ProgramTest, TestExecuteNegative) { { std::string Error; bool ExecutionFailed; - ProcessInfo PI = ExecuteNoWait(Executable, argv, nullptr, {}, 0, &Error, + ProcessInfo PI = ExecuteNoWait(Executable, argv, llvm::None, {}, 0, &Error, &ExecutionFailed); ASSERT_EQ(PI.Pid, ProcessInfo::InvalidPid) << "On error ExecuteNoWait should return an invalid ProcessInfo"; @@ -312,7 +297,7 @@ TEST(ProgramTest, TestExecuteNegative) { } -#ifdef LLVM_ON_WIN32 +#ifdef _WIN32 const char utf16le_text[] = "\x6c\x00\x69\x00\x6e\x00\x67\x00\xfc\x00\x69\x00\xe7\x00\x61\x00"; const char utf16be_text[] = @@ -332,7 +317,7 @@ TEST(ProgramTest, TestWriteWithSystemEncoding) { sys::WEM_UTF16)); int fd = 0; ASSERT_NO_ERROR(fs::openFileForRead(file_pathname.c_str(), fd)); -#if defined(LLVM_ON_WIN32) +#if defined(_WIN32) char buf[18]; ASSERT_EQ(::read(fd, buf, 18), 18); if (strncmp(buf, "\xfe\xff", 2) == 0) { // UTF16-BE diff --git a/unittests/Support/ReplaceFileTest.cpp b/unittests/Support/ReplaceFileTest.cpp index 794f36b1f654..15143be794fc 100644 --- a/unittests/Support/ReplaceFileTest.cpp +++ b/unittests/Support/ReplaceFileTest.cpp @@ -31,7 +31,7 @@ namespace { std::error_code CreateFileWithContent(const SmallString<128> &FilePath, const StringRef &content) { int FD = 0; - if (std::error_code ec = fs::openFileForWrite(FilePath, FD, fs::F_None)) + if (std::error_code ec = fs::openFileForWrite(FilePath, FD)) return ec; const bool ShouldClose = true; diff --git a/unittests/Support/SourceMgrTest.cpp b/unittests/Support/SourceMgrTest.cpp index 2a84a89912ad..7bb76f556f70 100644 --- a/unittests/Support/SourceMgrTest.cpp +++ b/unittests/Support/SourceMgrTest.cpp @@ -107,6 +107,320 @@ TEST_F(SourceMgrTest, LocationAtNewline) { Output); } +TEST_F(SourceMgrTest, LocationAtEmptyBuffer) { + setMainBuffer("", "file.in"); + printMessage(getLoc(0), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:1:1: error: message\n" + "\n" + "^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationJustOnSoleNewline) { + setMainBuffer("\n", "file.in"); + printMessage(getLoc(0), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:1:1: error: message\n" + "\n" + "^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationJustAfterSoleNewline) { + setMainBuffer("\n", "file.in"); + printMessage(getLoc(1), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:2:1: error: message\n" + "\n" + "^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationJustAfterNonNewline) { + setMainBuffer("123", "file.in"); + printMessage(getLoc(3), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:1:4: error: message\n" + "123\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationOnFirstLineOfMultiline) { + setMainBuffer("1234\n6789\n", "file.in"); + printMessage(getLoc(3), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:1:4: error: message\n" + "1234\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationOnEOLOfFirstLineOfMultiline) { + setMainBuffer("1234\n6789\n", "file.in"); + printMessage(getLoc(4), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:1:5: error: message\n" + "1234\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationOnSecondLineOfMultiline) { + setMainBuffer("1234\n6789\n", "file.in"); + printMessage(getLoc(5), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:2:1: error: message\n" + "6789\n" + "^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationOnSecondLineOfMultilineNoSecondEOL) { + setMainBuffer("1234\n6789", "file.in"); + printMessage(getLoc(5), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:2:1: error: message\n" + "6789\n" + "^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationOnEOLOfSecondSecondLineOfMultiline) { + setMainBuffer("1234\n6789\n", "file.in"); + printMessage(getLoc(9), SourceMgr::DK_Error, "message", None, None); + + EXPECT_EQ("file.in:2:5: error: message\n" + "6789\n" + " ^\n", + Output); +} + +#define STRING_LITERAL_253_BYTES \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n1234567890\n" \ + "1234567890\n" + +//===----------------------------------------------------------------------===// +// 255-byte buffer tests +//===----------------------------------------------------------------------===// + +TEST_F(SourceMgrTest, LocationBeforeEndOf255ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "12" // + 2 = 255 bytes + , "file.in"); + printMessage(getLoc(253), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:1: error: message\n" + "12\n" + "^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationAtEndOf255ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "12" // + 2 = 255 bytes + , "file.in"); + printMessage(getLoc(254), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:2: error: message\n" + "12\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationPastEndOf255ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "12" // + 2 = 255 bytes + , "file.in"); + printMessage(getLoc(255), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:3: error: message\n" + "12\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationBeforeEndOf255ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "1\n" // + 2 = 255 bytes + , "file.in"); + printMessage(getLoc(253), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:1: error: message\n" + "1\n" + "^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationAtEndOf255ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "1\n" // + 2 = 255 bytes + , "file.in"); + printMessage(getLoc(254), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:2: error: message\n" + "1\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationPastEndOf255ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "1\n" // + 2 = 255 bytes + , "file.in"); + printMessage(getLoc(255), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:25:1: error: message\n" + "\n" + "^\n", + Output); +} + +//===----------------------------------------------------------------------===// +// 256-byte buffer tests +//===----------------------------------------------------------------------===// + +TEST_F(SourceMgrTest, LocationBeforeEndOf256ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "123" // + 3 = 256 bytes + , "file.in"); + printMessage(getLoc(254), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:2: error: message\n" + "123\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationAtEndOf256ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "123" // + 3 = 256 bytes + , "file.in"); + printMessage(getLoc(255), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:3: error: message\n" + "123\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationPastEndOf256ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "123" // + 3 = 256 bytes + , "file.in"); + printMessage(getLoc(256), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:4: error: message\n" + "123\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationBeforeEndOf256ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "12\n" // + 3 = 256 bytes + , "file.in"); + printMessage(getLoc(254), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:2: error: message\n" + "12\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationAtEndOf256ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "12\n" // + 3 = 256 bytes + , "file.in"); + printMessage(getLoc(255), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:3: error: message\n" + "12\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationPastEndOf256ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "12\n" // + 3 = 256 bytes + , "file.in"); + printMessage(getLoc(256), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:25:1: error: message\n" + "\n" + "^\n", + Output); +} + +//===----------------------------------------------------------------------===// +// 257-byte buffer tests +//===----------------------------------------------------------------------===// + +TEST_F(SourceMgrTest, LocationBeforeEndOf257ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "1234" // + 4 = 257 bytes + , "file.in"); + printMessage(getLoc(255), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:3: error: message\n" + "1234\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationAtEndOf257ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "1234" // + 4 = 257 bytes + , "file.in"); + printMessage(getLoc(256), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:4: error: message\n" + "1234\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationPastEndOf257ByteBuffer) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "1234" // + 4 = 257 bytes + , "file.in"); + printMessage(getLoc(257), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:5: error: message\n" + "1234\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationBeforeEndOf257ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "123\n" // + 4 = 257 bytes + , "file.in"); + printMessage(getLoc(255), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:3: error: message\n" + "123\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationAtEndOf257ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "123\n" // + 4 = 257 bytes + , "file.in"); + printMessage(getLoc(256), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:24:4: error: message\n" + "123\n" + " ^\n", + Output); +} + +TEST_F(SourceMgrTest, LocationPastEndOf257ByteBufferEndingInNewline) { + setMainBuffer(STRING_LITERAL_253_BYTES // first 253 bytes + "123\n" // + 4 = 257 bytes + , "file.in"); + printMessage(getLoc(257), SourceMgr::DK_Error, "message", None, None); + EXPECT_EQ("file.in:25:1: error: message\n" + "\n" + "^\n", + Output); +} + TEST_F(SourceMgrTest, BasicRange) { setMainBuffer("aaa bbb\nccc ddd\n", "file.in"); printMessage(getLoc(4), SourceMgr::DK_Error, "message", getRange(4, 3), None); diff --git a/unittests/Support/TargetParserTest.cpp b/unittests/Support/TargetParserTest.cpp index 48fffca1aa18..fc26c985f265 100644 --- a/unittests/Support/TargetParserTest.cpp +++ b/unittests/Support/TargetParserTest.cpp @@ -17,17 +17,18 @@ using namespace llvm; namespace { const char *ARMArch[] = { - "armv2", "armv2a", "armv3", "armv3m", "armv4", - "armv4t", "armv5", "armv5t", "armv5e", "armv5te", - "armv5tej", "armv6", "armv6j", "armv6k", "armv6hl", - "armv6t2", "armv6kz", "armv6z", "armv6zk", "armv6-m", - "armv6m", "armv6sm", "armv6s-m", "armv7-a", "armv7", - "armv7a", "armv7ve", "armv7hl", "armv7l", "armv7-r", - "armv7r", "armv7-m", "armv7m", "armv7k", "armv7s", - "armv7e-m", "armv7em", "armv8-a", "armv8", "armv8a", - "armv8.1-a", "armv8.1a", "armv8.2-a", "armv8.2a", "armv8.3-a", - "armv8.3a", "armv8-r", "armv8r", "armv8-m.base", "armv8m.base", - "armv8-m.main", "armv8m.main", "iwmmxt", "iwmmxt2", "xscale"}; + "armv2", "armv2a", "armv3", "armv3m", "armv4", + "armv4t", "armv5", "armv5t", "armv5e", "armv5te", + "armv5tej", "armv6", "armv6j", "armv6k", "armv6hl", + "armv6t2", "armv6kz", "armv6z", "armv6zk", "armv6-m", + "armv6m", "armv6sm", "armv6s-m", "armv7-a", "armv7", + "armv7a", "armv7ve", "armv7hl", "armv7l", "armv7-r", + "armv7r", "armv7-m", "armv7m", "armv7k", "armv7s", + "armv7e-m", "armv7em", "armv8-a", "armv8", "armv8a", + "armv8l", "armv8.1-a", "armv8.1a", "armv8.2-a", "armv8.2a", + "armv8.3-a", "armv8.3a", "armv8-r", "armv8r", "armv8-m.base", + "armv8m.base", "armv8-m.main", "armv8m.main", "iwmmxt", "iwmmxt2", + "xscale"}; bool testARMCPU(StringRef CPUName, StringRef ExpectedArch, StringRef ExpectedFPU, unsigned ExpectedFlags, @@ -252,18 +253,23 @@ TEST(TargetParserTest, testARMCPU) { "8-A")); EXPECT_TRUE(testARMCPU("exynos-m1", "armv8-a", "crypto-neon-fp-armv8", ARM::AEK_CRC | ARM::AEK_SEC | ARM::AEK_MP | - ARM::AEK_VIRT | ARM::AEK_HWDIVARM | - ARM::AEK_HWDIVTHUMB | ARM::AEK_DSP, + ARM::AEK_VIRT | ARM::AEK_HWDIVARM | + ARM::AEK_HWDIVTHUMB | ARM::AEK_DSP, "8-A")); EXPECT_TRUE(testARMCPU("exynos-m2", "armv8-a", "crypto-neon-fp-armv8", ARM::AEK_CRC | ARM::AEK_SEC | ARM::AEK_MP | - ARM::AEK_VIRT | ARM::AEK_HWDIVARM | - ARM::AEK_HWDIVTHUMB | ARM::AEK_DSP, + ARM::AEK_VIRT | ARM::AEK_HWDIVARM | + ARM::AEK_HWDIVTHUMB | ARM::AEK_DSP, "8-A")); EXPECT_TRUE(testARMCPU("exynos-m3", "armv8-a", "crypto-neon-fp-armv8", ARM::AEK_CRC | ARM::AEK_SEC | ARM::AEK_MP | - ARM::AEK_VIRT | ARM::AEK_HWDIVARM | - ARM::AEK_HWDIVTHUMB | ARM::AEK_DSP, + ARM::AEK_VIRT | ARM::AEK_HWDIVARM | + ARM::AEK_HWDIVTHUMB | ARM::AEK_DSP, + "8-A")); + EXPECT_TRUE(testARMCPU("exynos-m4", "armv8-a", "crypto-neon-fp-armv8", + ARM::AEK_CRC | ARM::AEK_SEC | ARM::AEK_MP | + ARM::AEK_VIRT | ARM::AEK_HWDIVARM | + ARM::AEK_HWDIVTHUMB | ARM::AEK_DSP, "8-A")); EXPECT_TRUE(testARMCPU("cortex-m23", "armv8-m.base", "none", ARM::AEK_HWDIVTHUMB, "8-M.Baseline")); @@ -278,6 +284,20 @@ TEST(TargetParserTest, testARMCPU) { "7-S")); } +static constexpr unsigned NumARMCPUArchs = 83; + +TEST(TargetParserTest, testARMCPUArchList) { + SmallVector<StringRef, NumARMCPUArchs> List; + ARM::fillValidCPUArchList(List); + + // No list exists for these in this test suite, so ensure all are + // valid, and match the expected 'magic' count. + EXPECT_EQ(List.size(), NumARMCPUArchs); + for(StringRef CPU : List) { + EXPECT_NE(ARM::parseCPUArch(CPU), ARM::ArchKind::INVALID); + } +} + TEST(TargetParserTest, testInvalidARMArch) { auto InvalidArchStrings = {"armv", "armv99", "noarm"}; for (const char* InvalidArch : InvalidArchStrings) @@ -558,12 +578,12 @@ TEST(TargetParserTest, ARMparseHWDiv) { TEST(TargetParserTest, ARMparseArchEndianAndISA) { const char *Arch[] = { - "v2", "v2a", "v3", "v3m", "v4", "v4t", "v5", "v5t", - "v5e", "v5te", "v5tej", "v6", "v6j", "v6k", "v6hl", "v6t2", - "v6kz", "v6z", "v6zk", "v6-m", "v6m", "v6sm", "v6s-m", "v7-a", - "v7", "v7a", "v7ve", "v7hl", "v7l", "v7-r", "v7r", "v7-m", - "v7m", "v7k", "v7s", "v7e-m", "v7em", "v8-a", "v8", "v8a", - "v8.1-a", "v8.1a", "v8.2-a", "v8.2a", "v8.3-a", "v8.3a", "v8-r"}; + "v2", "v2a", "v3", "v3m", "v4", "v4t", "v5", "v5t", + "v5e", "v5te", "v5tej", "v6", "v6j", "v6k", "v6hl", "v6t2", + "v6kz", "v6z", "v6zk", "v6-m", "v6m", "v6sm", "v6s-m", "v7-a", + "v7", "v7a", "v7ve", "v7hl", "v7l", "v7-r", "v7r", "v7-m", + "v7m", "v7k", "v7s", "v7e-m", "v7em", "v8-a", "v8", "v8a", + "v8l", "v8.1-a", "v8.1a", "v8.2-a", "v8.2a", "v8.3-a", "v8.3a", "v8-r"}; for (unsigned i = 0; i < array_lengthof(Arch); i++) { std::string arm_1 = "armeb" + (std::string)(Arch[i]); @@ -713,6 +733,10 @@ TEST(TargetParserTest, testAArch64CPU) { AArch64::AEK_CRC | AArch64::AEK_CRYPTO | AArch64::AEK_FP | AArch64::AEK_SIMD, "8-A")); EXPECT_TRUE(testAArch64CPU( + "exynos-m4", "armv8-a", "crypto-neon-fp-armv8", + AArch64::AEK_CRC | AArch64::AEK_CRYPTO | AArch64::AEK_FP | + AArch64::AEK_SIMD, "8-A")); + EXPECT_TRUE(testAArch64CPU( "falkor", "armv8-a", "crypto-neon-fp-armv8", AArch64::AEK_CRC | AArch64::AEK_CRYPTO | AArch64::AEK_FP | AArch64::AEK_SIMD | AArch64::AEK_RDM, "8-A")); @@ -746,6 +770,20 @@ TEST(TargetParserTest, testAArch64CPU) { "8-A")); } +static constexpr unsigned NumAArch64CPUArchs = 20; + +TEST(TargetParserTest, testAArch64CPUArchList) { + SmallVector<StringRef, NumAArch64CPUArchs> List; + AArch64::fillValidCPUArchList(List); + + // No list exists for these in this test suite, so ensure all are + // valid, and match the expected 'magic' count. + EXPECT_EQ(List.size(), NumAArch64CPUArchs); + for(StringRef CPU : List) { + EXPECT_NE(AArch64::parseCPUArch(CPU), AArch64::ArchKind::INVALID); + } +} + bool testAArch64Arch(StringRef Arch, StringRef DefaultCPU, StringRef SubArch, unsigned ArchAttr) { AArch64::ArchKind AK = AArch64::parseArch(Arch); @@ -795,6 +833,8 @@ TEST(TargetParserTest, testAArch64Extension) { AArch64::ArchKind::INVALID, "ras")); EXPECT_FALSE(testAArch64Extension("exynos-m3", AArch64::ArchKind::INVALID, "ras")); + EXPECT_FALSE(testAArch64Extension("exynos-m4", + AArch64::ArchKind::INVALID, "ras")); EXPECT_TRUE(testAArch64Extension("falkor", AArch64::ArchKind::INVALID, "rdm")); EXPECT_FALSE(testAArch64Extension("kryo", diff --git a/unittests/Support/TaskQueueTest.cpp b/unittests/Support/TaskQueueTest.cpp new file mode 100644 index 000000000000..14ca9c057ac8 --- /dev/null +++ b/unittests/Support/TaskQueueTest.cpp @@ -0,0 +1,108 @@ +//========- unittests/Support/TaskQueue.cpp - TaskQueue.h tests ------========// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Config/llvm-config.h" + +#if LLVM_ENABLE_THREADS + +#include "llvm/Support/TaskQueue.h" + +#include "gtest/gtest.h" + +using namespace llvm; + +class TaskQueueTest : public testing::Test { +protected: + TaskQueueTest() {} +}; + +TEST_F(TaskQueueTest, OrderedFutures) { + ThreadPool TP(1); + TaskQueue TQ(TP); + std::atomic<int> X{ 0 }; + std::atomic<int> Y{ 0 }; + std::atomic<int> Z{ 0 }; + + std::mutex M1, M2, M3; + std::unique_lock<std::mutex> L1(M1); + std::unique_lock<std::mutex> L2(M2); + std::unique_lock<std::mutex> L3(M3); + + std::future<void> F1 = TQ.async([&] { + std::unique_lock<std::mutex> Lock(M1); + ++X; + }); + std::future<void> F2 = TQ.async([&] { + std::unique_lock<std::mutex> Lock(M2); + ++Y; + }); + std::future<void> F3 = TQ.async([&] { + std::unique_lock<std::mutex> Lock(M3); + ++Z; + }); + + L1.unlock(); + F1.wait(); + ASSERT_EQ(1, X); + ASSERT_EQ(0, Y); + ASSERT_EQ(0, Z); + + L2.unlock(); + F2.wait(); + ASSERT_EQ(1, X); + ASSERT_EQ(1, Y); + ASSERT_EQ(0, Z); + + L3.unlock(); + F3.wait(); + ASSERT_EQ(1, X); + ASSERT_EQ(1, Y); + ASSERT_EQ(1, Z); +} + +TEST_F(TaskQueueTest, UnOrderedFutures) { + ThreadPool TP(1); + TaskQueue TQ(TP); + std::atomic<int> X{ 0 }; + std::atomic<int> Y{ 0 }; + std::atomic<int> Z{ 0 }; + std::mutex M; + + std::unique_lock<std::mutex> Lock(M); + + std::future<void> F1 = TQ.async([&] { ++X; }); + std::future<void> F2 = TQ.async([&] { ++Y; }); + std::future<void> F3 = TQ.async([&M, &Z] { + std::unique_lock<std::mutex> Lock(M); + ++Z; + }); + + F2.wait(); + ASSERT_EQ(1, X); + ASSERT_EQ(1, Y); + ASSERT_EQ(0, Z); + + Lock.unlock(); + + F3.wait(); + ASSERT_EQ(1, X); + ASSERT_EQ(1, Y); + ASSERT_EQ(1, Z); +} + +TEST_F(TaskQueueTest, FutureWithReturnValue) { + ThreadPool TP(1); + TaskQueue TQ(TP); + std::future<std::string> F1 = TQ.async([&] { return std::string("Hello"); }); + std::future<int> F2 = TQ.async([&] { return 42; }); + + ASSERT_EQ(42, F2.get()); + ASSERT_EQ("Hello", F1.get()); +} +#endif diff --git a/unittests/Support/TimerTest.cpp b/unittests/Support/TimerTest.cpp index 082a7e35db52..a92ecf1ac510 100644 --- a/unittests/Support/TimerTest.cpp +++ b/unittests/Support/TimerTest.cpp @@ -10,7 +10,7 @@ #include "llvm/Support/Timer.h" #include "gtest/gtest.h" -#if LLVM_ON_WIN32 +#if _WIN32 #include <windows.h> #else #include <time.h> @@ -22,7 +22,7 @@ namespace { // FIXME: Put this somewhere in Support, it's also used in LockFileManager. void SleepMS() { -#if LLVM_ON_WIN32 +#if _WIN32 Sleep(1); #else struct timespec Interval; diff --git a/unittests/Support/TypeTraitsTest.cpp b/unittests/Support/TypeTraitsTest.cpp new file mode 100644 index 000000000000..2f7340dd92f0 --- /dev/null +++ b/unittests/Support/TypeTraitsTest.cpp @@ -0,0 +1,77 @@ +//===- TypeTraitsTest.cpp -------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/type_traits.h" + +namespace { + +// Compile-time tests using static assert. +namespace triviality { + +// Helper for compile time checking trivially copy constructible and trivially +// move constructible type traits. +template <typename T, bool IsTriviallyCopyConstructible, + bool IsTriviallyMoveConstructible> +void TrivialityTester() { + static_assert(llvm::is_trivially_copy_constructible<T>::value == + IsTriviallyCopyConstructible, + "Mismatch in expected trivial copy construction!"); + static_assert(llvm::is_trivially_move_constructible<T>::value == + IsTriviallyMoveConstructible, + "Mismatch in expected trivial move construction!"); + +#if defined(_LIBCPP_VERSION) || defined(_MSC_VER) + // On compilers with support for the standard traits, make sure they agree. + static_assert(std::is_trivially_copy_constructible<T>::value == + IsTriviallyCopyConstructible, + "Mismatch in expected trivial copy construction!"); + static_assert(std::is_trivially_move_constructible<T>::value == + IsTriviallyMoveConstructible, + "Mismatch in expected trivial move construction!"); +#endif +}; + +template void TrivialityTester<int, true, true>(); +template void TrivialityTester<void *, true, true>(); +template void TrivialityTester<int &, true, true>(); +template void TrivialityTester<int &&, false, true>(); + +struct X {}; +struct Y { + Y(const Y &); +}; +struct Z { + Z(const Z &); + Z(Z &&); +}; +struct A { + A(const A &) = default; + A(A &&); +}; +struct B { + B(const B &); + B(B &&) = default; +}; + +template void TrivialityTester<X, true, true>(); +template void TrivialityTester<Y, false, false>(); +template void TrivialityTester<Z, false, false>(); +template void TrivialityTester<A, true, false>(); +template void TrivialityTester<B, false, true>(); + +template void TrivialityTester<Z &, true, true>(); +template void TrivialityTester<A &, true, true>(); +template void TrivialityTester<B &, true, true>(); +template void TrivialityTester<Z &&, false, true>(); +template void TrivialityTester<A &&, false, true>(); +template void TrivialityTester<B &&, false, true>(); + +} // namespace triviality + +} // end anonymous namespace diff --git a/unittests/Support/VersionTupleTest.cpp b/unittests/Support/VersionTupleTest.cpp new file mode 100644 index 000000000000..cd7ecda5fefd --- /dev/null +++ b/unittests/Support/VersionTupleTest.cpp @@ -0,0 +1,50 @@ +//===- VersionTupleTests.cpp - Version Number Handling Tests --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Support/VersionTuple.h" +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(VersionTuple, getAsString) { + EXPECT_EQ("0", VersionTuple().getAsString()); + EXPECT_EQ("1", VersionTuple(1).getAsString()); + EXPECT_EQ("1.2", VersionTuple(1, 2).getAsString()); + EXPECT_EQ("1.2.3", VersionTuple(1, 2, 3).getAsString()); + EXPECT_EQ("1.2.3.4", VersionTuple(1, 2, 3, 4).getAsString()); +} + +TEST(VersionTuple, tryParse) { + VersionTuple VT; + + EXPECT_FALSE(VT.tryParse("1")); + EXPECT_EQ("1", VT.getAsString()); + + EXPECT_FALSE(VT.tryParse("1.2")); + EXPECT_EQ("1.2", VT.getAsString()); + + EXPECT_FALSE(VT.tryParse("1.2.3")); + EXPECT_EQ("1.2.3", VT.getAsString()); + + EXPECT_FALSE(VT.tryParse("1.2.3.4")); + EXPECT_EQ("1.2.3.4", VT.getAsString()); + + EXPECT_TRUE(VT.tryParse("")); + EXPECT_TRUE(VT.tryParse("1.")); + EXPECT_TRUE(VT.tryParse("1.2.")); + EXPECT_TRUE(VT.tryParse("1.2.3.")); + EXPECT_TRUE(VT.tryParse("1.2.3.4.")); + EXPECT_TRUE(VT.tryParse("1.2.3.4.5")); + EXPECT_TRUE(VT.tryParse("1-2")); + EXPECT_TRUE(VT.tryParse("1+2")); + EXPECT_TRUE(VT.tryParse(".1")); + EXPECT_TRUE(VT.tryParse(" 1")); + EXPECT_TRUE(VT.tryParse("1 ")); + EXPECT_TRUE(VT.tryParse(".")); +} diff --git a/unittests/Support/YAMLIOTest.cpp b/unittests/Support/YAMLIOTest.cpp index 914b22f0fcdf..133648065240 100644 --- a/unittests/Support/YAMLIOTest.cpp +++ b/unittests/Support/YAMLIOTest.cpp @@ -13,6 +13,7 @@ #include "llvm/Support/Endian.h" #include "llvm/Support/Format.h" #include "llvm/Support/YAMLTraits.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" using llvm::yaml::Input; @@ -25,6 +26,7 @@ using llvm::yaml::Hex8; using llvm::yaml::Hex16; using llvm::yaml::Hex32; using llvm::yaml::Hex64; +using ::testing::StartsWith; @@ -249,6 +251,72 @@ TEST(YAMLIO, TestGivenFilename) { EXPECT_TRUE(!!yin.error()); } +struct WithStringField { + std::string str1; + std::string str2; + std::string str3; +}; + +namespace llvm { +namespace yaml { +template <> struct MappingTraits<WithStringField> { + static void mapping(IO &io, WithStringField &fb) { + io.mapRequired("str1", fb.str1); + io.mapRequired("str2", fb.str2); + io.mapRequired("str3", fb.str3); + } +}; +} // namespace yaml +} // namespace llvm + +TEST(YAMLIO, MultilineStrings) { + WithStringField Original; + Original.str1 = "a multiline string\nfoobarbaz"; + Original.str2 = "another one\rfoobarbaz"; + Original.str3 = "a one-line string"; + + std::string Serialized; + { + llvm::raw_string_ostream OS(Serialized); + Output YOut(OS); + YOut << Original; + } + auto Expected = "---\n" + "str1: 'a multiline string\n" + "foobarbaz'\n" + "str2: 'another one\r" + "foobarbaz'\n" + "str3: a one-line string\n" + "...\n"; + ASSERT_EQ(Serialized, Expected); + + // Also check it parses back without the errors. + WithStringField Deserialized; + { + Input YIn(Serialized); + YIn >> Deserialized; + ASSERT_FALSE(YIn.error()) + << "Parsing error occurred during deserialization. Serialized string:\n" + << Serialized; + } + EXPECT_EQ(Original.str1, Deserialized.str1); + EXPECT_EQ(Original.str2, Deserialized.str2); + EXPECT_EQ(Original.str3, Deserialized.str3); +} + +TEST(YAMLIO, NoQuotesForTab) { + WithStringField WithTab; + WithTab.str1 = "aba\tcaba"; + std::string Serialized; + { + llvm::raw_string_ostream OS(Serialized); + Output YOut(OS); + YOut << WithTab; + } + auto ExpectedPrefix = "---\n" + "str1: aba\tcaba\n"; + EXPECT_THAT(Serialized, StartsWith(ExpectedPrefix)); +} //===----------------------------------------------------------------------===// // Test built-in types @@ -533,6 +601,7 @@ struct StringTypes { std::string stdstr9; std::string stdstr10; std::string stdstr11; + std::string stdstr12; }; namespace llvm { @@ -562,6 +631,7 @@ namespace yaml { io.mapRequired("stdstr9", st.stdstr9); io.mapRequired("stdstr10", st.stdstr10); io.mapRequired("stdstr11", st.stdstr11); + io.mapRequired("stdstr12", st.stdstr12); } }; } @@ -593,6 +663,7 @@ TEST(YAMLIO, TestReadWriteStringTypes) { map.stdstr9 = "~"; map.stdstr10 = "0.2e20"; map.stdstr11 = "0x30"; + map.stdstr12 = "- match"; llvm::raw_string_ostream ostr(intermediate); Output yout(ostr); @@ -611,6 +682,7 @@ TEST(YAMLIO, TestReadWriteStringTypes) { EXPECT_NE(llvm::StringRef::npos, flowOut.find("'~'\n")); EXPECT_NE(llvm::StringRef::npos, flowOut.find("'0.2e20'\n")); EXPECT_NE(llvm::StringRef::npos, flowOut.find("'0x30'\n")); + EXPECT_NE(llvm::StringRef::npos, flowOut.find("'- match'\n")); EXPECT_NE(std::string::npos, flowOut.find("'''eee")); EXPECT_NE(std::string::npos, flowOut.find("'\"fff'")); EXPECT_NE(std::string::npos, flowOut.find("'`ggg'")); @@ -2460,7 +2532,10 @@ static void TestEscaped(llvm::StringRef Input, llvm::StringRef Expected) { yamlize(xout, Input, true, Ctx); ostr.flush(); - EXPECT_EQ(Expected, out); + + // Make a separate StringRef so we get nice byte-by-byte output. + llvm::StringRef Got(out); + EXPECT_EQ(Expected, Got); } TEST(YAMLIO, TestEscaped) { @@ -2481,4 +2556,16 @@ TEST(YAMLIO, TestEscaped) { // UTF8 with single quote inside double quote TestEscaped("parameter 'параметр' is unused", "\"parameter 'параметр' is unused\""); + + // String with embedded non-printable multibyte UTF-8 sequence (U+200B + // zero-width space). The thing to test here is that we emit a + // unicode-scalar level escape like \uNNNN (at the YAML level), and don't + // just pass the UTF-8 byte sequence through as with quoted printables. + { + const unsigned char foobar[10] = {'f', 'o', 'o', + 0xE2, 0x80, 0x8B, // UTF-8 of U+200B + 'b', 'a', 'r', + 0x0}; + TestEscaped((char const *)foobar, "\"foo\\u200Bbar\""); + } } diff --git a/unittests/Support/raw_pwrite_stream_test.cpp b/unittests/Support/raw_pwrite_stream_test.cpp index 249780a8c829..a528fd25b932 100644 --- a/unittests/Support/raw_pwrite_stream_test.cpp +++ b/unittests/Support/raw_pwrite_stream_test.cpp @@ -8,6 +8,7 @@ //===----------------------------------------------------------------------===// #include "llvm/ADT/SmallString.h" +#include "llvm/Config/llvm-config.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/FileUtilities.h" #include "llvm/Support/raw_ostream.h" @@ -83,7 +84,7 @@ TEST(raw_pwrite_ostreamTest, TestFD) { #ifdef LLVM_ON_UNIX TEST(raw_pwrite_ostreamTest, TestDevNull) { int FD; - sys::fs::openFileForWrite("/dev/null", FD, sys::fs::F_None); + sys::fs::openFileForWrite("/dev/null", FD, sys::fs::CD_OpenExisting); raw_fd_ostream OS(FD, true); OS << "abcd"; StringRef Test = "test"; diff --git a/unittests/Target/WebAssembly/CMakeLists.txt b/unittests/Target/WebAssembly/CMakeLists.txt new file mode 100644 index 000000000000..5ec82240b038 --- /dev/null +++ b/unittests/Target/WebAssembly/CMakeLists.txt @@ -0,0 +1,18 @@ +include_directories( + ${CMAKE_SOURCE_DIR}/lib/Target/WebAssembly + ${CMAKE_BINARY_DIR}/lib/Target/WebAssembly + ) + +set(LLVM_LINK_COMPONENTS + CodeGen + Core + MC + MIRParser + WebAssemblyCodeGen + WebAssemblyDesc + WebAssemblyInfo + ) + +add_llvm_unittest(WebAssemblyTests + WebAssemblyExceptionInfoTest.cpp + ) diff --git a/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp b/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp new file mode 100644 index 000000000000..599f2e7f10fc --- /dev/null +++ b/unittests/Target/WebAssembly/WebAssemblyExceptionInfoTest.cpp @@ -0,0 +1,549 @@ +//=== WebAssemblyExceptionInfoTest.cpp - WebAssebmlyExceptionInfo unit tests =// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "WebAssemblyExceptionInfo.h" +#include "llvm/CodeGen/MIRParser/MIRParser.h" +#include "llvm/CodeGen/MachineDominanceFrontier.h" +#include "llvm/CodeGen/MachineDominators.h" +#include "llvm/CodeGen/MachineModuleInfo.h" +#include "llvm/Support/SourceMgr.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "llvm/Target/TargetMachine.h" +#include "gtest/gtest.h" + +using namespace llvm; + +namespace { + +std::unique_ptr<TargetMachine> createTargetMachine() { + auto TT(Triple::normalize("wasm32-unknown-unknown")); + std::string CPU(""); + std::string FS(""); + + LLVMInitializeWebAssemblyTargetInfo(); + LLVMInitializeWebAssemblyTarget(); + LLVMInitializeWebAssemblyTargetMC(); + + std::string Error; + const Target *TheTarget = TargetRegistry::lookupTarget(TT, Error); + assert(TheTarget); + + return std::unique_ptr<TargetMachine>(TheTarget->createTargetMachine( + TT, CPU, FS, TargetOptions(), None, None, CodeGenOpt::Default)); +} + +std::unique_ptr<Module> parseMIR(LLVMContext &Context, + std::unique_ptr<MIRParser> &MIR, + const TargetMachine &TM, StringRef MIRCode, + const char *FuncName, MachineModuleInfo &MMI) { + SMDiagnostic Diagnostic; + std::unique_ptr<MemoryBuffer> MBuffer = MemoryBuffer::getMemBuffer(MIRCode); + MIR = createMIRParser(std::move(MBuffer), Context); + if (!MIR) + return nullptr; + + std::unique_ptr<Module> M = MIR->parseIRModule(); + if (!M) + return nullptr; + + M->setDataLayout(TM.createDataLayout()); + + if (MIR->parseMachineFunctions(*M, MMI)) + return nullptr; + + return M; +} + +} // namespace + +TEST(WebAssemblyExceptionInfoTest, TEST0) { + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + ASSERT_TRUE(TM); + + StringRef MIRString = R"MIR( +--- | + target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" + target triple = "wasm32-unknown-unknown" + + declare i32 @__gxx_wasm_personality_v0(...) + + define hidden void @test0() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { + unreachable + } + +... +--- +name: test0 +liveins: + - { reg: '$arguments' } + - { reg: '$value_stack' } +body: | + bb.0: + successors: %bb.1, %bb.2 + liveins: $arguments, $value_stack + BR %bb.1, implicit-def dead $arguments + + bb.1: + ; predecessors: %bb.0 + successors: %bb.7 + liveins: $value_stack + BR %bb.7, implicit-def $arguments + + bb.2 (landing-pad): + ; predecessors: %bb.0 + successors: %bb.3, %bb.9 + liveins: $value_stack + CATCH_ALL implicit-def $arguments + CLEANUPRET implicit-def dead $arguments + + bb.3 (landing-pad): + ; predecessors: %bb.2 + successors: %bb.4, %bb.6 + liveins: $value_stack + CATCH_ALL implicit-def $arguments + BR_IF %bb.4, %58:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack + BR %bb.6, implicit-def $arguments + + bb.4: + ; predecessors: %bb.3 + successors: %bb.5, %bb.8 + liveins: $value_stack + BR %bb.5, implicit-def dead $arguments + + bb.5: + ; predecessors: %bb.4 + successors: %bb.7 + liveins: $value_stack + CATCHRET %bb.7, %bb.0, implicit-def dead $arguments + + bb.6: + ; predecessors: %bb.3 + successors: %bb.10, %bb.9 + liveins: $value_stack + BR %bb.10, implicit-def dead $arguments + + bb.7: + ; predecessors: %bb.5, %bb.1 + liveins: $value_stack + RETURN_VOID implicit-def $arguments + + bb.8 (landing-pad): + ; predecessors: %bb.4 + successors: %bb.9 + liveins: $value_stack + CATCH_ALL implicit-def $arguments + CLEANUPRET implicit-def dead $arguments + + bb.9 (landing-pad): + ; predecessors: %bb.2, %bb.6, %bb.8 + liveins: $value_stack + CATCH_ALL implicit-def $arguments + CLEANUPRET implicit-def dead $arguments + + bb.10: + ; predecessors: %bb.6 + liveins: $value_stack + UNREACHABLE implicit-def $arguments +)MIR"; + + LLVMContext Context; + std::unique_ptr<MIRParser> MIR; + MachineModuleInfo MMI(TM.get()); + std::unique_ptr<Module> M = + parseMIR(Context, MIR, *TM, MIRString, "test0", MMI); + ASSERT_TRUE(M); + + Function *F = M->getFunction("test0"); + auto *MF = MMI.getMachineFunction(*F); + ASSERT_TRUE(MF); + + WebAssemblyExceptionInfo WEI; + MachineDominatorTree MDT; + MachineDominanceFrontier MDF; + MDT.runOnMachineFunction(*MF); + MDF.getBase().analyze(MDT.getBase()); + WEI.recalculate(MDT, MDF); + + // Exception info structure: + // |- bb2 (ehpad), bb3, bb4, bb5, bb6, bb8, bb9, bb10 + // |- bb3 (ehpad), bb4, bb5, bb6, bb8, bb10 + // |- bb8 (ehpad) + // |- bb9 (ehpad) + + auto *MBB2 = MF->getBlockNumbered(2); + auto *WE0 = WEI.getExceptionFor(MBB2); + ASSERT_TRUE(WE0); + EXPECT_EQ(WE0->getEHPad(), MBB2); + EXPECT_EQ(WE0->getParentException(), nullptr); + EXPECT_EQ(WE0->getExceptionDepth(), (unsigned)1); + + auto *MBB3 = MF->getBlockNumbered(3); + auto *WE0_0 = WEI.getExceptionFor(MBB3); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + EXPECT_EQ(WE0_0->getParentException(), WE0); + EXPECT_EQ(WE0_0->getExceptionDepth(), (unsigned)2); + + auto *MBB4 = MF->getBlockNumbered(4); + WE0_0 = WEI.getExceptionFor(MBB4); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + + auto *MBB5 = MF->getBlockNumbered(5); + WE0_0 = WEI.getExceptionFor(MBB5); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + + auto *MBB6 = MF->getBlockNumbered(6); + WE0_0 = WEI.getExceptionFor(MBB6); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + + auto *MBB10 = MF->getBlockNumbered(10); + WE0_0 = WEI.getExceptionFor(MBB10); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + + auto *MBB8 = MF->getBlockNumbered(8); + auto *WE0_0_0 = WEI.getExceptionFor(MBB8); + ASSERT_TRUE(WE0_0_0); + EXPECT_EQ(WE0_0_0->getEHPad(), MBB8); + EXPECT_EQ(WE0_0_0->getParentException(), WE0_0); + EXPECT_EQ(WE0_0_0->getExceptionDepth(), (unsigned)3); + + auto *MBB9 = MF->getBlockNumbered(9); + auto *WE0_1 = WEI.getExceptionFor(MBB9); + ASSERT_TRUE(WE0_1); + EXPECT_EQ(WE0_1->getEHPad(), MBB9); + EXPECT_EQ(WE0_1->getParentException(), WE0); + EXPECT_EQ(WE0_1->getExceptionDepth(), (unsigned)2); +} + +TEST(WebAssemblyExceptionInfoTest, TEST1) { + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + ASSERT_TRUE(TM); + + StringRef MIRString = R"MIR( +--- | + target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" + target triple = "wasm32-unknown-unknown" + + declare i32 @__gxx_wasm_personality_v0(...) + + define hidden void @test1() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { + unreachable + } + +... +--- +name: test1 +liveins: + - { reg: '$arguments' } + - { reg: '$value_stack' } +body: | + bb.0: + successors: %bb.9, %bb.1 + liveins: $arguments, $value_stack + BR %bb.9, implicit-def dead $arguments + + bb.1 (landing-pad): + ; predecessors: %bb.0 + successors: %bb.2, %bb.8 + liveins: $value_stack + %52:i32 = CATCH_I32 0, implicit-def dead $arguments + BR_IF %bb.2, %32:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack + BR %bb.8, implicit-def $arguments + + bb.2: + ; predecessors: %bb.1 + successors: %bb.7, %bb.3, %bb.11 + liveins: $value_stack + BR %bb.7, implicit-def dead $arguments + + bb.3 (landing-pad): + ; predecessors: %bb.2 + successors: %bb.4, %bb.6 + liveins: $value_stack + CATCH_ALL implicit-def $arguments + BR_IF %bb.4, %43:i32, implicit-def $arguments, implicit-def $value_stack, implicit $value_stack + BR %bb.6, implicit-def $arguments + + bb.4: + ; predecessors: %bb.3 + successors: %bb.5, %bb.10 + liveins: $value_stack + BR %bb.5, implicit-def dead $arguments + + bb.5: + ; predecessors: %bb.4 + successors: %bb.7(0x80000000); %bb.7(200.00%) + liveins: $value_stack + CATCHRET %bb.7, %bb.1, implicit-def dead $arguments + + bb.6: + ; predecessors: %bb.3 + successors: %bb.12, %bb.11 + liveins: $value_stack + BR %bb.12, implicit-def dead $arguments + + bb.7: + ; predecessors: %bb.2, %bb.5 + successors: %bb.9(0x80000000); %bb.9(200.00%) + liveins: $value_stack + CATCHRET %bb.9, %bb.0, implicit-def dead $arguments + + bb.8: + ; predecessors: %bb.1 + liveins: $value_stack + UNREACHABLE implicit-def $arguments + + bb.9: + ; predecessors: %bb.0, %bb.7 + liveins: $value_stack + RETURN_VOID implicit-def $arguments + + bb.10 (landing-pad): + ; predecessors: %bb.4 + successors: %bb.11 + liveins: $value_stack + CATCH_ALL implicit-def $arguments + CLEANUPRET implicit-def dead $arguments + + bb.11 (landing-pad): + ; predecessors: %bb.2, %bb.6, %bb.10 + liveins: $value_stack + CATCH_ALL implicit-def $arguments + CLEANUPRET implicit-def dead $arguments + + bb.12: + ; predecessors: %bb.6 + liveins: $value_stack + UNREACHABLE implicit-def $arguments +)MIR"; + + LLVMContext Context; + std::unique_ptr<MIRParser> MIR; + MachineModuleInfo MMI(TM.get()); + std::unique_ptr<Module> M = + parseMIR(Context, MIR, *TM, MIRString, "test1", MMI); + ASSERT_TRUE(M); + + Function *F = M->getFunction("test1"); + auto *MF = MMI.getMachineFunction(*F); + ASSERT_TRUE(MF); + + WebAssemblyExceptionInfo WEI; + MachineDominatorTree MDT; + MachineDominanceFrontier MDF; + MDT.runOnMachineFunction(*MF); + MDF.getBase().analyze(MDT.getBase()); + WEI.recalculate(MDT, MDF); + + // Exception info structure: + // |- bb1 (ehpad), bb2, bb3, bb4, bb5, bb6, bb7, bb8, bb10, bb11, bb12 + // |- bb3 (ehpad), bb4, bb5, bb6, bb10, bb12 + // |- bb10 (ehpad) + // |- bb11 (ehpad) + + auto *MBB1 = MF->getBlockNumbered(1); + auto *WE0 = WEI.getExceptionFor(MBB1); + ASSERT_TRUE(WE0); + EXPECT_EQ(WE0->getEHPad(), MBB1); + EXPECT_EQ(WE0->getParentException(), nullptr); + EXPECT_EQ(WE0->getExceptionDepth(), (unsigned)1); + + auto *MBB2 = MF->getBlockNumbered(2); + WE0 = WEI.getExceptionFor(MBB2); + ASSERT_TRUE(WE0); + EXPECT_EQ(WE0->getEHPad(), MBB1); + + auto *MBB7 = MF->getBlockNumbered(7); + WE0 = WEI.getExceptionFor(MBB7); + ASSERT_TRUE(WE0); + EXPECT_EQ(WE0->getEHPad(), MBB1); + + auto *MBB8 = MF->getBlockNumbered(8); + WE0 = WEI.getExceptionFor(MBB8); + ASSERT_TRUE(WE0); + EXPECT_EQ(WE0->getEHPad(), MBB1); + + auto *MBB3 = MF->getBlockNumbered(3); + auto *WE0_0 = WEI.getExceptionFor(MBB3); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + EXPECT_EQ(WE0_0->getParentException(), WE0); + EXPECT_EQ(WE0_0->getExceptionDepth(), (unsigned)2); + + auto *MBB4 = MF->getBlockNumbered(4); + WE0_0 = WEI.getExceptionFor(MBB4); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + + auto *MBB5 = MF->getBlockNumbered(5); + WE0_0 = WEI.getExceptionFor(MBB5); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + + auto *MBB6 = MF->getBlockNumbered(6); + WE0_0 = WEI.getExceptionFor(MBB6); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + + auto *MBB12 = MF->getBlockNumbered(12); + WE0_0 = WEI.getExceptionFor(MBB12); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB3); + + auto *MBB10 = MF->getBlockNumbered(10); + auto *WE0_0_0 = WEI.getExceptionFor(MBB10); + ASSERT_TRUE(WE0_0_0); + EXPECT_EQ(WE0_0_0->getEHPad(), MBB10); + EXPECT_EQ(WE0_0_0->getParentException(), WE0_0); + EXPECT_EQ(WE0_0_0->getExceptionDepth(), (unsigned)3); + + auto *MBB11 = MF->getBlockNumbered(11); + auto *WE0_1 = WEI.getExceptionFor(MBB11); + ASSERT_TRUE(WE0_1); + EXPECT_EQ(WE0_1->getEHPad(), MBB11); + EXPECT_EQ(WE0_1->getParentException(), WE0); + EXPECT_EQ(WE0_1->getExceptionDepth(), (unsigned)2); +} + +// Terminate pad test +TEST(WebAssemblyExceptionInfoTest, TEST2) { + std::unique_ptr<TargetMachine> TM = createTargetMachine(); + ASSERT_TRUE(TM); + + StringRef MIRString = R"MIR( +--- | + target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" + target triple = "wasm32-unknown-unknown" + + declare i32 @__gxx_wasm_personality_v0(...) + declare void @_ZSt9terminatev() + declare void @__clang_call_terminate(i8*) + + define hidden void @test2() personality i8* bitcast (i32 (...)* @__gxx_wasm_personality_v0 to i8*) { + unreachable + } + +... +--- +name: test2 +liveins: + - { reg: '$arguments' } + - { reg: '$value_stack' } +body: | + bb.0: + successors: %bb.3, %bb.1 + BR %bb.3, implicit-def dead $arguments + + bb.1 (landing-pad): + ; predecessors: %bb.0 + successors: %bb.2, %bb.4 + %3:i32 = CATCH_I32 0, implicit-def dead $arguments + BR %bb.2, implicit-def dead $arguments + + bb.2: + ; predecessors: %bb.1 + successors: %bb.3(0x80000000); %bb.3(200.00%) + CATCHRET %bb.3, %bb.0, implicit-def dead $arguments + + bb.3: + ; predecessors: %bb.0, %bb.2 + RETURN_VOID implicit-def $arguments + + bb.4 (landing-pad): + ; predecessors: %bb.1 + successors: %bb.5, %bb.6 + CATCH_ALL implicit-def $arguments + BR %bb.5, implicit-def dead $arguments + + bb.5: + ; predecessors: %bb.4 + CLEANUPRET implicit-def dead $arguments + + bb.6 (landing-pad): + ; predecessors: %bb.4 + successors: %bb.7(0x80000000); %bb.7(200.00%) + %6:i32 = CATCH_I32 0, implicit-def dead $arguments + CALL_VOID @__clang_call_terminate, %7:i32, implicit-def $arguments + UNREACHABLE implicit-def $arguments + + bb.7 (landing-pad): + ; predecessors: %bb.6 + CATCH_ALL implicit-def $arguments + CALL_VOID @_ZSt9terminatev, implicit-def $arguments + UNREACHABLE implicit-def $arguments +)MIR"; + + LLVMContext Context; + std::unique_ptr<MIRParser> MIR; + MachineModuleInfo MMI(TM.get()); + std::unique_ptr<Module> M = + parseMIR(Context, MIR, *TM, MIRString, "test2", MMI); + ASSERT_TRUE(M); + + Function *F = M->getFunction("test2"); + auto *MF = MMI.getMachineFunction(*F); + ASSERT_TRUE(MF); + + WebAssemblyExceptionInfo WEI; + MachineDominatorTree MDT; + MachineDominanceFrontier MDF; + MDT.runOnMachineFunction(*MF); + MDF.getBase().analyze(MDT.getBase()); + WEI.recalculate(MDT, MDF); + + // Exception info structure: + // |- bb1 (ehpad), bb2, bb4, bb5, bb6, bb7 + // |- bb4 (ehpad), bb5, bb6, bb7 + // |- bb6 (ehpad), bb7 + // + // Here, bb6 is a terminate pad with a 'catch' instruction, and bb7 is a + // terminate pad with a 'catch_all' instruction, In this case we put bb6 and + // bb7 into one exception. + + auto *MBB1 = MF->getBlockNumbered(1); + auto *WE0 = WEI.getExceptionFor(MBB1); + ASSERT_TRUE(WE0); + EXPECT_EQ(WE0->getEHPad(), MBB1); + EXPECT_EQ(WE0->getParentException(), nullptr); + EXPECT_EQ(WE0->getExceptionDepth(), (unsigned)1); + + auto *MBB2 = MF->getBlockNumbered(2); + WE0 = WEI.getExceptionFor(MBB2); + ASSERT_TRUE(WE0); + EXPECT_EQ(WE0->getEHPad(), MBB1); + + auto *MBB4 = MF->getBlockNumbered(4); + auto *WE0_0 = WEI.getExceptionFor(MBB4); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB4); + EXPECT_EQ(WE0_0->getParentException(), WE0); + EXPECT_EQ(WE0_0->getExceptionDepth(), (unsigned)2); + + auto *MBB5 = MF->getBlockNumbered(5); + WE0_0 = WEI.getExceptionFor(MBB5); + ASSERT_TRUE(WE0_0); + EXPECT_EQ(WE0_0->getEHPad(), MBB4); + + auto *MBB6 = MF->getBlockNumbered(6); + auto *WE0_0_0 = WEI.getExceptionFor(MBB6); + ASSERT_TRUE(WE0_0_0); + EXPECT_EQ(WE0_0_0->getEHPad(), MBB6); + EXPECT_EQ(WE0_0_0->getParentException(), WE0_0); + EXPECT_EQ(WE0_0_0->getExceptionDepth(), (unsigned)3); + + auto *MBB7 = MF->getBlockNumbered(7); + WE0_0_0 = WEI.getExceptionFor(MBB7); + ASSERT_TRUE(WE0_0_0); + EXPECT_EQ(WE0_0_0->getEHPad(), MBB6); +} diff --git a/unittests/Transforms/CMakeLists.txt b/unittests/Transforms/CMakeLists.txt index e2570a3b6537..b7f1817849ab 100644 --- a/unittests/Transforms/CMakeLists.txt +++ b/unittests/Transforms/CMakeLists.txt @@ -1,3 +1,4 @@ add_subdirectory(IPO) add_subdirectory(Scalar) add_subdirectory(Utils) +add_subdirectory(Vectorize) diff --git a/unittests/Transforms/Scalar/LoopPassManagerTest.cpp b/unittests/Transforms/Scalar/LoopPassManagerTest.cpp index 2b8130b9e009..57fb57ec66c1 100644 --- a/unittests/Transforms/Scalar/LoopPassManagerTest.cpp +++ b/unittests/Transforms/Scalar/LoopPassManagerTest.cpp @@ -962,7 +962,7 @@ TEST_F(LoopPassManagerTest, LoopChildInsertion) { AR.DT.addNewBlock(NewLoop010PHBB, &Loop01BB); AR.DT.addNewBlock(NewLoop010BB, NewLoop010PHBB); AR.DT.addNewBlock(NewLoop01LatchBB, NewLoop010BB); - AR.DT.verifyDomTree(); + EXPECT_TRUE(AR.DT.verify()); L.addBasicBlockToLoop(NewLoop010PHBB, AR.LI); NewLoop->addBasicBlockToLoop(NewLoop010BB, AR.LI); L.addBasicBlockToLoop(NewLoop01LatchBB, AR.LI); @@ -1004,7 +1004,7 @@ TEST_F(LoopPassManagerTest, LoopChildInsertion) { AR.DT.addNewBlock(NewLoop011PHBB, NewLoop010BB); auto *NewDTNode = AR.DT.addNewBlock(NewLoop011BB, NewLoop011PHBB); AR.DT.changeImmediateDominator(AR.DT[NewLoop01LatchBB], NewDTNode); - AR.DT.verifyDomTree(); + EXPECT_TRUE(AR.DT.verify()); L.addBasicBlockToLoop(NewLoop011PHBB, AR.LI); NewLoop->addBasicBlockToLoop(NewLoop011BB, AR.LI); NewLoop->verifyLoop(); @@ -1149,7 +1149,7 @@ TEST_F(LoopPassManagerTest, LoopPeerInsertion) { AR.DT.addNewBlock(NewLoop01PHBB, &Loop00BB); auto *NewDTNode = AR.DT.addNewBlock(NewLoop01BB, NewLoop01PHBB); AR.DT.changeImmediateDominator(AR.DT[&Loop02PHBB], NewDTNode); - AR.DT.verifyDomTree(); + EXPECT_TRUE(AR.DT.verify()); L.getParentLoop()->addBasicBlockToLoop(NewLoop01PHBB, AR.LI); NewLoop->addBasicBlockToLoop(NewLoop01BB, AR.LI); L.getParentLoop()->verifyLoop(); @@ -1216,7 +1216,7 @@ TEST_F(LoopPassManagerTest, LoopPeerInsertion) { AR.DT.addNewBlock(NewLoop040PHBB, NewLoop04BB); AR.DT.addNewBlock(NewLoop040BB, NewLoop040PHBB); AR.DT.addNewBlock(NewLoop04LatchBB, NewLoop040BB); - AR.DT.verifyDomTree(); + EXPECT_TRUE(AR.DT.verify()); L.getParentLoop()->addBasicBlockToLoop(NewLoop03PHBB, AR.LI); NewLoops[0]->addBasicBlockToLoop(NewLoop03BB, AR.LI); L.getParentLoop()->addBasicBlockToLoop(NewLoop04PHBB, AR.LI); @@ -1271,7 +1271,7 @@ TEST_F(LoopPassManagerTest, LoopPeerInsertion) { AR.DT.addNewBlock(NewLoop1PHBB, &Loop0BB); auto *NewDTNode = AR.DT.addNewBlock(NewLoop1BB, NewLoop1PHBB); AR.DT.changeImmediateDominator(AR.DT[&Loop2PHBB], NewDTNode); - AR.DT.verifyDomTree(); + EXPECT_TRUE(AR.DT.verify()); NewLoop->addBasicBlockToLoop(NewLoop1BB, AR.LI); NewLoop->verifyLoop(); Updater.addSiblingLoops({NewLoop}); @@ -1508,7 +1508,7 @@ TEST_F(LoopPassManagerTest, LoopDeletion) { AR.DT.addNewBlock(NewLoop03BB, NewLoop03PHBB); AR.DT.changeImmediateDominator(AR.DT[&Loop0LatchBB], AR.DT[NewLoop03BB]); - AR.DT.verifyDomTree(); + EXPECT_TRUE(AR.DT.verify()); ParentL->addBasicBlockToLoop(NewLoop03PHBB, AR.LI); NewSibling->addBasicBlockToLoop(NewLoop03BB, AR.LI); NewSibling->verifyLoop(); diff --git a/unittests/Transforms/Utils/BasicBlockUtils.cpp b/unittests/Transforms/Utils/BasicBlockUtils.cpp new file mode 100644 index 000000000000..2d0a9302011f --- /dev/null +++ b/unittests/Transforms/Utils/BasicBlockUtils.cpp @@ -0,0 +1,52 @@ +//===- BasicBlockUtils.cpp - Unit tests for BasicBlockUtils ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Utils/BasicBlockUtils.h" +#include "llvm/AsmParser/Parser.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/Support/SourceMgr.h" +#include "gtest/gtest.h" + +using namespace llvm; + +static std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) { + SMDiagnostic Err; + std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C); + if (!Mod) + Err.print("BasicBlockUtilsTests", errs()); + return Mod; +} + +TEST(BasicBlockUtils, SplitBlockPredecessors) { + LLVMContext C; + + std::unique_ptr<Module> M = parseIR( + C, + "define i32 @basic_func(i1 %cond) {\n" + "entry:\n" + " br i1 %cond, label %bb0, label %bb1\n" + "bb0:\n" + " br label %bb1\n" + "bb1:\n" + " %phi = phi i32 [ 0, %entry ], [ 1, %bb0 ]" + " ret i32 %phi\n" + "}\n" + "\n" + ); + + auto *F = M->getFunction("basic_func"); + DominatorTree DT(*F); + + // Make sure the dominator tree is properly updated if calling this on the + // entry block. + SplitBlockPredecessors(&F->getEntryBlock(), {}, "split.entry", &DT); + EXPECT_TRUE(DT.verify()); +} diff --git a/unittests/Transforms/Utils/CMakeLists.txt b/unittests/Transforms/Utils/CMakeLists.txt index e2bb0af0f773..9781b0aaedb0 100644 --- a/unittests/Transforms/Utils/CMakeLists.txt +++ b/unittests/Transforms/Utils/CMakeLists.txt @@ -8,11 +8,13 @@ set(LLVM_LINK_COMPONENTS add_llvm_unittest(UtilsTests ASanStackFrameLayoutTest.cpp + BasicBlockUtils.cpp Cloning.cpp CodeExtractor.cpp FunctionComparator.cpp IntegerDivision.cpp Local.cpp OrderedInstructions.cpp + SSAUpdaterBulk.cpp ValueMapperTest.cpp ) diff --git a/unittests/Transforms/Utils/Cloning.cpp b/unittests/Transforms/Utils/Cloning.cpp index fe4e2ba7e943..9a1ad19ebaa4 100644 --- a/unittests/Transforms/Utils/Cloning.cpp +++ b/unittests/Transforms/Utils/Cloning.cpp @@ -251,6 +251,104 @@ TEST_F(CloneInstruction, DuplicateInstructionsToSplit) { delete F; } +TEST_F(CloneInstruction, DuplicateInstructionsToSplitBlocksEq1) { + Type *ArgTy1[] = {Type::getInt32PtrTy(context)}; + FunctionType *FT = FunctionType::get(Type::getVoidTy(context), ArgTy1, false); + V = new Argument(Type::getInt32Ty(context)); + + Function *F = Function::Create(FT, Function::ExternalLinkage); + + BasicBlock *BB1 = BasicBlock::Create(context, "", F); + IRBuilder<> Builder1(BB1); + + BasicBlock *BB2 = BasicBlock::Create(context, "", F); + IRBuilder<> Builder2(BB2); + + Builder1.CreateBr(BB2); + + Instruction *AddInst = cast<Instruction>(Builder2.CreateAdd(V, V)); + Instruction *MulInst = cast<Instruction>(Builder2.CreateMul(AddInst, V)); + Instruction *SubInst = cast<Instruction>(Builder2.CreateSub(MulInst, V)); + Builder2.CreateBr(BB2); + + ValueToValueMapTy Mapping; + + auto Split = DuplicateInstructionsInSplitBetween(BB2, BB2, BB2->getTerminator(), Mapping); + + EXPECT_TRUE(Split); + EXPECT_EQ(Mapping.size(), 3u); + EXPECT_TRUE(Mapping.find(AddInst) != Mapping.end()); + EXPECT_TRUE(Mapping.find(MulInst) != Mapping.end()); + EXPECT_TRUE(Mapping.find(SubInst) != Mapping.end()); + + auto AddSplit = dyn_cast<Instruction>(Mapping[AddInst]); + EXPECT_TRUE(AddSplit); + EXPECT_EQ(AddSplit->getOperand(0), V); + EXPECT_EQ(AddSplit->getOperand(1), V); + EXPECT_EQ(AddSplit->getParent(), Split); + + auto MulSplit = dyn_cast<Instruction>(Mapping[MulInst]); + EXPECT_TRUE(MulSplit); + EXPECT_EQ(MulSplit->getOperand(0), AddSplit); + EXPECT_EQ(MulSplit->getOperand(1), V); + EXPECT_EQ(MulSplit->getParent(), Split); + + auto SubSplit = dyn_cast<Instruction>(Mapping[SubInst]); + EXPECT_EQ(MulSplit->getNextNode(), SubSplit); + EXPECT_EQ(SubSplit->getNextNode(), Split->getTerminator()); + EXPECT_EQ(Split->getSingleSuccessor(), BB2); + EXPECT_EQ(BB2->getSingleSuccessor(), Split); + + delete F; +} + +TEST_F(CloneInstruction, DuplicateInstructionsToSplitBlocksEq2) { + Type *ArgTy1[] = {Type::getInt32PtrTy(context)}; + FunctionType *FT = FunctionType::get(Type::getVoidTy(context), ArgTy1, false); + V = new Argument(Type::getInt32Ty(context)); + + Function *F = Function::Create(FT, Function::ExternalLinkage); + + BasicBlock *BB1 = BasicBlock::Create(context, "", F); + IRBuilder<> Builder1(BB1); + + BasicBlock *BB2 = BasicBlock::Create(context, "", F); + IRBuilder<> Builder2(BB2); + + Builder1.CreateBr(BB2); + + Instruction *AddInst = cast<Instruction>(Builder2.CreateAdd(V, V)); + Instruction *MulInst = cast<Instruction>(Builder2.CreateMul(AddInst, V)); + Instruction *SubInst = cast<Instruction>(Builder2.CreateSub(MulInst, V)); + Builder2.CreateBr(BB2); + + ValueToValueMapTy Mapping; + + auto Split = DuplicateInstructionsInSplitBetween(BB2, BB2, SubInst, Mapping); + + EXPECT_TRUE(Split); + EXPECT_EQ(Mapping.size(), 2u); + EXPECT_TRUE(Mapping.find(AddInst) != Mapping.end()); + EXPECT_TRUE(Mapping.find(MulInst) != Mapping.end()); + + auto AddSplit = dyn_cast<Instruction>(Mapping[AddInst]); + EXPECT_TRUE(AddSplit); + EXPECT_EQ(AddSplit->getOperand(0), V); + EXPECT_EQ(AddSplit->getOperand(1), V); + EXPECT_EQ(AddSplit->getParent(), Split); + + auto MulSplit = dyn_cast<Instruction>(Mapping[MulInst]); + EXPECT_TRUE(MulSplit); + EXPECT_EQ(MulSplit->getOperand(0), AddSplit); + EXPECT_EQ(MulSplit->getOperand(1), V); + EXPECT_EQ(MulSplit->getParent(), Split); + EXPECT_EQ(MulSplit->getNextNode(), Split->getTerminator()); + EXPECT_EQ(Split->getSingleSuccessor(), BB2); + EXPECT_EQ(BB2->getSingleSuccessor(), Split); + + delete F; +} + class CloneFunc : public ::testing::Test { protected: void SetUp() override { @@ -527,7 +625,7 @@ protected: DBuilder.finalize(); } - void CreateNewModule() { NewM = llvm::CloneModule(OldM).release(); } + void CreateNewModule() { NewM = llvm::CloneModule(*OldM).release(); } LLVMContext C; Module *OldM; diff --git a/unittests/Transforms/Utils/Local.cpp b/unittests/Transforms/Utils/Local.cpp index 4789b0558d77..5850910403f1 100644 --- a/unittests/Transforms/Utils/Local.cpp +++ b/unittests/Transforms/Utils/Local.cpp @@ -15,6 +15,7 @@ #include "llvm/IR/Instructions.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Verifier.h" #include "llvm/Support/SourceMgr.h" #include "gtest/gtest.h" @@ -100,7 +101,7 @@ TEST(Local, RemoveDuplicatePHINodes) { EXPECT_EQ(3U, BB->size()); } -std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) { +static std::unique_ptr<Module> parseIR(LLVMContext &C, const char *IR) { SMDiagnostic Err; std::unique_ptr<Module> Mod = parseAssemblyString(IR, Err, C); if (!Mod) @@ -113,37 +114,31 @@ TEST(Local, ReplaceDbgDeclare) { // Original C source to get debug info for a local variable: // void f() { int x; } - std::unique_ptr<Module> M = parseIR( - C, - "define void @f() !dbg !8 {\n" - "entry:\n" - " %x = alloca i32, align 4\n" - " call void @llvm.dbg.declare(metadata i32* %x, metadata !11, metadata " - "!DIExpression()), !dbg !13\n" - " call void @llvm.dbg.declare(metadata i32* %x, metadata !11, metadata " - "!DIExpression()), !dbg !13\n" - " ret void, !dbg !14\n" - "}\n" - "declare void @llvm.dbg.declare(metadata, metadata, metadata)\n" - "!llvm.dbg.cu = !{!0}\n" - "!llvm.module.flags = !{!3, !4}\n" - "!0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: " - "\"clang version 6.0.0 \", isOptimized: false, runtimeVersion: 0, " - "emissionKind: FullDebug, enums: !2)\n" - "!1 = !DIFile(filename: \"t2.c\", directory: \"foo\")\n" - "!2 = !{}\n" - "!3 = !{i32 2, !\"Dwarf Version\", i32 4}\n" - "!4 = !{i32 2, !\"Debug Info Version\", i32 3}\n" - "!8 = distinct !DISubprogram(name: \"f\", scope: !1, file: !1, line: 1, " - "type: !9, isLocal: false, isDefinition: true, scopeLine: 1, " - "isOptimized: false, unit: !0, variables: !2)\n" - "!9 = !DISubroutineType(types: !10)\n" - "!10 = !{null}\n" - "!11 = !DILocalVariable(name: \"x\", scope: !8, file: !1, line: 2, type: " - "!12)\n" - "!12 = !DIBasicType(name: \"int\", size: 32, encoding: DW_ATE_signed)\n" - "!13 = !DILocation(line: 2, column: 7, scope: !8)\n" - "!14 = !DILocation(line: 3, column: 1, scope: !8)\n"); + std::unique_ptr<Module> M = parseIR(C, + R"( + define void @f() !dbg !8 { + entry: + %x = alloca i32, align 4 + call void @llvm.dbg.declare(metadata i32* %x, metadata !11, metadata !DIExpression()), !dbg !13 + call void @llvm.dbg.declare(metadata i32* %x, metadata !11, metadata !DIExpression()), !dbg !13 + ret void, !dbg !14 + } + declare void @llvm.dbg.declare(metadata, metadata, metadata) + !llvm.dbg.cu = !{!0} + !llvm.module.flags = !{!3, !4} + !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 6.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) + !1 = !DIFile(filename: "t2.c", directory: "foo") + !2 = !{} + !3 = !{i32 2, !"Dwarf Version", i32 4} + !4 = !{i32 2, !"Debug Info Version", i32 3} + !8 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !9, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: false, unit: !0, retainedNodes: !2) + !9 = !DISubroutineType(types: !10) + !10 = !{null} + !11 = !DILocalVariable(name: "x", scope: !8, file: !1, line: 2, type: !12) + !12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) + !13 = !DILocation(line: 2, column: 7, scope: !8) + !14 = !DILocation(line: 3, column: 1, scope: !8) + )"); auto *GV = M->getNamedValue("f"); ASSERT_TRUE(GV); auto *F = dyn_cast<Function>(GV); @@ -182,23 +177,24 @@ static void runWithDomTree( TEST(Local, MergeBasicBlockIntoOnlyPred) { LLVMContext C; - std::unique_ptr<Module> M = parseIR( - C, - "define i32 @f(i8* %str) {\n" - "entry:\n" - " br label %bb2.i\n" - "bb2.i: ; preds = %bb4.i, %entry\n" - " br i1 false, label %bb4.i, label %base2flt.exit204\n" - "bb4.i: ; preds = %bb2.i\n" - " br i1 false, label %base2flt.exit204, label %bb2.i\n" - "bb10.i196.bb7.i197_crit_edge: ; No predecessors!\n" - " br label %bb7.i197\n" - "bb7.i197: ; preds = %bb10.i196.bb7.i197_crit_edge\n" - " %.reg2mem.0 = phi i32 [ %.reg2mem.0, %bb10.i196.bb7.i197_crit_edge ]\n" - " br i1 undef, label %base2flt.exit204, label %base2flt.exit204\n" - "base2flt.exit204: ; preds = %bb7.i197, %bb7.i197, %bb2.i, %bb4.i\n" - " ret i32 0\n" - "}\n"); + std::unique_ptr<Module> M = parseIR(C, + R"( + define i32 @f(i8* %str) { + entry: + br label %bb2.i + bb2.i: ; preds = %bb4.i, %entry + br i1 false, label %bb4.i, label %base2flt.exit204 + bb4.i: ; preds = %bb2.i + br i1 false, label %base2flt.exit204, label %bb2.i + bb10.i196.bb7.i197_crit_edge: ; No predecessors! + br label %bb7.i197 + bb7.i197: ; preds = %bb10.i196.bb7.i197_crit_edge + %.reg2mem.0 = phi i32 [ %.reg2mem.0, %bb10.i196.bb7.i197_crit_edge ] + br i1 undef, label %base2flt.exit204, label %base2flt.exit204 + base2flt.exit204: ; preds = %bb7.i197, %bb7.i197, %bb2.i, %bb4.i + ret i32 0 + } + )"); runWithDomTree( *M, "f", [&](Function &F, DominatorTree *DT) { for (Function::iterator I = F.begin(), E = F.end(); I != E;) { @@ -212,3 +208,413 @@ TEST(Local, MergeBasicBlockIntoOnlyPred) { EXPECT_TRUE(DT->verify()); }); } + +TEST(Local, ConstantFoldTerminator) { + LLVMContext C; + + std::unique_ptr<Module> M = parseIR(C, + R"( + define void @br_same_dest() { + entry: + br i1 false, label %bb0, label %bb0 + bb0: + ret void + } + + define void @br_different_dest() { + entry: + br i1 true, label %bb0, label %bb1 + bb0: + br label %exit + bb1: + br label %exit + exit: + ret void + } + + define void @switch_2_different_dest() { + entry: + switch i32 0, label %default [ i32 0, label %bb0 ] + default: + ret void + bb0: + ret void + } + define void @switch_2_different_dest_default() { + entry: + switch i32 1, label %default [ i32 0, label %bb0 ] + default: + ret void + bb0: + ret void + } + define void @switch_3_different_dest() { + entry: + switch i32 0, label %default [ i32 0, label %bb0 + i32 1, label %bb1 ] + default: + ret void + bb0: + ret void + bb1: + ret void + } + + define void @switch_variable_2_default_dest(i32 %arg) { + entry: + switch i32 %arg, label %default [ i32 0, label %default ] + default: + ret void + } + + define void @switch_constant_2_default_dest() { + entry: + switch i32 1, label %default [ i32 0, label %default ] + default: + ret void + } + + define void @switch_constant_3_repeated_dest() { + entry: + switch i32 0, label %default [ i32 0, label %bb0 + i32 1, label %bb0 ] + bb0: + ret void + default: + ret void + } + + define void @indirectbr() { + entry: + indirectbr i8* blockaddress(@indirectbr, %bb0), [label %bb0, label %bb1] + bb0: + ret void + bb1: + ret void + } + + define void @indirectbr_repeated() { + entry: + indirectbr i8* blockaddress(@indirectbr_repeated, %bb0), [label %bb0, label %bb0] + bb0: + ret void + } + + define void @indirectbr_unreachable() { + entry: + indirectbr i8* blockaddress(@indirectbr_unreachable, %bb0), [label %bb1] + bb0: + ret void + bb1: + ret void + } + )"); + + auto CFAllTerminators = [&](Function &F, DominatorTree *DT) { + DeferredDominance DDT(*DT); + for (Function::iterator I = F.begin(), E = F.end(); I != E;) { + BasicBlock *BB = &*I++; + ConstantFoldTerminator(BB, true, nullptr, &DDT); + } + + EXPECT_TRUE(DDT.flush().verify()); + }; + + runWithDomTree(*M, "br_same_dest", CFAllTerminators); + runWithDomTree(*M, "br_different_dest", CFAllTerminators); + runWithDomTree(*M, "switch_2_different_dest", CFAllTerminators); + runWithDomTree(*M, "switch_2_different_dest_default", CFAllTerminators); + runWithDomTree(*M, "switch_3_different_dest", CFAllTerminators); + runWithDomTree(*M, "switch_variable_2_default_dest", CFAllTerminators); + runWithDomTree(*M, "switch_constant_2_default_dest", CFAllTerminators); + runWithDomTree(*M, "switch_constant_3_repeated_dest", CFAllTerminators); + runWithDomTree(*M, "indirectbr", CFAllTerminators); + runWithDomTree(*M, "indirectbr_repeated", CFAllTerminators); + runWithDomTree(*M, "indirectbr_unreachable", CFAllTerminators); +} + +struct SalvageDebugInfoTest : ::testing::Test { + LLVMContext C; + std::unique_ptr<Module> M; + Function *F = nullptr; + + void SetUp() { + M = parseIR(C, + R"( + define void @f() !dbg !8 { + entry: + %x = add i32 0, 1 + %y = add i32 %x, 2 + call void @llvm.dbg.value(metadata i32 %x, metadata !11, metadata !DIExpression()), !dbg !13 + call void @llvm.dbg.value(metadata i32 %y, metadata !11, metadata !DIExpression()), !dbg !13 + ret void, !dbg !14 + } + declare void @llvm.dbg.value(metadata, metadata, metadata) + !llvm.dbg.cu = !{!0} + !llvm.module.flags = !{!3, !4} + !0 = distinct !DICompileUnit(language: DW_LANG_C99, file: !1, producer: "clang version 6.0.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) + !1 = !DIFile(filename: "t2.c", directory: "foo") + !2 = !{} + !3 = !{i32 2, !"Dwarf Version", i32 4} + !4 = !{i32 2, !"Debug Info Version", i32 3} + !8 = distinct !DISubprogram(name: "f", scope: !1, file: !1, line: 1, type: !9, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: false, unit: !0, retainedNodes: !2) + !9 = !DISubroutineType(types: !10) + !10 = !{null} + !11 = !DILocalVariable(name: "x", scope: !8, file: !1, line: 2, type: !12) + !12 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed) + !13 = !DILocation(line: 2, column: 7, scope: !8) + !14 = !DILocation(line: 3, column: 1, scope: !8) + )"); + + auto *GV = M->getNamedValue("f"); + ASSERT_TRUE(GV); + F = dyn_cast<Function>(GV); + ASSERT_TRUE(F); + } + + bool doesDebugValueDescribeX(const DbgValueInst &DI) { + const auto &CI = *cast<ConstantInt>(DI.getValue()); + if (CI.isZero()) + return DI.getExpression()->getElements().equals( + {dwarf::DW_OP_plus_uconst, 1, dwarf::DW_OP_stack_value}); + else if (CI.isOneValue()) + return DI.getExpression()->getElements().empty(); + return false; + } + + bool doesDebugValueDescribeY(const DbgValueInst &DI) { + const auto &CI = *cast<ConstantInt>(DI.getValue()); + if (CI.isZero()) + return DI.getExpression()->getElements().equals( + {dwarf::DW_OP_plus_uconst, 1, dwarf::DW_OP_plus_uconst, 2, + dwarf::DW_OP_stack_value}); + else if (CI.isOneValue()) + return DI.getExpression()->getElements().equals( + {dwarf::DW_OP_plus_uconst, 2, dwarf::DW_OP_stack_value}); + return false; + } + + void verifyDebugValuesAreSalvaged() { + // Check that the debug values for %x and %y are preserved. + bool FoundX = false; + bool FoundY = false; + for (const Instruction &I : F->front()) { + auto DI = dyn_cast<DbgValueInst>(&I); + if (!DI) { + // The function should only contain debug values and a terminator. + ASSERT_TRUE(isa<TerminatorInst>(&I)); + continue; + } + EXPECT_EQ(DI->getVariable()->getName(), "x"); + FoundX |= doesDebugValueDescribeX(*DI); + FoundY |= doesDebugValueDescribeY(*DI); + } + ASSERT_TRUE(FoundX); + ASSERT_TRUE(FoundY); + } +}; + +TEST_F(SalvageDebugInfoTest, RecursiveInstDeletion) { + Instruction *Inst = &F->front().front(); + Inst = Inst->getNextNode(); // Get %y = add ... + ASSERT_TRUE(Inst); + bool Deleted = RecursivelyDeleteTriviallyDeadInstructions(Inst); + ASSERT_TRUE(Deleted); + verifyDebugValuesAreSalvaged(); +} + +TEST_F(SalvageDebugInfoTest, RecursiveBlockSimplification) { + BasicBlock *BB = &F->front(); + ASSERT_TRUE(BB); + bool Deleted = SimplifyInstructionsInBlock(BB); + ASSERT_TRUE(Deleted); + verifyDebugValuesAreSalvaged(); +} + +TEST(Local, ReplaceAllDbgUsesWith) { + using namespace llvm::dwarf; + + LLVMContext Ctx; + + // Note: The datalayout simulates Darwin/x86_64. + std::unique_ptr<Module> M = parseIR(Ctx, + R"( + target datalayout = "e-m:o-i63:64-f80:128-n8:16:32:64-S128" + + declare i32 @escape(i32) + + define void @f() !dbg !6 { + entry: + %a = add i32 0, 1, !dbg !15 + call void @llvm.dbg.value(metadata i32 %a, metadata !9, metadata !DIExpression()), !dbg !15 + + %b = add i64 0, 1, !dbg !16 + call void @llvm.dbg.value(metadata i64 %b, metadata !11, metadata !DIExpression()), !dbg !16 + call void @llvm.dbg.value(metadata i64 %b, metadata !11, metadata !DIExpression(DW_OP_lit0, DW_OP_mul)), !dbg !16 + call void @llvm.dbg.value(metadata i64 %b, metadata !11, metadata !DIExpression(DW_OP_lit0, DW_OP_mul, DW_OP_stack_value)), !dbg !16 + call void @llvm.dbg.value(metadata i64 %b, metadata !11, metadata !DIExpression(DW_OP_LLVM_fragment, 0, 8)), !dbg !16 + call void @llvm.dbg.value(metadata i64 %b, metadata !11, metadata !DIExpression(DW_OP_lit0, DW_OP_mul, DW_OP_LLVM_fragment, 0, 8)), !dbg !16 + call void @llvm.dbg.value(metadata i64 %b, metadata !11, metadata !DIExpression(DW_OP_lit0, DW_OP_mul, DW_OP_stack_value, DW_OP_LLVM_fragment, 0, 8)), !dbg !16 + + %c = inttoptr i64 0 to i64*, !dbg !17 + call void @llvm.dbg.declare(metadata i64* %c, metadata !13, metadata !DIExpression()), !dbg !17 + + %d = inttoptr i64 0 to i32*, !dbg !18 + call void @llvm.dbg.addr(metadata i32* %d, metadata !20, metadata !DIExpression()), !dbg !18 + + %e = add <2 x i16> zeroinitializer, zeroinitializer + call void @llvm.dbg.value(metadata <2 x i16> %e, metadata !14, metadata !DIExpression()), !dbg !18 + + %f = call i32 @escape(i32 0) + call void @llvm.dbg.value(metadata i32 %f, metadata !9, metadata !DIExpression()), !dbg !15 + + %barrier = call i32 @escape(i32 0) + + %g = call i32 @escape(i32 %f) + call void @llvm.dbg.value(metadata i32 %g, metadata !9, metadata !DIExpression()), !dbg !15 + + ret void, !dbg !19 + } + + declare void @llvm.dbg.addr(metadata, metadata, metadata) + declare void @llvm.dbg.declare(metadata, metadata, metadata) + declare void @llvm.dbg.value(metadata, metadata, metadata) + + !llvm.dbg.cu = !{!0} + !llvm.module.flags = !{!5} + + !0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, enums: !2) + !1 = !DIFile(filename: "/Users/vsk/Desktop/foo.ll", directory: "/") + !2 = !{} + !5 = !{i32 2, !"Debug Info Version", i32 3} + !6 = distinct !DISubprogram(name: "f", linkageName: "f", scope: null, file: !1, line: 1, type: !7, isLocal: false, isDefinition: true, scopeLine: 1, isOptimized: true, unit: !0, retainedNodes: !8) + !7 = !DISubroutineType(types: !2) + !8 = !{!9, !11, !13, !14} + !9 = !DILocalVariable(name: "1", scope: !6, file: !1, line: 1, type: !10) + !10 = !DIBasicType(name: "ty32", size: 32, encoding: DW_ATE_signed) + !11 = !DILocalVariable(name: "2", scope: !6, file: !1, line: 2, type: !12) + !12 = !DIBasicType(name: "ty64", size: 64, encoding: DW_ATE_signed) + !13 = !DILocalVariable(name: "3", scope: !6, file: !1, line: 3, type: !12) + !14 = !DILocalVariable(name: "4", scope: !6, file: !1, line: 4, type: !10) + !15 = !DILocation(line: 1, column: 1, scope: !6) + !16 = !DILocation(line: 2, column: 1, scope: !6) + !17 = !DILocation(line: 3, column: 1, scope: !6) + !18 = !DILocation(line: 4, column: 1, scope: !6) + !19 = !DILocation(line: 5, column: 1, scope: !6) + !20 = !DILocalVariable(name: "5", scope: !6, file: !1, line: 5, type: !10) + )"); + + bool BrokenDebugInfo = true; + verifyModule(*M, &errs(), &BrokenDebugInfo); + ASSERT_FALSE(BrokenDebugInfo); + + Function &F = *cast<Function>(M->getNamedValue("f")); + DominatorTree DT{F}; + + BasicBlock &BB = F.front(); + Instruction &A = BB.front(); + Instruction &B = *A.getNextNonDebugInstruction(); + Instruction &C = *B.getNextNonDebugInstruction(); + Instruction &D = *C.getNextNonDebugInstruction(); + Instruction &E = *D.getNextNonDebugInstruction(); + Instruction &F_ = *E.getNextNonDebugInstruction(); + Instruction &Barrier = *F_.getNextNonDebugInstruction(); + Instruction &G = *Barrier.getNextNonDebugInstruction(); + + // Simulate i32 <-> i64* conversion. Expect no updates: the datalayout says + // pointers are 64 bits, so the conversion would be lossy. + EXPECT_FALSE(replaceAllDbgUsesWith(A, C, C, DT)); + EXPECT_FALSE(replaceAllDbgUsesWith(C, A, A, DT)); + + // Simulate i32 <-> <2 x i16> conversion. This is unsupported. + EXPECT_FALSE(replaceAllDbgUsesWith(E, A, A, DT)); + EXPECT_FALSE(replaceAllDbgUsesWith(A, E, E, DT)); + + // Simulate i32* <-> i64* conversion. + EXPECT_TRUE(replaceAllDbgUsesWith(D, C, C, DT)); + + SmallVector<DbgInfoIntrinsic *, 2> CDbgVals; + findDbgUsers(CDbgVals, &C); + EXPECT_EQ(2U, CDbgVals.size()); + EXPECT_TRUE(any_of(CDbgVals, [](DbgInfoIntrinsic *DII) { + return isa<DbgAddrIntrinsic>(DII); + })); + EXPECT_TRUE(any_of(CDbgVals, [](DbgInfoIntrinsic *DII) { + return isa<DbgDeclareInst>(DII); + })); + + EXPECT_TRUE(replaceAllDbgUsesWith(C, D, D, DT)); + + SmallVector<DbgInfoIntrinsic *, 2> DDbgVals; + findDbgUsers(DDbgVals, &D); + EXPECT_EQ(2U, DDbgVals.size()); + EXPECT_TRUE(any_of(DDbgVals, [](DbgInfoIntrinsic *DII) { + return isa<DbgAddrIntrinsic>(DII); + })); + EXPECT_TRUE(any_of(DDbgVals, [](DbgInfoIntrinsic *DII) { + return isa<DbgDeclareInst>(DII); + })); + + // Introduce a use-before-def. Check that the dbg.value for %a is salvaged. + EXPECT_TRUE(replaceAllDbgUsesWith(A, F_, F_, DT)); + + auto *ADbgVal = cast<DbgValueInst>(A.getNextNode()); + EXPECT_EQ(ConstantInt::get(A.getType(), 0), ADbgVal->getVariableLocation()); + + // Introduce a use-before-def. Check that the dbg.values for %f are deleted. + EXPECT_TRUE(replaceAllDbgUsesWith(F_, G, G, DT)); + + SmallVector<DbgValueInst *, 1> FDbgVals; + findDbgValues(FDbgVals, &F); + EXPECT_EQ(0U, FDbgVals.size()); + + // Simulate i32 -> i64 conversion to test sign-extension. Here are some + // interesting cases to handle: + // 1) debug user has empty DIExpression + // 2) debug user has non-empty, non-stack-value'd DIExpression + // 3) debug user has non-empty, stack-value'd DIExpression + // 4-6) like (1-3), but with a fragment + EXPECT_TRUE(replaceAllDbgUsesWith(B, A, A, DT)); + + SmallVector<DbgValueInst *, 8> ADbgVals; + findDbgValues(ADbgVals, &A); + EXPECT_EQ(6U, ADbgVals.size()); + + // Check that %a has a dbg.value with a DIExpression matching \p Ops. + auto hasADbgVal = [&](ArrayRef<uint64_t> Ops) { + return any_of(ADbgVals, [&](DbgValueInst *DVI) { + assert(DVI->getVariable()->getName() == "2"); + return DVI->getExpression()->getElements() == Ops; + }); + }; + + // Case 1: The original expr is empty, so no deref is needed. + EXPECT_TRUE(hasADbgVal({DW_OP_dup, DW_OP_constu, 31, DW_OP_shr, DW_OP_lit0, + DW_OP_not, DW_OP_mul, DW_OP_or, DW_OP_stack_value})); + + // Case 2: Perform an address calculation with the original expr, deref it, + // then sign-extend the result. + EXPECT_TRUE(hasADbgVal({DW_OP_lit0, DW_OP_mul, DW_OP_deref, DW_OP_dup, + DW_OP_constu, 31, DW_OP_shr, DW_OP_lit0, DW_OP_not, + DW_OP_mul, DW_OP_or, DW_OP_stack_value})); + + // Case 3: Insert the sign-extension logic before the DW_OP_stack_value. + EXPECT_TRUE(hasADbgVal({DW_OP_lit0, DW_OP_mul, DW_OP_dup, DW_OP_constu, 31, + DW_OP_shr, DW_OP_lit0, DW_OP_not, DW_OP_mul, DW_OP_or, + DW_OP_stack_value})); + + // Cases 4-6: Just like cases 1-3, but preserve the fragment at the end. + EXPECT_TRUE(hasADbgVal({DW_OP_dup, DW_OP_constu, 31, DW_OP_shr, DW_OP_lit0, + DW_OP_not, DW_OP_mul, DW_OP_or, DW_OP_stack_value, + DW_OP_LLVM_fragment, 0, 8})); + EXPECT_TRUE( + hasADbgVal({DW_OP_lit0, DW_OP_mul, DW_OP_deref, DW_OP_dup, DW_OP_constu, + 31, DW_OP_shr, DW_OP_lit0, DW_OP_not, DW_OP_mul, DW_OP_or, + DW_OP_stack_value, DW_OP_LLVM_fragment, 0, 8})); + EXPECT_TRUE(hasADbgVal({DW_OP_lit0, DW_OP_mul, DW_OP_dup, DW_OP_constu, 31, + DW_OP_shr, DW_OP_lit0, DW_OP_not, DW_OP_mul, DW_OP_or, + DW_OP_stack_value, DW_OP_LLVM_fragment, 0, 8})); + + verifyModule(*M, &errs(), &BrokenDebugInfo); + ASSERT_FALSE(BrokenDebugInfo); +} diff --git a/unittests/Transforms/Utils/SSAUpdaterBulk.cpp b/unittests/Transforms/Utils/SSAUpdaterBulk.cpp new file mode 100644 index 000000000000..61cbcb7b1a77 --- /dev/null +++ b/unittests/Transforms/Utils/SSAUpdaterBulk.cpp @@ -0,0 +1,195 @@ +//===- SSAUpdaterBulk.cpp - Unit tests for SSAUpdaterBulk -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "llvm/Transforms/Utils/SSAUpdaterBulk.h" +#include "llvm/AsmParser/Parser.h" +#include "llvm/IR/BasicBlock.h" +#include "llvm/IR/Dominators.h" +#include "llvm/IR/IRBuilder.h" +#include "llvm/IR/Instructions.h" +#include "llvm/IR/LLVMContext.h" +#include "llvm/IR/Module.h" +#include "gtest/gtest.h" + +using namespace llvm; + +TEST(SSAUpdaterBulk, SimpleMerge) { + SSAUpdaterBulk Updater; + LLVMContext C; + Module M("SSAUpdaterTest", C); + IRBuilder<> B(C); + Type *I32Ty = B.getInt32Ty(); + auto *F = Function::Create(FunctionType::get(B.getVoidTy(), {I32Ty}, false), + GlobalValue::ExternalLinkage, "F", &M); + + // Generate a simple program: + // if: + // br i1 true, label %true, label %false + // true: + // %1 = add i32 %0, 1 + // %2 = sub i32 %0, 2 + // br label %merge + // false: + // %3 = add i32 %0, 3 + // %4 = sub i32 %0, 4 + // br label %merge + // merge: + // %5 = add i32 %1, 5 + // %6 = add i32 %3, 6 + // %7 = add i32 %2, %4 + // %8 = sub i32 %2, %4 + Argument *FirstArg = &*(F->arg_begin()); + BasicBlock *IfBB = BasicBlock::Create(C, "if", F); + BasicBlock *TrueBB = BasicBlock::Create(C, "true", F); + BasicBlock *FalseBB = BasicBlock::Create(C, "false", F); + BasicBlock *MergeBB = BasicBlock::Create(C, "merge", F); + + B.SetInsertPoint(IfBB); + B.CreateCondBr(B.getTrue(), TrueBB, FalseBB); + + B.SetInsertPoint(TrueBB); + Value *AddOp1 = B.CreateAdd(FirstArg, ConstantInt::get(I32Ty, 1)); + Value *SubOp1 = B.CreateSub(FirstArg, ConstantInt::get(I32Ty, 2)); + B.CreateBr(MergeBB); + + B.SetInsertPoint(FalseBB); + Value *AddOp2 = B.CreateAdd(FirstArg, ConstantInt::get(I32Ty, 3)); + Value *SubOp2 = B.CreateSub(FirstArg, ConstantInt::get(I32Ty, 4)); + B.CreateBr(MergeBB); + + B.SetInsertPoint(MergeBB, MergeBB->begin()); + auto *I1 = cast<Instruction>(B.CreateAdd(AddOp1, ConstantInt::get(I32Ty, 5))); + auto *I2 = cast<Instruction>(B.CreateAdd(AddOp2, ConstantInt::get(I32Ty, 6))); + auto *I3 = cast<Instruction>(B.CreateAdd(SubOp1, SubOp2)); + auto *I4 = cast<Instruction>(B.CreateSub(SubOp1, SubOp2)); + + // Now rewrite uses in instructions %5, %6, %7. They need to use a phi, which + // SSAUpdater should insert into %merge. + // Intentionally don't touch %8 to see that SSAUpdater only changes + // instructions that were explicitly specified. + unsigned VarNum = Updater.AddVariable("a", I32Ty); + Updater.AddAvailableValue(VarNum, TrueBB, AddOp1); + Updater.AddAvailableValue(VarNum, FalseBB, AddOp2); + Updater.AddUse(VarNum, &I1->getOperandUse(0)); + Updater.AddUse(VarNum, &I2->getOperandUse(0)); + + VarNum = Updater.AddVariable("b", I32Ty); + Updater.AddAvailableValue(VarNum, TrueBB, SubOp1); + Updater.AddAvailableValue(VarNum, FalseBB, SubOp2); + Updater.AddUse(VarNum, &I3->getOperandUse(0)); + Updater.AddUse(VarNum, &I3->getOperandUse(1)); + + DominatorTree DT(*F); + Updater.RewriteAllUses(&DT); + + // Check how %5 and %6 were rewritten. + PHINode *UpdatePhiA = dyn_cast_or_null<PHINode>(I1->getOperand(0)); + EXPECT_NE(UpdatePhiA, nullptr); + EXPECT_EQ(UpdatePhiA->getIncomingValueForBlock(TrueBB), AddOp1); + EXPECT_EQ(UpdatePhiA->getIncomingValueForBlock(FalseBB), AddOp2); + EXPECT_EQ(UpdatePhiA, dyn_cast_or_null<PHINode>(I1->getOperand(0))); + + // Check how %7 was rewritten. + PHINode *UpdatePhiB = dyn_cast_or_null<PHINode>(I3->getOperand(0)); + EXPECT_EQ(UpdatePhiB->getIncomingValueForBlock(TrueBB), SubOp1); + EXPECT_EQ(UpdatePhiB->getIncomingValueForBlock(FalseBB), SubOp2); + EXPECT_EQ(UpdatePhiB, dyn_cast_or_null<PHINode>(I3->getOperand(1))); + + // Check that %8 was kept untouched. + EXPECT_EQ(I4->getOperand(0), SubOp1); + EXPECT_EQ(I4->getOperand(1), SubOp2); +} + +TEST(SSAUpdaterBulk, Irreducible) { + SSAUpdaterBulk Updater; + LLVMContext C; + Module M("SSAUpdaterTest", C); + IRBuilder<> B(C); + Type *I32Ty = B.getInt32Ty(); + auto *F = Function::Create(FunctionType::get(B.getVoidTy(), {I32Ty}, false), + GlobalValue::ExternalLinkage, "F", &M); + + // Generate a small program with a multi-entry loop: + // if: + // %1 = add i32 %0, 1 + // br i1 true, label %loopmain, label %loopstart + // + // loopstart: + // %2 = add i32 %0, 2 + // br label %loopmain + // + // loopmain: + // %3 = add i32 %1, 3 + // br i1 true, label %loopstart, label %afterloop + // + // afterloop: + // %4 = add i32 %2, 4 + // ret i32 %0 + Argument *FirstArg = &*F->arg_begin(); + BasicBlock *IfBB = BasicBlock::Create(C, "if", F); + BasicBlock *LoopStartBB = BasicBlock::Create(C, "loopstart", F); + BasicBlock *LoopMainBB = BasicBlock::Create(C, "loopmain", F); + BasicBlock *AfterLoopBB = BasicBlock::Create(C, "afterloop", F); + + B.SetInsertPoint(IfBB); + Value *AddOp1 = B.CreateAdd(FirstArg, ConstantInt::get(I32Ty, 1)); + B.CreateCondBr(B.getTrue(), LoopMainBB, LoopStartBB); + + B.SetInsertPoint(LoopStartBB); + Value *AddOp2 = B.CreateAdd(FirstArg, ConstantInt::get(I32Ty, 2)); + B.CreateBr(LoopMainBB); + + B.SetInsertPoint(LoopMainBB); + auto *I1 = cast<Instruction>(B.CreateAdd(AddOp1, ConstantInt::get(I32Ty, 3))); + B.CreateCondBr(B.getTrue(), LoopStartBB, AfterLoopBB); + + B.SetInsertPoint(AfterLoopBB); + auto *I2 = cast<Instruction>(B.CreateAdd(AddOp2, ConstantInt::get(I32Ty, 4))); + ReturnInst *Return = B.CreateRet(FirstArg); + + // Now rewrite uses in instructions %3, %4, and 'ret i32 %0'. Only %4 needs a + // new phi, others should be able to work with existing values. + // The phi for %4 should be inserted into LoopMainBB and should look like + // this: + // %b = phi i32 [ %2, %loopstart ], [ undef, %if ] + // No other rewrites should be made. + + // Add use in %3. + unsigned VarNum = Updater.AddVariable("c", I32Ty); + Updater.AddAvailableValue(VarNum, IfBB, AddOp1); + Updater.AddUse(VarNum, &I1->getOperandUse(0)); + + // Add use in %4. + VarNum = Updater.AddVariable("b", I32Ty); + Updater.AddAvailableValue(VarNum, LoopStartBB, AddOp2); + Updater.AddUse(VarNum, &I2->getOperandUse(0)); + + // Add use in the return instruction. + VarNum = Updater.AddVariable("a", I32Ty); + Updater.AddAvailableValue(VarNum, &F->getEntryBlock(), FirstArg); + Updater.AddUse(VarNum, &Return->getOperandUse(0)); + + // Save all inserted phis into a vector. + SmallVector<PHINode *, 8> Inserted; + DominatorTree DT(*F); + Updater.RewriteAllUses(&DT, &Inserted); + + // Only one phi should have been inserted. + EXPECT_EQ(Inserted.size(), 1u); + + // I1 and Return should use the same values as they used before. + EXPECT_EQ(I1->getOperand(0), AddOp1); + EXPECT_EQ(Return->getOperand(0), FirstArg); + + // I2 should use the new phi. + PHINode *UpdatePhi = dyn_cast_or_null<PHINode>(I2->getOperand(0)); + EXPECT_NE(UpdatePhi, nullptr); + EXPECT_EQ(UpdatePhi->getIncomingValueForBlock(LoopStartBB), AddOp2); + EXPECT_EQ(UpdatePhi->getIncomingValueForBlock(IfBB), UndefValue::get(I32Ty)); +} diff --git a/unittests/Transforms/Vectorize/CMakeLists.txt b/unittests/Transforms/Vectorize/CMakeLists.txt new file mode 100644 index 000000000000..4f2b8e9a139c --- /dev/null +++ b/unittests/Transforms/Vectorize/CMakeLists.txt @@ -0,0 +1,11 @@ +set(LLVM_LINK_COMPONENTS + Analysis + Core + Vectorize + AsmParser + ) + +add_llvm_unittest(VectorizeTests + VPlanTest.cpp + VPlanHCFGTest.cpp + ) diff --git a/unittests/Transforms/Vectorize/VPlanHCFGTest.cpp b/unittests/Transforms/Vectorize/VPlanHCFGTest.cpp new file mode 100644 index 000000000000..215c3ef40daa --- /dev/null +++ b/unittests/Transforms/Vectorize/VPlanHCFGTest.cpp @@ -0,0 +1,158 @@ +//===- llvm/unittest/Transforms/Vectorize/VPlanHCFGTest.cpp ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../lib/Transforms/Vectorize/VPlan.h" +#include "../lib/Transforms/Vectorize/VPlanHCFGTransforms.h" +#include "VPlanTestBase.h" +#include "gtest/gtest.h" + +namespace llvm { +namespace { + +class VPlanHCFGTest : public VPlanTestBase {}; + +TEST_F(VPlanHCFGTest, testBuildHCFGInnerLoop) { + const char *ModuleString = + "define void @f(i32* %A, i64 %N) {\n" + "entry:\n" + " br label %for.body\n" + "for.body:\n" + " %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]\n" + " %arr.idx = getelementptr inbounds i32, i32* %A, i64 %indvars.iv\n" + " %l1 = load i32, i32* %arr.idx, align 4\n" + " %res = add i32 %l1, 10\n" + " store i32 %res, i32* %arr.idx, align 4\n" + " %indvars.iv.next = add i64 %indvars.iv, 1\n" + " %exitcond = icmp ne i64 %indvars.iv.next, %N\n" + " br i1 %exitcond, label %for.body, label %for.end\n" + "for.end:\n" + " ret void\n" + "}\n"; + + Module &M = parseModule(ModuleString); + + Function *F = M.getFunction("f"); + BasicBlock *LoopHeader = F->getEntryBlock().getSingleSuccessor(); + auto Plan = buildHCFG(LoopHeader); + + VPBasicBlock *Entry = Plan->getEntry()->getEntryBasicBlock(); + EXPECT_NE(nullptr, Entry->getSingleSuccessor()); + EXPECT_EQ(0u, Entry->getNumPredecessors()); + EXPECT_EQ(1u, Entry->getNumSuccessors()); + EXPECT_EQ(nullptr, Entry->getCondBit()); + + VPBasicBlock *VecBB = Entry->getSingleSuccessor()->getEntryBasicBlock(); + EXPECT_EQ(7u, VecBB->size()); + EXPECT_EQ(2u, VecBB->getNumPredecessors()); + EXPECT_EQ(2u, VecBB->getNumSuccessors()); + + auto Iter = VecBB->begin(); + VPInstruction *Phi = dyn_cast<VPInstruction>(&*Iter++); + EXPECT_EQ(Instruction::PHI, Phi->getOpcode()); + + VPInstruction *Idx = dyn_cast<VPInstruction>(&*Iter++); + EXPECT_EQ(Instruction::GetElementPtr, Idx->getOpcode()); + EXPECT_EQ(2u, Idx->getNumOperands()); + EXPECT_EQ(Phi, Idx->getOperand(1)); + + VPInstruction *Load = dyn_cast<VPInstruction>(&*Iter++); + EXPECT_EQ(Instruction::Load, Load->getOpcode()); + EXPECT_EQ(1u, Load->getNumOperands()); + EXPECT_EQ(Idx, Load->getOperand(0)); + + VPInstruction *Add = dyn_cast<VPInstruction>(&*Iter++); + EXPECT_EQ(Instruction::Add, Add->getOpcode()); + EXPECT_EQ(2u, Add->getNumOperands()); + EXPECT_EQ(Load, Add->getOperand(0)); + + VPInstruction *Store = dyn_cast<VPInstruction>(&*Iter++); + EXPECT_EQ(Instruction::Store, Store->getOpcode()); + EXPECT_EQ(2u, Store->getNumOperands()); + EXPECT_EQ(Add, Store->getOperand(0)); + EXPECT_EQ(Idx, Store->getOperand(1)); + + VPInstruction *IndvarAdd = dyn_cast<VPInstruction>(&*Iter++); + EXPECT_EQ(Instruction::Add, IndvarAdd->getOpcode()); + EXPECT_EQ(2u, IndvarAdd->getNumOperands()); + EXPECT_EQ(Phi, IndvarAdd->getOperand(0)); + + VPInstruction *ICmp = dyn_cast<VPInstruction>(&*Iter++); + EXPECT_EQ(Instruction::ICmp, ICmp->getOpcode()); + EXPECT_EQ(2u, ICmp->getNumOperands()); + EXPECT_EQ(IndvarAdd, ICmp->getOperand(0)); + EXPECT_EQ(VecBB->getCondBit(), ICmp); + + LoopVectorizationLegality::InductionList Inductions; + SmallPtrSet<Instruction *, 1> DeadInstructions; + VPlanHCFGTransforms::VPInstructionsToVPRecipes(Plan, &Inductions, + DeadInstructions); +} + +TEST_F(VPlanHCFGTest, testVPInstructionToVPRecipesInner) { + const char *ModuleString = + "define void @f(i32* %A, i64 %N) {\n" + "entry:\n" + " br label %for.body\n" + "for.body:\n" + " %indvars.iv = phi i64 [ 0, %entry ], [ %indvars.iv.next, %for.body ]\n" + " %arr.idx = getelementptr inbounds i32, i32* %A, i64 %indvars.iv\n" + " %l1 = load i32, i32* %arr.idx, align 4\n" + " %res = add i32 %l1, 10\n" + " store i32 %res, i32* %arr.idx, align 4\n" + " %indvars.iv.next = add i64 %indvars.iv, 1\n" + " %exitcond = icmp ne i64 %indvars.iv.next, %N\n" + " br i1 %exitcond, label %for.body, label %for.end\n" + "for.end:\n" + " ret void\n" + "}\n"; + + Module &M = parseModule(ModuleString); + + Function *F = M.getFunction("f"); + BasicBlock *LoopHeader = F->getEntryBlock().getSingleSuccessor(); + auto Plan = buildHCFG(LoopHeader); + + LoopVectorizationLegality::InductionList Inductions; + SmallPtrSet<Instruction *, 1> DeadInstructions; + VPlanHCFGTransforms::VPInstructionsToVPRecipes(Plan, &Inductions, + DeadInstructions); + + VPBlockBase *Entry = Plan->getEntry()->getEntryBasicBlock(); + EXPECT_NE(nullptr, Entry->getSingleSuccessor()); + EXPECT_EQ(0u, Entry->getNumPredecessors()); + EXPECT_EQ(1u, Entry->getNumSuccessors()); + + VPBasicBlock *VecBB = Entry->getSingleSuccessor()->getEntryBasicBlock(); + EXPECT_EQ(6u, VecBB->size()); + EXPECT_EQ(2u, VecBB->getNumPredecessors()); + EXPECT_EQ(2u, VecBB->getNumSuccessors()); + + auto Iter = VecBB->begin(); + auto *Phi = dyn_cast<VPWidenPHIRecipe>(&*Iter++); + EXPECT_NE(nullptr, Phi); + + auto *Idx = dyn_cast<VPWidenRecipe>(&*Iter++); + EXPECT_NE(nullptr, Idx); + + auto *Load = dyn_cast<VPWidenMemoryInstructionRecipe>(&*Iter++); + EXPECT_NE(nullptr, Load); + + auto *Add = dyn_cast<VPWidenRecipe>(&*Iter++); + EXPECT_NE(nullptr, Add); + + auto *Store = dyn_cast<VPWidenMemoryInstructionRecipe>(&*Iter++); + EXPECT_NE(nullptr, Store); + + auto *LastWiden = dyn_cast<VPWidenRecipe>(&*Iter++); + EXPECT_NE(nullptr, LastWiden); + EXPECT_EQ(VecBB->end(), Iter); +} + +} // namespace +} // namespace llvm diff --git a/unittests/Transforms/Vectorize/VPlanTest.cpp b/unittests/Transforms/Vectorize/VPlanTest.cpp new file mode 100644 index 000000000000..67712a7cae2e --- /dev/null +++ b/unittests/Transforms/Vectorize/VPlanTest.cpp @@ -0,0 +1,64 @@ +//===- llvm/unittests/Transforms/Vectorize/VPlanTest.cpp - VPlan tests ----===// +// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../lib/Transforms/Vectorize/VPlan.h" +#include "llvm/IR/Instruction.h" +#include "llvm/IR/Instructions.h" +#include "gtest/gtest.h" + +namespace llvm { +namespace { + +#define CHECK_ITERATOR(Range1, ...) \ + do { \ + std::vector<VPInstruction *> Tmp = {__VA_ARGS__}; \ + EXPECT_EQ((size_t)std::distance(Range1.begin(), Range1.end()), \ + Tmp.size()); \ + for (auto Pair : zip(Range1, make_range(Tmp.begin(), Tmp.end()))) \ + EXPECT_EQ(&std::get<0>(Pair), std::get<1>(Pair)); \ + } while (0) + +TEST(VPInstructionTest, insertBefore) { + VPInstruction *I1 = new VPInstruction(0, {}); + VPInstruction *I2 = new VPInstruction(1, {}); + VPInstruction *I3 = new VPInstruction(2, {}); + + VPBasicBlock VPBB1; + VPBB1.appendRecipe(I1); + + I2->insertBefore(I1); + CHECK_ITERATOR(VPBB1, I2, I1); + + I3->insertBefore(I2); + CHECK_ITERATOR(VPBB1, I3, I2, I1); +} + +TEST(VPInstructionTest, eraseFromParent) { + VPInstruction *I1 = new VPInstruction(0, {}); + VPInstruction *I2 = new VPInstruction(1, {}); + VPInstruction *I3 = new VPInstruction(2, {}); + + VPBasicBlock VPBB1; + VPBB1.appendRecipe(I1); + VPBB1.appendRecipe(I2); + VPBB1.appendRecipe(I3); + + I2->eraseFromParent(); + CHECK_ITERATOR(VPBB1, I1, I3); + + I1->eraseFromParent(); + CHECK_ITERATOR(VPBB1, I3); + + I3->eraseFromParent(); + EXPECT_TRUE(VPBB1.empty()); +} + +} // namespace +} // namespace llvm diff --git a/unittests/Transforms/Vectorize/VPlanTestBase.h b/unittests/Transforms/Vectorize/VPlanTestBase.h new file mode 100644 index 000000000000..da3b39f4df01 --- /dev/null +++ b/unittests/Transforms/Vectorize/VPlanTestBase.h @@ -0,0 +1,61 @@ +//===- llvm/unittest/Transforms/Vectorize/VPlanTestBase.h -----------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// \file +/// This file defines a VPlanTestBase class, which provides helpers to parse +/// a LLVM IR string and create VPlans given a loop entry block. +//===----------------------------------------------------------------------===// +#ifndef LLVM_UNITTESTS_TRANSFORMS_VECTORIZE_VPLANTESTBASE_H +#define LLVM_UNITTESTS_TRANSFORMS_VECTORIZE_VPLANTESTBASE_H + +#include "../lib/Transforms/Vectorize/VPlan.h" +#include "../lib/Transforms/Vectorize/VPlanHCFGBuilder.h" +#include "llvm/Analysis/LoopInfo.h" +#include "llvm/AsmParser/Parser.h" +#include "llvm/IR/Dominators.h" +#include "llvm/Support/SourceMgr.h" +#include "gtest/gtest.h" + +namespace llvm { + +/// Helper class to create a module from an assembly string and VPlans for a +/// given loop entry block. +class VPlanTestBase : public testing::Test { +protected: + std::unique_ptr<LLVMContext> Ctx; + std::unique_ptr<Module> M; + std::unique_ptr<LoopInfo> LI; + std::unique_ptr<DominatorTree> DT; + + VPlanTestBase() : Ctx(new LLVMContext) {} + + Module &parseModule(const char *ModuleString) { + SMDiagnostic Err; + M = parseAssemblyString(ModuleString, Err, *Ctx); + EXPECT_TRUE(M); + return *M; + } + + void doAnalysis(Function &F) { + DT.reset(new DominatorTree(F)); + LI.reset(new LoopInfo(*DT)); + } + + VPlanPtr buildHCFG(BasicBlock *LoopHeader) { + doAnalysis(*LoopHeader->getParent()); + + auto Plan = llvm::make_unique<VPlan>(); + VPlanHCFGBuilder HCFGBuilder(LI->getLoopFor(LoopHeader), LI.get()); + HCFGBuilder.buildHierarchicalCFG(*Plan.get()); + return Plan; + } +}; + +} // namespace llvm + +#endif // LLVM_UNITTESTS_TRANSFORMS_VECTORIZE_VPLANTESTBASE_H diff --git a/unittests/XRay/CMakeLists.txt b/unittests/XRay/CMakeLists.txt index 30bccd1bbe62..9872c901d137 100644 --- a/unittests/XRay/CMakeLists.txt +++ b/unittests/XRay/CMakeLists.txt @@ -2,12 +2,8 @@ set(LLVM_LINK_COMPONENTS Support ) -set(XRAYSources - GraphTest.cpp - ) - add_llvm_unittest(XRayTests - ${XRAYSources} + GraphTest.cpp ) add_dependencies(XRayTests intrinsics_gen) diff --git a/unittests/tools/CMakeLists.txt b/unittests/tools/CMakeLists.txt index 5ac4c950efe7..e7c7dca68d49 100644 --- a/unittests/tools/CMakeLists.txt +++ b/unittests/tools/CMakeLists.txt @@ -1,4 +1,10 @@ if(LLVM_TARGETS_TO_BUILD MATCHES "X86") - add_subdirectory(llvm-cfi-verify) + add_subdirectory( + llvm-cfi-verify + ) endif() +add_subdirectory( + llvm-exegesis +) + diff --git a/unittests/tools/llvm-cfi-verify/CMakeLists.txt b/unittests/tools/llvm-cfi-verify/CMakeLists.txt index e47bbdf7f131..8f865463de1b 100644 --- a/unittests/tools/llvm-cfi-verify/CMakeLists.txt +++ b/unittests/tools/llvm-cfi-verify/CMakeLists.txt @@ -13,5 +13,6 @@ set(LLVM_LINK_COMPONENTS add_llvm_unittest(CFIVerifyTests FileAnalysis.cpp - GraphBuilder.cpp) + GraphBuilder.cpp + ) target_link_libraries(CFIVerifyTests PRIVATE LLVMCFIVerify) diff --git a/unittests/tools/llvm-cfi-verify/FileAnalysis.cpp b/unittests/tools/llvm-cfi-verify/FileAnalysis.cpp index 27275d6e2312..05880fcec805 100644 --- a/unittests/tools/llvm-cfi-verify/FileAnalysis.cpp +++ b/unittests/tools/llvm-cfi-verify/FileAnalysis.cpp @@ -45,10 +45,10 @@ using ::testing::Field; namespace llvm { namespace cfi_verify { namespace { -class ELFx86TestFileAnalysis : public FileAnalysis { +class ELFTestFileAnalysis : public FileAnalysis { public: - ELFx86TestFileAnalysis() - : FileAnalysis(Triple("x86_64--"), SubtargetFeatures()) {} + ELFTestFileAnalysis(StringRef Trip) + : FileAnalysis(Triple(Trip), SubtargetFeatures()) {} // Expose this method publicly for testing. void parseSectionContents(ArrayRef<uint8_t> SectionBytes, @@ -62,6 +62,8 @@ public: }; class BasicFileAnalysisTest : public ::testing::Test { +public: + BasicFileAnalysisTest(StringRef Trip) : Analysis(Trip) {} protected: virtual void SetUp() { IgnoreDWARFFlag = true; @@ -70,17 +72,27 @@ protected: handleAllErrors(std::move(Err), [&](const UnsupportedDisassembly &E) { SuccessfullyInitialised = false; outs() - << "Note: CFIVerifyTests are disabled due to lack of x86 support " + << "Note: CFIVerifyTests are disabled due to lack of support " "on this build.\n"; }); } } bool SuccessfullyInitialised; - ELFx86TestFileAnalysis Analysis; + ELFTestFileAnalysis Analysis; +}; + +class BasicX86FileAnalysisTest : public BasicFileAnalysisTest { +public: + BasicX86FileAnalysisTest() : BasicFileAnalysisTest("x86_64--") {} +}; + +class BasicAArch64FileAnalysisTest : public BasicFileAnalysisTest { +public: + BasicAArch64FileAnalysisTest() : BasicFileAnalysisTest("aarch64--") {} }; -TEST_F(BasicFileAnalysisTest, BasicDisassemblyTraversalTest) { +TEST_F(BasicX86FileAnalysisTest, BasicDisassemblyTraversalTest) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -190,7 +202,7 @@ TEST_F(BasicFileAnalysisTest, BasicDisassemblyTraversalTest) { EXPECT_EQ(nullptr, Analysis.getPrevInstructionSequential(*InstrMeta)); } -TEST_F(BasicFileAnalysisTest, PrevAndNextFromBadInst) { +TEST_F(BasicX86FileAnalysisTest, PrevAndNextFromBadInst) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -213,7 +225,7 @@ TEST_F(BasicFileAnalysisTest, PrevAndNextFromBadInst) { EXPECT_EQ(1u, GoodInstrMeta->InstructionSize); } -TEST_F(BasicFileAnalysisTest, CFITrapTest) { +TEST_F(BasicX86FileAnalysisTest, CFITrapTest) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -248,7 +260,7 @@ TEST_F(BasicFileAnalysisTest, CFITrapTest) { Analysis.isCFITrap(Analysis.getInstructionOrDie(0xDEADBEEF + 28))); } -TEST_F(BasicFileAnalysisTest, FallThroughTest) { +TEST_F(BasicX86FileAnalysisTest, FallThroughTest) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -288,7 +300,7 @@ TEST_F(BasicFileAnalysisTest, FallThroughTest) { Analysis.canFallThrough(Analysis.getInstructionOrDie(0xDEADBEEF + 19))); } -TEST_F(BasicFileAnalysisTest, DefiniteNextInstructionTest) { +TEST_F(BasicX86FileAnalysisTest, DefiniteNextInstructionTest) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -378,7 +390,7 @@ TEST_F(BasicFileAnalysisTest, DefiniteNextInstructionTest) { EXPECT_EQ(0xDEADBEEF + 4, Next->VMAddress); } -TEST_F(BasicFileAnalysisTest, ControlFlowXRefsTest) { +TEST_F(BasicX86FileAnalysisTest, ControlFlowXRefsTest) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -483,7 +495,7 @@ TEST_F(BasicFileAnalysisTest, ControlFlowXRefsTest) { EXPECT_TRUE(Analysis.getDirectControlFlowXRefs(*InstrMetaPtr).empty()); } -TEST_F(BasicFileAnalysisTest, CFIProtectionInvalidTargets) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionInvalidTargets) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -507,7 +519,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionInvalidTargets) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionBasicFallthroughToUd2) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionBasicFallthroughToUd2) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -522,7 +534,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionBasicFallthroughToUd2) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionBasicJumpToUd2) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionBasicJumpToUd2) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -537,7 +549,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionBasicJumpToUd2) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionDualPathUd2) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionDualPathUd2) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -555,7 +567,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionDualPathUd2) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionDualPathSingleUd2) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionDualPathSingleUd2) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -572,7 +584,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionDualPathSingleUd2) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionDualFailLimitUpwards) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionDualFailLimitUpwards) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -597,7 +609,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionDualFailLimitUpwards) { SearchLengthForConditionalBranch = PrevSearchLengthForConditionalBranch; } -TEST_F(BasicFileAnalysisTest, CFIProtectionDualFailLimitDownwards) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionDualFailLimitDownwards) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -621,7 +633,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionDualFailLimitDownwards) { SearchLengthForUndef = PrevSearchLengthForUndef; } -TEST_F(BasicFileAnalysisTest, CFIProtectionGoodAndBadPaths) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionGoodAndBadPaths) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -637,7 +649,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionGoodAndBadPaths) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionWithUnconditionalJumpInFallthrough) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionWithUnconditionalJumpInFallthrough) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -653,7 +665,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionWithUnconditionalJumpInFallthrough) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionComplexExample) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionComplexExample) { if (!SuccessfullyInitialised) return; // See unittests/GraphBuilder.cpp::BuildFlowGraphComplexExample for this @@ -683,7 +695,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionComplexExample) { SearchLengthForUndef = PrevSearchLengthForUndef; } -TEST_F(BasicFileAnalysisTest, UndefSearchLengthOneTest) { +TEST_F(BasicX86FileAnalysisTest, UndefSearchLengthOneTest) { Analysis.parseSectionContents( { 0x77, 0x0d, // 0x688118: ja 0x688127 [+12] @@ -702,7 +714,7 @@ TEST_F(BasicFileAnalysisTest, UndefSearchLengthOneTest) { SearchLengthForUndef = PrevSearchLengthForUndef; } -TEST_F(BasicFileAnalysisTest, UndefSearchLengthOneTestFarAway) { +TEST_F(BasicX86FileAnalysisTest, UndefSearchLengthOneTestFarAway) { Analysis.parseSectionContents( { 0x74, 0x73, // 0x7759eb: je 0x775a60 @@ -741,7 +753,7 @@ TEST_F(BasicFileAnalysisTest, UndefSearchLengthOneTestFarAway) { SearchLengthForUndef = PrevSearchLengthForUndef; } -TEST_F(BasicFileAnalysisTest, CFIProtectionClobberSinglePathExplicit) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionClobberSinglePathExplicit) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -757,7 +769,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionClobberSinglePathExplicit) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionClobberSinglePathExplicit2) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionClobberSinglePathExplicit2) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -773,7 +785,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionClobberSinglePathExplicit2) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionClobberSinglePathImplicit) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionClobberSinglePathImplicit) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -789,7 +801,7 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionClobberSinglePathImplicit) { Analysis.validateCFIProtection(Result)); } -TEST_F(BasicFileAnalysisTest, CFIProtectionClobberDualPathImplicit) { +TEST_F(BasicX86FileAnalysisTest, CFIProtectionClobberDualPathImplicit) { if (!SuccessfullyInitialised) return; Analysis.parseSectionContents( @@ -807,6 +819,243 @@ TEST_F(BasicFileAnalysisTest, CFIProtectionClobberDualPathImplicit) { Analysis.validateCFIProtection(Result)); } +TEST_F(BasicAArch64FileAnalysisTest, AArch64BasicUnprotected) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x00, 0x01, 0x3f, 0xd6, // 0: blr x8 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF); + EXPECT_EQ(CFIProtectionStatus::FAIL_ORPHANS, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64BasicProtected) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x00, 0x01, 0x3f, 0xd6, // 8: blr x8 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 8); + EXPECT_EQ(CFIProtectionStatus::PROTECTED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64ClobberBasic) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x08, 0x05, 0x00, 0x91, // 8: add x8, x8, #1 + 0x00, 0x01, 0x3f, 0xd6, // 12: blr x8 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 12); + EXPECT_EQ(CFIProtectionStatus::FAIL_REGISTER_CLOBBERED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64ClobberOneLoad) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x21, 0x09, 0x40, 0xf9, // 8: ldr x1, [x9,#16] + 0x20, 0x00, 0x1f, 0xd6, // 12: br x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 12); + EXPECT_EQ(CFIProtectionStatus::PROTECTED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64ClobberLoadAddGood) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x21, 0x04, 0x00, 0x91, // 8: add x1, x1, #1 + 0x21, 0x09, 0x40, 0xf9, // 12: ldr x1, [x9,#16] + 0x20, 0x00, 0x1f, 0xd6, // 16: br x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 16); + EXPECT_EQ(CFIProtectionStatus::PROTECTED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64ClobberLoadAddBad) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x21, 0x09, 0x40, 0xf9, // 8: ldr x1, [x9,#16] + 0x21, 0x04, 0x00, 0x91, // 12: add x1, x1, #1 + 0x20, 0x00, 0x1f, 0xd6, // 16: br x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 16); + EXPECT_EQ(CFIProtectionStatus::FAIL_REGISTER_CLOBBERED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64ClobberLoadAddBad2) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x29, 0x04, 0x00, 0x91, // 16: add x9, x1, #1 + 0x21, 0x09, 0x40, 0xf9, // 12: ldr x1, [x9,#16] + 0x20, 0x00, 0x1f, 0xd6, // 16: br x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 16); + EXPECT_EQ(CFIProtectionStatus::FAIL_REGISTER_CLOBBERED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64ClobberTwoLoads) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x21, 0x09, 0x40, 0xf9, // 8: ldr x1, [x9,#16] + 0x21, 0x08, 0x40, 0xf9, // 12: ldr x1, [x1,#16] + 0x20, 0x00, 0x1f, 0xd6, // 16: br x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 16); + EXPECT_EQ(CFIProtectionStatus::FAIL_REGISTER_CLOBBERED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64ClobberUnrelatedSecondLoad) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x21, 0x09, 0x40, 0xf9, // 8: ldr x1, [x9,#16] + 0x21, 0x09, 0x40, 0xf9, // 12: ldr x1, [x9,#16] + 0x20, 0x00, 0x1f, 0xd6, // 16: br x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 16); + EXPECT_EQ(CFIProtectionStatus::PROTECTED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64ClobberUnrelatedLoads) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x49, 0x00, 0x00, 0x54, // 0: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 4: brk #0x1 + 0x22, 0x09, 0x40, 0xf9, // 8: ldr x2, [x9,#16] + 0x22, 0x08, 0x40, 0xf9, // 12: ldr x2, [x1,#16] + 0x20, 0x00, 0x1f, 0xd6, // 16: br x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 16); + EXPECT_EQ(CFIProtectionStatus::PROTECTED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64GoodAndBadPaths) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0x03, 0x00, 0x00, 0x14, // 0: b 12 + 0x49, 0x00, 0x00, 0x54, // 4: b.ls 8 + 0x20, 0x00, 0x20, 0xd4, // 8: brk #0x1 + 0x20, 0x00, 0x1f, 0xd6, // 12: br x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 12); + EXPECT_EQ(CFIProtectionStatus::FAIL_ORPHANS, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64TwoPaths) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0xc9, 0x00, 0x00, 0x54, // 0: b.ls 24 + 0x21, 0x08, 0x40, 0xf9, // 4: ldr x1, [x1,#16] + 0x03, 0x00, 0x00, 0x14, // 8: b 12 + 0x69, 0x00, 0x00, 0x54, // 12: b.ls 12 + 0x21, 0x08, 0x40, 0xf9, // 16: ldr x1, [x1,#16] + 0x20, 0x00, 0x1f, 0xd6, // 20: br x1 + 0x20, 0x00, 0x20, 0xd4, // 24: brk #0x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 20); + EXPECT_EQ(CFIProtectionStatus::PROTECTED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64TwoPathsBadLoad1) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0xe9, 0x00, 0x00, 0x54, // 0: b.ls 28 + 0x21, 0x08, 0x40, 0xf9, // 4: ldr x1, [x1,#16] + 0x21, 0x08, 0x40, 0xf9, // 8: ldr x1, [x1,#16] + 0x03, 0x00, 0x00, 0x14, // 12: b 12 + 0x69, 0x00, 0x00, 0x54, // 16: b.ls 12 + 0x21, 0x08, 0x40, 0xf9, // 20: ldr x1, [x1,#16] + 0x20, 0x00, 0x1f, 0xd6, // 24: br x1 + 0x20, 0x00, 0x20, 0xd4, // 28: brk #0x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 24); + EXPECT_EQ(CFIProtectionStatus::FAIL_REGISTER_CLOBBERED, + Analysis.validateCFIProtection(Result)); +} + +TEST_F(BasicAArch64FileAnalysisTest, AArch64TwoPathsBadLoad2) { + if (!SuccessfullyInitialised) + return; + Analysis.parseSectionContents( + { + 0xe9, 0x00, 0x00, 0x54, // 0: b.ls 28 + 0x21, 0x08, 0x40, 0xf9, // 4: ldr x1, [x1,#16] + 0x03, 0x00, 0x00, 0x14, // 8: b 12 + 0x89, 0x00, 0x00, 0x54, // 12: b.ls 16 + 0x21, 0x08, 0x40, 0xf9, // 16: ldr x1, [x1,#16] + 0x21, 0x08, 0x40, 0xf9, // 20: ldr x1, [x1,#16] + 0x20, 0x00, 0x1f, 0xd6, // 24: br x1 + 0x20, 0x00, 0x20, 0xd4, // 28: brk #0x1 + }, + 0xDEADBEEF); + GraphResult Result = GraphBuilder::buildFlowGraph(Analysis, 0xDEADBEEF + 24); + EXPECT_EQ(CFIProtectionStatus::FAIL_REGISTER_CLOBBERED, + Analysis.validateCFIProtection(Result)); +} + } // anonymous namespace } // end namespace cfi_verify } // end namespace llvm diff --git a/unittests/tools/llvm-exegesis/AArch64/CMakeLists.txt b/unittests/tools/llvm-exegesis/AArch64/CMakeLists.txt new file mode 100644 index 000000000000..392efce26c00 --- /dev/null +++ b/unittests/tools/llvm-exegesis/AArch64/CMakeLists.txt @@ -0,0 +1,21 @@ +include_directories( + ${LLVM_MAIN_SRC_DIR}/lib/Target/AArch64 + ${LLVM_BINARY_DIR}/lib/Target/AArch64 + ${LLVM_MAIN_SRC_DIR}/tools/llvm-exegesis/lib + ) + +set(LLVM_LINK_COMPONENTS + MC + MCParser + Object + Support + Symbolize + AArch64 + ) + +add_llvm_unittest(LLVMExegesisAArch64Tests + TargetTest.cpp + ) +target_link_libraries(LLVMExegesisAArch64Tests PRIVATE + LLVMExegesis + LLVMExegesisAArch64) diff --git a/unittests/tools/llvm-exegesis/AArch64/TargetTest.cpp b/unittests/tools/llvm-exegesis/AArch64/TargetTest.cpp new file mode 100644 index 000000000000..cf3c11a9fdef --- /dev/null +++ b/unittests/tools/llvm-exegesis/AArch64/TargetTest.cpp @@ -0,0 +1,53 @@ +#include "Target.h" + +#include <cassert> +#include <memory> + +#include "MCTargetDesc/AArch64MCTargetDesc.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace exegesis { + +void InitializeAArch64ExegesisTarget(); + +namespace { + +using testing::Gt; +using testing::NotNull; +using testing::SizeIs; + +constexpr const char kTriple[] = "aarch64-unknown-linux"; + +class AArch64TargetTest : public ::testing::Test { +protected: + AArch64TargetTest() + : ExegesisTarget_(ExegesisTarget::lookup(llvm::Triple(kTriple))) { + EXPECT_THAT(ExegesisTarget_, NotNull()); + std::string error; + Target_ = llvm::TargetRegistry::lookupTarget(kTriple, error); + EXPECT_THAT(Target_, NotNull()); + } + static void SetUpTestCase() { + LLVMInitializeAArch64TargetInfo(); + LLVMInitializeAArch64Target(); + LLVMInitializeAArch64TargetMC(); + InitializeAArch64ExegesisTarget(); + } + + const llvm::Target *Target_; + const ExegesisTarget *const ExegesisTarget_; +}; + +TEST_F(AArch64TargetTest, SetRegToConstant) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "generic", "")); + // The AArch64 target currently doesn't know how to set register values + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::AArch64::X0); + EXPECT_THAT(Insts, SizeIs(0)); +} + +} // namespace +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/ARM/AssemblerTest.cpp b/unittests/tools/llvm-exegesis/ARM/AssemblerTest.cpp new file mode 100644 index 000000000000..8c70b9eaaade --- /dev/null +++ b/unittests/tools/llvm-exegesis/ARM/AssemblerTest.cpp @@ -0,0 +1,48 @@ +//===-- AssemblerTest.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../Common/AssemblerUtils.h" +#include "ARMInstrInfo.h" + +namespace exegesis { +namespace { + +using llvm::MCInstBuilder; + +class ARMMachineFunctionGeneratorTest + : public MachineFunctionGeneratorBaseTest { +protected: + ARMMachineFunctionGeneratorTest() + : MachineFunctionGeneratorBaseTest("armv7-none-linux-gnueabi", "") {} + + static void SetUpTestCase() { + LLVMInitializeARMTargetInfo(); + LLVMInitializeARMTargetMC(); + LLVMInitializeARMTarget(); + LLVMInitializeARMAsmPrinter(); + } +}; + +TEST_F(ARMMachineFunctionGeneratorTest, DISABLED_JitFunction) { + Check(llvm::MCInst(), 0x1e, 0xff, 0x2f, 0xe1); +} + +TEST_F(ARMMachineFunctionGeneratorTest, DISABLED_JitFunctionADDrr) { + Check(MCInstBuilder(llvm::ARM::ADDrr) + .addReg(llvm::ARM::R0) + .addReg(llvm::ARM::R0) + .addReg(llvm::ARM::R0) + .addImm(llvm::ARMCC::AL) + .addReg(0) + .addReg(0), + 0x00, 0x00, 0x80, 0xe0, 0x1e, 0xff, 0x2f, 0xe1); +} + +} // namespace +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/ARM/CMakeLists.txt b/unittests/tools/llvm-exegesis/ARM/CMakeLists.txt new file mode 100644 index 000000000000..b000974938f7 --- /dev/null +++ b/unittests/tools/llvm-exegesis/ARM/CMakeLists.txt @@ -0,0 +1,19 @@ +include_directories( + ${LLVM_MAIN_SRC_DIR}/lib/Target/ARM + ${LLVM_BINARY_DIR}/lib/Target/ARM + ${LLVM_MAIN_SRC_DIR}/tools/llvm-exegesis/lib + ) + +set(LLVM_LINK_COMPONENTS + MC + MCParser + Object + Support + Symbolize + ARM + ) + +add_llvm_unittest(LLVMExegesisARMTests + AssemblerTest.cpp + ) +target_link_libraries(LLVMExegesisARMTests PRIVATE LLVMExegesis) diff --git a/unittests/tools/llvm-exegesis/BenchmarkResultTest.cpp b/unittests/tools/llvm-exegesis/BenchmarkResultTest.cpp new file mode 100644 index 000000000000..2b604dcdeb44 --- /dev/null +++ b/unittests/tools/llvm-exegesis/BenchmarkResultTest.cpp @@ -0,0 +1,134 @@ +//===-- BenchmarkResultTest.cpp ---------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "BenchmarkResult.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/Path.h" +#include "llvm/Support/YAMLTraits.h" +#include "llvm/Support/raw_ostream.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +using ::testing::AllOf; +using ::testing::Eq; +using ::testing::get; +using ::testing::Pointwise; +using ::testing::Property; + +namespace exegesis { + +bool operator==(const BenchmarkMeasure &A, const BenchmarkMeasure &B) { + return std::tie(A.Key, A.Value) == std::tie(B.Key, B.Value); +} + +static std::string Dump(const llvm::MCInst &McInst) { + std::string Buffer; + llvm::raw_string_ostream OS(Buffer); + McInst.print(OS); + return Buffer; +} + +MATCHER(EqMCInst, "") { + const std::string Lhs = Dump(get<0>(arg)); + const std::string Rhs = Dump(get<1>(arg)); + if (Lhs != Rhs) { + *result_listener << Lhs << " <=> " << Rhs; + return false; + } + return true; +} + +namespace { + +static constexpr const unsigned kInstrId = 5; +static constexpr const char kInstrName[] = "Instruction5"; +static constexpr const unsigned kReg1Id = 1; +static constexpr const char kReg1Name[] = "Reg1"; +static constexpr const unsigned kReg2Id = 2; +static constexpr const char kReg2Name[] = "Reg2"; + +TEST(BenchmarkResultTest, WriteToAndReadFromDisk) { + llvm::ExitOnError ExitOnErr; + BenchmarkResultContext Ctx; + Ctx.addInstrEntry(kInstrId, kInstrName); + Ctx.addRegEntry(kReg1Id, kReg1Name); + Ctx.addRegEntry(kReg2Id, kReg2Name); + + InstructionBenchmark ToDisk; + + ToDisk.Key.Instructions.push_back(llvm::MCInstBuilder(kInstrId) + .addReg(kReg1Id) + .addReg(kReg2Id) + .addImm(123) + .addFPImm(0.5)); + ToDisk.Key.Config = "config"; + ToDisk.Mode = InstructionBenchmark::Latency; + ToDisk.CpuName = "cpu_name"; + ToDisk.LLVMTriple = "llvm_triple"; + ToDisk.NumRepetitions = 1; + ToDisk.Measurements.push_back(BenchmarkMeasure{"a", 1, "debug a"}); + ToDisk.Measurements.push_back(BenchmarkMeasure{"b", 2, ""}); + ToDisk.Error = "error"; + ToDisk.Info = "info"; + + llvm::SmallString<64> Filename; + std::error_code EC; + EC = llvm::sys::fs::createUniqueDirectory("BenchmarkResultTestDir", Filename); + ASSERT_FALSE(EC); + llvm::sys::path::append(Filename, "data.yaml"); + ExitOnErr(ToDisk.writeYaml(Ctx, Filename)); + + { + // One-element version. + const auto FromDisk = + ExitOnErr(InstructionBenchmark::readYaml(Ctx, Filename)); + + EXPECT_THAT(FromDisk.Key.Instructions, + Pointwise(EqMCInst(), ToDisk.Key.Instructions)); + EXPECT_EQ(FromDisk.Key.Config, ToDisk.Key.Config); + EXPECT_EQ(FromDisk.Mode, ToDisk.Mode); + EXPECT_EQ(FromDisk.CpuName, ToDisk.CpuName); + EXPECT_EQ(FromDisk.LLVMTriple, ToDisk.LLVMTriple); + EXPECT_EQ(FromDisk.NumRepetitions, ToDisk.NumRepetitions); + EXPECT_THAT(FromDisk.Measurements, ToDisk.Measurements); + EXPECT_THAT(FromDisk.Error, ToDisk.Error); + EXPECT_EQ(FromDisk.Info, ToDisk.Info); + } + { + // Vector version. + const auto FromDiskVector = + ExitOnErr(InstructionBenchmark::readYamls(Ctx, Filename)); + ASSERT_EQ(FromDiskVector.size(), size_t{1}); + const auto FromDisk = FromDiskVector[0]; + EXPECT_THAT(FromDisk.Key.Instructions, + Pointwise(EqMCInst(), ToDisk.Key.Instructions)); + EXPECT_EQ(FromDisk.Key.Config, ToDisk.Key.Config); + EXPECT_EQ(FromDisk.Mode, ToDisk.Mode); + EXPECT_EQ(FromDisk.CpuName, ToDisk.CpuName); + EXPECT_EQ(FromDisk.LLVMTriple, ToDisk.LLVMTriple); + EXPECT_EQ(FromDisk.NumRepetitions, ToDisk.NumRepetitions); + EXPECT_THAT(FromDisk.Measurements, ToDisk.Measurements); + EXPECT_THAT(FromDisk.Error, ToDisk.Error); + EXPECT_EQ(FromDisk.Info, ToDisk.Info); + } +} + +TEST(BenchmarkResultTest, BenchmarkMeasureStats) { + BenchmarkMeasureStats Stats; + Stats.push(BenchmarkMeasure{"a", 0.5, "debug a"}); + Stats.push(BenchmarkMeasure{"a", 1.5, "debug a"}); + Stats.push(BenchmarkMeasure{"a", -1.0, "debug a"}); + Stats.push(BenchmarkMeasure{"a", 0.0, "debug a"}); + EXPECT_EQ(Stats.min(), -1.0); + EXPECT_EQ(Stats.max(), 1.5); + EXPECT_EQ(Stats.avg(), 0.25); // (0.5+1.5-1.0+0.0) / 4 +} +} // namespace +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/CMakeLists.txt b/unittests/tools/llvm-exegesis/CMakeLists.txt new file mode 100644 index 000000000000..9afa17a8e801 --- /dev/null +++ b/unittests/tools/llvm-exegesis/CMakeLists.txt @@ -0,0 +1,28 @@ +include_directories( + ${LLVM_MAIN_SRC_DIR}/tools/llvm-exegesis/lib + ) + +set(LLVM_LINK_COMPONENTS + MC + MCParser + Object + Support + Symbolize + ) + +add_llvm_unittest(LLVMExegesisTests + BenchmarkResultTest.cpp + ClusteringTest.cpp + PerfHelperTest.cpp + ) +target_link_libraries(LLVMExegesisTests PRIVATE LLVMExegesis) + +if(LLVM_TARGETS_TO_BUILD MATCHES "X86") + add_subdirectory(X86) +endif() +if(LLVM_TARGETS_TO_BUILD MATCHES "ARM") + add_subdirectory(ARM) +endif() +if(LLVM_TARGETS_TO_BUILD MATCHES "AArch64") + add_subdirectory(AArch64) +endif() diff --git a/unittests/tools/llvm-exegesis/ClusteringTest.cpp b/unittests/tools/llvm-exegesis/ClusteringTest.cpp new file mode 100644 index 000000000000..ecdb7d2a274b --- /dev/null +++ b/unittests/tools/llvm-exegesis/ClusteringTest.cpp @@ -0,0 +1,100 @@ +//===-- ClusteringTest.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Clustering.h" +#include "BenchmarkResult.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace exegesis { + +namespace { + +using testing::Field; +using testing::UnorderedElementsAre; +using testing::UnorderedElementsAreArray; + +TEST(ClusteringTest, Clusters3D) { + std::vector<InstructionBenchmark> Points(6); + + // Cluster around (x=0, y=1, z=2): points {0, 3}. + Points[0].Measurements = {{"x", 0.01, ""}, {"y", 1.02, ""}, {"z", 1.98, "A"}}; + Points[3].Measurements = {{"x", -0.01, ""}, {"y", 1.02, ""}, {"z", 1.98, ""}}; + // Cluster around (x=1, y=1, z=2): points {1, 4}. + Points[1].Measurements = {{"x", 1.01, ""}, {"y", 1.02, ""}, {"z", 1.98, ""}}; + Points[4].Measurements = {{"x", 0.99, ""}, {"y", 1.02, ""}, {"z", 1.98, ""}}; + // Cluster around (x=0, y=0, z=0): points {5}, marked as noise. + Points[5].Measurements = {{"x", 0.0, ""}, {"y", 0.01, ""}, {"z", -0.02, ""}}; + // Error cluster: points {2} + Points[2].Error = "oops"; + + auto HasPoints = [](const std::vector<int> &Indices) { + return Field(&InstructionBenchmarkClustering::Cluster::PointIndices, + UnorderedElementsAreArray(Indices)); + }; + + auto Clustering = InstructionBenchmarkClustering::create(Points, 2, 0.25); + ASSERT_TRUE((bool)Clustering); + EXPECT_THAT(Clustering.get().getValidClusters(), + UnorderedElementsAre(HasPoints({0, 3}), HasPoints({1, 4}))); + EXPECT_THAT(Clustering.get().getCluster( + InstructionBenchmarkClustering::ClusterId::noise()), + HasPoints({5})); + EXPECT_THAT(Clustering.get().getCluster( + InstructionBenchmarkClustering::ClusterId::error()), + HasPoints({2})); + + EXPECT_EQ(Clustering.get().getClusterIdForPoint(2), + InstructionBenchmarkClustering::ClusterId::error()); + EXPECT_EQ(Clustering.get().getClusterIdForPoint(5), + InstructionBenchmarkClustering::ClusterId::noise()); + EXPECT_EQ(Clustering.get().getClusterIdForPoint(0), + Clustering.get().getClusterIdForPoint(3)); + EXPECT_EQ(Clustering.get().getClusterIdForPoint(1), + Clustering.get().getClusterIdForPoint(4)); +} + +TEST(ClusteringTest, Clusters3D_InvalidSize) { + std::vector<InstructionBenchmark> Points(6); + Points[0].Measurements = {{"x", 0.01, ""}, {"y", 1.02, ""}, {"z", 1.98, ""}}; + Points[1].Measurements = {{"y", 1.02, ""}, {"z", 1.98, ""}}; + auto Error = + InstructionBenchmarkClustering::create(Points, 2, 0.25).takeError(); + ASSERT_TRUE((bool)Error); + consumeError(std::move(Error)); +} + +TEST(ClusteringTest, Clusters3D_InvalidOrder) { + std::vector<InstructionBenchmark> Points(6); + Points[0].Measurements = {{"x", 0.01, ""}, {"y", 1.02, ""}}; + Points[1].Measurements = {{"y", 1.02, ""}, {"x", 1.98, ""}}; + auto Error = + InstructionBenchmarkClustering::create(Points, 2, 0.25).takeError(); + ASSERT_TRUE((bool)Error); + consumeError(std::move(Error)); +} + +TEST(ClusteringTest, Ordering) { + ASSERT_LT(InstructionBenchmarkClustering::ClusterId::makeValid(1), + InstructionBenchmarkClustering::ClusterId::makeValid(2)); + + ASSERT_LT(InstructionBenchmarkClustering::ClusterId::makeValid(2), + InstructionBenchmarkClustering::ClusterId::noise()); + + ASSERT_LT(InstructionBenchmarkClustering::ClusterId::makeValid(2), + InstructionBenchmarkClustering::ClusterId::error()); + + ASSERT_LT(InstructionBenchmarkClustering::ClusterId::noise(), + InstructionBenchmarkClustering::ClusterId::error()); +} + +} // namespace +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/Common/AssemblerUtils.h b/unittests/tools/llvm-exegesis/Common/AssemblerUtils.h new file mode 100644 index 000000000000..a0cfb1d86c7a --- /dev/null +++ b/unittests/tools/llvm-exegesis/Common/AssemblerUtils.h @@ -0,0 +1,87 @@ +//===-- AssemblerUtils.h ----------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "Assembler.h" +#include "Target.h" +#include "llvm/ADT/ArrayRef.h" +#include "llvm/CodeGen/MachineInstrBuilder.h" +#include "llvm/CodeGen/TargetInstrInfo.h" +#include "llvm/CodeGen/TargetSubtargetInfo.h" +#include "llvm/MC/MCInstBuilder.h" +#include "llvm/Support/Host.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace exegesis { + +class MachineFunctionGeneratorBaseTest : public ::testing::Test { +protected: + MachineFunctionGeneratorBaseTest(const std::string &TT, + const std::string &CpuName) + : TT(TT), CpuName(CpuName), + CanExecute(llvm::Triple(TT).getArch() == + llvm::Triple(llvm::sys::getProcessTriple()).getArch()) { + if (!CanExecute) { + llvm::outs() << "Skipping execution, host:" + << llvm::sys::getProcessTriple() << ", target:" << TT + << "\n"; + } + } + + template <class... Bs> inline void Check(llvm::MCInst MCInst, Bs... Bytes) { + CheckWithSetup(ExegesisTarget::getDefault(), {}, MCInst, Bytes...); + } + + template <class... Bs> + inline void CheckWithSetup(const ExegesisTarget &ET, + llvm::ArrayRef<unsigned> RegsToDef, + llvm::MCInst MCInst, Bs... Bytes) { + ExecutableFunction Function = + (MCInst.getOpcode() == 0) ? assembleToFunction(ET, RegsToDef, {}) + : assembleToFunction(ET, RegsToDef, {MCInst}); + ASSERT_THAT(Function.getFunctionBytes().str(), + testing::ElementsAre(Bytes...)); + if (CanExecute) + Function(); + } + +private: + std::unique_ptr<llvm::LLVMTargetMachine> createTargetMachine() { + std::string Error; + const llvm::Target *TheTarget = + llvm::TargetRegistry::lookupTarget(TT, Error); + EXPECT_TRUE(TheTarget) << Error << " " << TT; + const llvm::TargetOptions Options; + llvm::TargetMachine *TM = TheTarget->createTargetMachine( + TT, CpuName, "", Options, llvm::Reloc::Model::Static); + EXPECT_TRUE(TM) << TT << " " << CpuName; + return std::unique_ptr<llvm::LLVMTargetMachine>( + static_cast<llvm::LLVMTargetMachine *>(TM)); + } + + ExecutableFunction + assembleToFunction(const ExegesisTarget &ET, + llvm::ArrayRef<unsigned> RegsToDef, + llvm::ArrayRef<llvm::MCInst> Instructions) { + llvm::SmallString<256> Buffer; + llvm::raw_svector_ostream AsmStream(Buffer); + assembleToStream(ET, createTargetMachine(), RegsToDef, Instructions, + AsmStream); + return ExecutableFunction(createTargetMachine(), + getObjectFromBuffer(AsmStream.str())); + } + + const std::string TT; + const std::string CpuName; + const bool CanExecute; +}; + +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/PerfHelperTest.cpp b/unittests/tools/llvm-exegesis/PerfHelperTest.cpp new file mode 100644 index 000000000000..a8205f9e3eb2 --- /dev/null +++ b/unittests/tools/llvm-exegesis/PerfHelperTest.cpp @@ -0,0 +1,47 @@ +//===-- PerfHelperTest.cpp --------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "PerfHelper.h" +#include "llvm/Config/config.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace exegesis { +namespace pfm { +namespace { + +using ::testing::IsEmpty; +using ::testing::Not; + +TEST(PerfHelperTest, FunctionalTest) { +#ifdef HAVE_LIBPFM + ASSERT_FALSE(pfmInitialize()); + const PerfEvent SingleEvent("CYCLES:u"); + const auto &EmptyFn = []() {}; + std::string CallbackEventName; + std::string CallbackEventNameFullyQualifed; + int64_t CallbackEventCycles; + Measure(llvm::makeArrayRef(SingleEvent), + [&](const PerfEvent &Event, int64_t Value) { + CallbackEventName = Event.name(); + CallbackEventNameFullyQualifed = Event.getPfmEventString(); + CallbackEventCycles = Value; + }, + EmptyFn); + EXPECT_EQ(CallbackEventName, "CYCLES:u"); + EXPECT_THAT(CallbackEventNameFullyQualifed, Not(IsEmpty())); + pfmTerminate(); +#else + ASSERT_TRUE(pfmInitialize()); +#endif +} + +} // namespace +} // namespace pfm +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/X86/AnalysisTest.cpp b/unittests/tools/llvm-exegesis/X86/AnalysisTest.cpp new file mode 100644 index 000000000000..d2d4c152d79e --- /dev/null +++ b/unittests/tools/llvm-exegesis/X86/AnalysisTest.cpp @@ -0,0 +1,102 @@ +#include "Analysis.h" + +#include <cassert> +#include <memory> + +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace exegesis { +namespace { + +using testing::Pair; +using testing::UnorderedElementsAre; + +class AnalysisTest : public ::testing::Test { +protected: + AnalysisTest() { + const std::string TT = "x86_64-unknown-linux"; + std::string error; + const llvm::Target *const TheTarget = + llvm::TargetRegistry::lookupTarget(TT, error); + if (!TheTarget) { + llvm::errs() << error << "\n"; + return; + } + STI.reset(TheTarget->createMCSubtargetInfo(TT, "haswell", "")); + + // Compute the ProxResIdx of ports unes in tests. + const auto &SM = STI->getSchedModel(); + for (unsigned I = 0, E = SM.getNumProcResourceKinds(); I < E; ++I) { + const std::string Name = SM.getProcResource(I)->Name; + if (Name == "HWPort0") { + P0Idx = I; + } else if (Name == "HWPort1") { + P1Idx = I; + } else if (Name == "HWPort5") { + P5Idx = I; + } else if (Name == "HWPort6") { + P6Idx = I; + } else if (Name == "HWPort05") { + P05Idx = I; + } else if (Name == "HWPort0156") { + P0156Idx = I; + } + } + EXPECT_NE(P0Idx, 0); + EXPECT_NE(P1Idx, 0); + EXPECT_NE(P5Idx, 0); + EXPECT_NE(P6Idx, 0); + EXPECT_NE(P05Idx, 0); + EXPECT_NE(P0156Idx, 0); + } + + static void SetUpTestCase() { + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86Target(); + LLVMInitializeX86TargetMC(); + } + +protected: + std::unique_ptr<const llvm::MCSubtargetInfo> STI; + uint16_t P0Idx = 0; + uint16_t P1Idx = 0; + uint16_t P5Idx = 0; + uint16_t P6Idx = 0; + uint16_t P05Idx = 0; + uint16_t P0156Idx = 0; +}; + +TEST_F(AnalysisTest, ComputeIdealizedProcResPressure_2P0) { + const auto Pressure = + computeIdealizedProcResPressure(STI->getSchedModel(), {{P0Idx, 2}}); + EXPECT_THAT(Pressure, UnorderedElementsAre(Pair(P0Idx, 2.0))); +} + +TEST_F(AnalysisTest, ComputeIdealizedProcResPressure_2P05) { + const auto Pressure = + computeIdealizedProcResPressure(STI->getSchedModel(), {{P05Idx, 2}}); + EXPECT_THAT(Pressure, + UnorderedElementsAre(Pair(P0Idx, 1.0), Pair(P5Idx, 1.0))); +} + +TEST_F(AnalysisTest, ComputeIdealizedProcResPressure_2P05_2P0156) { + const auto Pressure = computeIdealizedProcResPressure( + STI->getSchedModel(), {{P05Idx, 2}, {P0156Idx, 2}}); + EXPECT_THAT(Pressure, + UnorderedElementsAre(Pair(P0Idx, 1.0), Pair(P1Idx, 1.0), + Pair(P5Idx, 1.0), Pair(P6Idx, 1.0))); +} + +TEST_F(AnalysisTest, ComputeIdealizedProcResPressure_1P1_1P05_2P0156) { + const auto Pressure = computeIdealizedProcResPressure( + STI->getSchedModel(), {{P1Idx, 1}, {P05Idx, 1}, {P0156Idx, 2}}); + EXPECT_THAT(Pressure, + UnorderedElementsAre(Pair(P0Idx, 1.0), Pair(P1Idx, 1.0), + Pair(P5Idx, 1.0), Pair(P6Idx, 1.0))); +} + +} // namespace +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/X86/AssemblerTest.cpp b/unittests/tools/llvm-exegesis/X86/AssemblerTest.cpp new file mode 100644 index 000000000000..4e3c54accac9 --- /dev/null +++ b/unittests/tools/llvm-exegesis/X86/AssemblerTest.cpp @@ -0,0 +1,57 @@ +//===-- AssemblerTest.cpp ---------------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../Common/AssemblerUtils.h" +#include "X86InstrInfo.h" + +namespace exegesis { +namespace { + +using llvm::MCInstBuilder; +using llvm::X86::EAX; +using llvm::X86::MOV32ri; +using llvm::X86::MOV64ri32; +using llvm::X86::RAX; +using llvm::X86::XOR32rr; + +class X86MachineFunctionGeneratorTest + : public MachineFunctionGeneratorBaseTest { +protected: + X86MachineFunctionGeneratorTest() + : MachineFunctionGeneratorBaseTest("x86_64-unknown-linux", "haswell") {} + + static void SetUpTestCase() { + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86TargetMC(); + LLVMInitializeX86Target(); + LLVMInitializeX86AsmPrinter(); + } +}; + +TEST_F(X86MachineFunctionGeneratorTest, DISABLED_JitFunction) { + Check(llvm::MCInst(), 0xc3); +} + +TEST_F(X86MachineFunctionGeneratorTest, DISABLED_JitFunctionXOR32rr) { + Check(MCInstBuilder(XOR32rr).addReg(EAX).addReg(EAX).addReg(EAX), 0x31, 0xc0, + 0xc3); +} + +TEST_F(X86MachineFunctionGeneratorTest, DISABLED_JitFunctionMOV64ri) { + Check(MCInstBuilder(MOV64ri32).addReg(RAX).addImm(42), 0x48, 0xc7, 0xc0, 0x2a, + 0x00, 0x00, 0x00, 0xc3); +} + +TEST_F(X86MachineFunctionGeneratorTest, DISABLED_JitFunctionMOV32ri) { + Check(MCInstBuilder(MOV32ri).addReg(EAX).addImm(42), 0xb8, 0x2a, 0x00, 0x00, + 0x00, 0xc3); +} + +} // namespace +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/X86/CMakeLists.txt b/unittests/tools/llvm-exegesis/X86/CMakeLists.txt new file mode 100644 index 000000000000..5a74c87d9b5f --- /dev/null +++ b/unittests/tools/llvm-exegesis/X86/CMakeLists.txt @@ -0,0 +1,25 @@ +include_directories( + ${LLVM_MAIN_SRC_DIR}/lib/Target/X86 + ${LLVM_BINARY_DIR}/lib/Target/X86 + ${LLVM_MAIN_SRC_DIR}/tools/llvm-exegesis/lib + ) + +set(LLVM_LINK_COMPONENTS + MC + MCParser + Object + Support + Symbolize + X86 + ) + +add_llvm_unittest(LLVMExegesisX86Tests + AssemblerTest.cpp + AnalysisTest.cpp + SnippetGeneratorTest.cpp + RegisterAliasingTest.cpp + TargetTest.cpp + ) +target_link_libraries(LLVMExegesisX86Tests PRIVATE + LLVMExegesis + LLVMExegesisX86) diff --git a/unittests/tools/llvm-exegesis/X86/RegisterAliasingTest.cpp b/unittests/tools/llvm-exegesis/X86/RegisterAliasingTest.cpp new file mode 100644 index 000000000000..12f76541d4d6 --- /dev/null +++ b/unittests/tools/llvm-exegesis/X86/RegisterAliasingTest.cpp @@ -0,0 +1,91 @@ +#include "RegisterAliasing.h" + +#include <cassert> +#include <memory> + +#include "X86InstrInfo.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace exegesis { +namespace { + +class RegisterAliasingTest : public ::testing::Test { +protected: + RegisterAliasingTest() { + const std::string TT = "x86_64-unknown-linux"; + std::string error; + const llvm::Target *const TheTarget = + llvm::TargetRegistry::lookupTarget(TT, error); + if (!TheTarget) { + llvm::errs() << error << "\n"; + return; + } + MCRegInfo.reset(TheTarget->createMCRegInfo(TT)); + } + + static void SetUpTestCase() { + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86Target(); + LLVMInitializeX86TargetMC(); + } + + const llvm::MCRegisterInfo &getMCRegInfo() { + assert(MCRegInfo); + return *MCRegInfo; + } + +private: + std::unique_ptr<const llvm::MCRegisterInfo> MCRegInfo; +}; + +TEST_F(RegisterAliasingTest, TrackSimpleRegister) { + const auto &RegInfo = getMCRegInfo(); + const RegisterAliasingTracker tracker(RegInfo, llvm::X86::EAX); + std::set<llvm::MCPhysReg> ActualAliasedRegisters; + for (unsigned I : tracker.aliasedBits().set_bits()) + ActualAliasedRegisters.insert(static_cast<llvm::MCPhysReg>(I)); + const std::set<llvm::MCPhysReg> ExpectedAliasedRegisters = { + llvm::X86::AL, llvm::X86::AH, llvm::X86::AX, + llvm::X86::EAX, llvm::X86::HAX, llvm::X86::RAX}; + ASSERT_THAT(ActualAliasedRegisters, ExpectedAliasedRegisters); + for (llvm::MCPhysReg aliased : ExpectedAliasedRegisters) { + ASSERT_THAT(tracker.getOrigin(aliased), llvm::X86::EAX); + } +} + +TEST_F(RegisterAliasingTest, TrackRegisterClass) { + // The alias bits for GR8_ABCD_LRegClassID are the union of the alias bits for + // AL, BL, CL and DL. + const auto &RegInfo = getMCRegInfo(); + const llvm::BitVector NoReservedReg(RegInfo.getNumRegs()); + + const RegisterAliasingTracker RegClassTracker( + RegInfo, NoReservedReg, + RegInfo.getRegClass(llvm::X86::GR8_ABCD_LRegClassID)); + + llvm::BitVector sum(RegInfo.getNumRegs()); + sum |= RegisterAliasingTracker(RegInfo, llvm::X86::AL).aliasedBits(); + sum |= RegisterAliasingTracker(RegInfo, llvm::X86::BL).aliasedBits(); + sum |= RegisterAliasingTracker(RegInfo, llvm::X86::CL).aliasedBits(); + sum |= RegisterAliasingTracker(RegInfo, llvm::X86::DL).aliasedBits(); + + ASSERT_THAT(RegClassTracker.aliasedBits(), sum); +} + +TEST_F(RegisterAliasingTest, TrackRegisterClassCache) { + // Fetching twice the same tracker yields the same pointers. + const auto &RegInfo = getMCRegInfo(); + const llvm::BitVector NoReservedReg(RegInfo.getNumRegs()); + RegisterAliasingTrackerCache Cache(RegInfo, NoReservedReg); + ASSERT_THAT(&Cache.getRegister(llvm::X86::AX), + &Cache.getRegister(llvm::X86::AX)); + + ASSERT_THAT(&Cache.getRegisterClass(llvm::X86::GR8_ABCD_LRegClassID), + &Cache.getRegisterClass(llvm::X86::GR8_ABCD_LRegClassID)); +} + +} // namespace +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp b/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp new file mode 100644 index 000000000000..edacd7fe6291 --- /dev/null +++ b/unittests/tools/llvm-exegesis/X86/SnippetGeneratorTest.cpp @@ -0,0 +1,285 @@ +//===-- SnippetGeneratorTest.cpp --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "../Common/AssemblerUtils.h" +#include "Latency.h" +#include "LlvmState.h" +#include "MCInstrDescView.h" +#include "RegisterAliasing.h" +#include "Uops.h" +#include "X86InstrInfo.h" + +#include <unordered_set> + +namespace exegesis { +namespace { + +using testing::AnyOf; +using testing::ElementsAre; +using testing::HasSubstr; +using testing::Not; +using testing::SizeIs; +using testing::UnorderedElementsAre; + +MATCHER(IsInvalid, "") { return !arg.isValid(); } +MATCHER(IsReg, "") { return arg.isReg(); } + +class X86SnippetGeneratorTest : public ::testing::Test { +protected: + X86SnippetGeneratorTest() + : State("x86_64-unknown-linux", "haswell"), + MCInstrInfo(State.getInstrInfo()), MCRegisterInfo(State.getRegInfo()) {} + + static void SetUpTestCase() { + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86TargetMC(); + LLVMInitializeX86Target(); + LLVMInitializeX86AsmPrinter(); + } + + const LLVMState State; + const llvm::MCInstrInfo &MCInstrInfo; + const llvm::MCRegisterInfo &MCRegisterInfo; +}; + +template <typename BenchmarkRunner> +class SnippetGeneratorTest : public X86SnippetGeneratorTest { +protected: + SnippetGeneratorTest() : Runner(State) {} + + SnippetPrototype checkAndGetConfigurations(unsigned Opcode) { + randomGenerator().seed(0); // Initialize seed. + auto ProtoOrError = Runner.generatePrototype(Opcode); + EXPECT_FALSE(ProtoOrError.takeError()); // Valid configuration. + return std::move(ProtoOrError.get()); + } + + BenchmarkRunner Runner; +}; + +using LatencySnippetGeneratorTest = + SnippetGeneratorTest<LatencyBenchmarkRunner>; + +using UopsSnippetGeneratorTest = SnippetGeneratorTest<UopsBenchmarkRunner>; + +TEST_F(LatencySnippetGeneratorTest, ImplicitSelfDependency) { + // ADC16i16 self alias because of implicit use and def. + + // explicit use 0 : imm + // implicit def : AX + // implicit def : EFLAGS + // implicit use : AX + // implicit use : EFLAGS + const unsigned Opcode = llvm::X86::ADC16i16; + EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[0], llvm::X86::AX); + EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[1], llvm::X86::EFLAGS); + EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitUses()[0], llvm::X86::AX); + EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitUses()[1], llvm::X86::EFLAGS); + const SnippetPrototype Proto = checkAndGetConfigurations(Opcode); + EXPECT_THAT(Proto.Explanation, HasSubstr("implicit")); + ASSERT_THAT(Proto.Snippet, SizeIs(1)); + const InstructionInstance &II = Proto.Snippet[0]; + EXPECT_THAT(II.getOpcode(), Opcode); + ASSERT_THAT(II.VariableValues, SizeIs(1)); // Imm. + EXPECT_THAT(II.VariableValues[0], IsInvalid()) << "Immediate is not set"; +} + +TEST_F(LatencySnippetGeneratorTest, ExplicitSelfDependency) { + // ADD16ri self alias because Op0 and Op1 are tied together. + + // explicit def 0 : reg RegClass=GR16 + // explicit use 1 : reg RegClass=GR16 | TIED_TO:0 + // explicit use 2 : imm + // implicit def : EFLAGS + const unsigned Opcode = llvm::X86::ADD16ri; + EXPECT_THAT(MCInstrInfo.get(Opcode).getImplicitDefs()[0], llvm::X86::EFLAGS); + const SnippetPrototype Proto = checkAndGetConfigurations(Opcode); + EXPECT_THAT(Proto.Explanation, HasSubstr("explicit")); + ASSERT_THAT(Proto.Snippet, SizeIs(1)); + const InstructionInstance &II = Proto.Snippet[0]; + EXPECT_THAT(II.getOpcode(), Opcode); + ASSERT_THAT(II.VariableValues, SizeIs(2)); + EXPECT_THAT(II.VariableValues[0], IsReg()) << "Operand 0 and 1"; + EXPECT_THAT(II.VariableValues[1], IsInvalid()) << "Operand 2 is not set"; +} + +TEST_F(LatencySnippetGeneratorTest, DependencyThroughOtherOpcode) { + // CMP64rr + // explicit use 0 : reg RegClass=GR64 + // explicit use 1 : reg RegClass=GR64 + // implicit def : EFLAGS + + const unsigned Opcode = llvm::X86::CMP64rr; + const SnippetPrototype Proto = checkAndGetConfigurations(Opcode); + EXPECT_THAT(Proto.Explanation, HasSubstr("cycle through")); + ASSERT_THAT(Proto.Snippet, SizeIs(2)); + const InstructionInstance &II = Proto.Snippet[0]; + EXPECT_THAT(II.getOpcode(), Opcode); + ASSERT_THAT(II.VariableValues, SizeIs(2)); + EXPECT_THAT(II.VariableValues, AnyOf(ElementsAre(IsReg(), IsInvalid()), + ElementsAre(IsInvalid(), IsReg()))); + EXPECT_THAT(Proto.Snippet[1].getOpcode(), Not(Opcode)); + // TODO: check that the two instructions alias each other. +} + +TEST_F(LatencySnippetGeneratorTest, LAHF) { + const unsigned Opcode = llvm::X86::LAHF; + const SnippetPrototype Proto = checkAndGetConfigurations(Opcode); + EXPECT_THAT(Proto.Explanation, HasSubstr("cycle through")); + ASSERT_THAT(Proto.Snippet, SizeIs(2)); + const InstructionInstance &II = Proto.Snippet[0]; + EXPECT_THAT(II.getOpcode(), Opcode); + ASSERT_THAT(II.VariableValues, SizeIs(0)); +} + +TEST_F(UopsSnippetGeneratorTest, ParallelInstruction) { + // BNDCL32rr is parallel no matter what. + + // explicit use 0 : reg RegClass=BNDR + // explicit use 1 : reg RegClass=GR32 + + const unsigned Opcode = llvm::X86::BNDCL32rr; + const SnippetPrototype Proto = checkAndGetConfigurations(Opcode); + EXPECT_THAT(Proto.Explanation, HasSubstr("parallel")); + ASSERT_THAT(Proto.Snippet, SizeIs(1)); + const InstructionInstance &II = Proto.Snippet[0]; + EXPECT_THAT(II.getOpcode(), Opcode); + ASSERT_THAT(II.VariableValues, SizeIs(2)); + EXPECT_THAT(II.VariableValues[0], IsInvalid()); + EXPECT_THAT(II.VariableValues[1], IsInvalid()); +} + +TEST_F(UopsSnippetGeneratorTest, SerialInstruction) { + // CDQ is serial no matter what. + + // implicit def : EAX + // implicit def : EDX + // implicit use : EAX + const unsigned Opcode = llvm::X86::CDQ; + const SnippetPrototype Proto = checkAndGetConfigurations(Opcode); + EXPECT_THAT(Proto.Explanation, HasSubstr("serial")); + ASSERT_THAT(Proto.Snippet, SizeIs(1)); + const InstructionInstance &II = Proto.Snippet[0]; + EXPECT_THAT(II.getOpcode(), Opcode); + ASSERT_THAT(II.VariableValues, SizeIs(0)); +} + +TEST_F(UopsSnippetGeneratorTest, StaticRenaming) { + // CMOVA32rr has tied variables, we enumarate the possible values to execute + // as many in parallel as possible. + + // explicit def 0 : reg RegClass=GR32 + // explicit use 1 : reg RegClass=GR32 | TIED_TO:0 + // explicit use 2 : reg RegClass=GR32 + // implicit use : EFLAGS + const unsigned Opcode = llvm::X86::CMOVA32rr; + const SnippetPrototype Proto = checkAndGetConfigurations(Opcode); + EXPECT_THAT(Proto.Explanation, HasSubstr("static renaming")); + constexpr const unsigned kInstructionCount = 15; + ASSERT_THAT(Proto.Snippet, SizeIs(kInstructionCount)); + std::unordered_set<unsigned> AllDefRegisters; + for (const auto &II : Proto.Snippet) { + ASSERT_THAT(II.VariableValues, SizeIs(2)); + AllDefRegisters.insert(II.VariableValues[0].getReg()); + } + EXPECT_THAT(AllDefRegisters, SizeIs(kInstructionCount)) + << "Each instruction writes to a different register"; +} + +TEST_F(UopsSnippetGeneratorTest, NoTiedVariables) { + // CMOV_GR32 has no tied variables, we make sure def and use are different + // from each other. + + // explicit def 0 : reg RegClass=GR32 + // explicit use 1 : reg RegClass=GR32 + // explicit use 2 : reg RegClass=GR32 + // explicit use 3 : imm + // implicit use : EFLAGS + const unsigned Opcode = llvm::X86::CMOV_GR32; + const SnippetPrototype Proto = checkAndGetConfigurations(Opcode); + EXPECT_THAT(Proto.Explanation, HasSubstr("no tied variables")); + ASSERT_THAT(Proto.Snippet, SizeIs(1)); + const InstructionInstance &II = Proto.Snippet[0]; + EXPECT_THAT(II.getOpcode(), Opcode); + ASSERT_THAT(II.VariableValues, SizeIs(4)); + EXPECT_THAT(II.VariableValues[0].getReg(), Not(II.VariableValues[1].getReg())) + << "Def is different from first Use"; + EXPECT_THAT(II.VariableValues[0].getReg(), Not(II.VariableValues[2].getReg())) + << "Def is different from second Use"; + EXPECT_THAT(II.VariableValues[3], IsInvalid()); +} + +class FakeBenchmarkRunner : public BenchmarkRunner { +public: + FakeBenchmarkRunner(const LLVMState &State) + : BenchmarkRunner(State, InstructionBenchmark::Unknown) {} + + Instruction createInstruction(unsigned Opcode) { + return Instruction(State.getInstrInfo().get(Opcode), RATC); + } + +private: + llvm::Expected<SnippetPrototype> + generatePrototype(unsigned Opcode) const override { + return llvm::make_error<llvm::StringError>("not implemented", + llvm::inconvertibleErrorCode()); + } + + std::vector<BenchmarkMeasure> + runMeasurements(const ExecutableFunction &EF, + const unsigned NumRepetitions) const override { + return {}; + } +}; + +using FakeSnippetGeneratorTest = SnippetGeneratorTest<FakeBenchmarkRunner>; + +TEST_F(FakeSnippetGeneratorTest, ComputeRegsToDefAdd16ri) { + // ADD16ri: + // explicit def 0 : reg RegClass=GR16 + // explicit use 1 : reg RegClass=GR16 | TIED_TO:0 + // explicit use 2 : imm + // implicit def : EFLAGS + InstructionInstance II(Runner.createInstruction(llvm::X86::ADD16ri)); + II.getValueFor(II.Instr.Variables[0]) = + llvm::MCOperand::createReg(llvm::X86::AX); + std::vector<InstructionInstance> Snippet; + Snippet.push_back(std::move(II)); + const auto RegsToDef = Runner.computeRegsToDef(Snippet); + EXPECT_THAT(RegsToDef, UnorderedElementsAre(llvm::X86::AX)); +} + +TEST_F(FakeSnippetGeneratorTest, ComputeRegsToDefAdd64rr) { + // ADD64rr: + // mov64ri rax, 42 + // add64rr rax, rax, rbx + // -> only rbx needs defining. + std::vector<InstructionInstance> Snippet; + { + InstructionInstance Mov(Runner.createInstruction(llvm::X86::MOV64ri)); + Mov.getValueFor(Mov.Instr.Variables[0]) = + llvm::MCOperand::createReg(llvm::X86::RAX); + Mov.getValueFor(Mov.Instr.Variables[1]) = llvm::MCOperand::createImm(42); + Snippet.push_back(std::move(Mov)); + } + { + InstructionInstance Add(Runner.createInstruction(llvm::X86::ADD64rr)); + Add.getValueFor(Add.Instr.Variables[0]) = + llvm::MCOperand::createReg(llvm::X86::RAX); + Add.getValueFor(Add.Instr.Variables[1]) = + llvm::MCOperand::createReg(llvm::X86::RBX); + Snippet.push_back(std::move(Add)); + } + + const auto RegsToDef = Runner.computeRegsToDef(Snippet); + EXPECT_THAT(RegsToDef, UnorderedElementsAre(llvm::X86::RBX)); +} + +} // namespace +} // namespace exegesis diff --git a/unittests/tools/llvm-exegesis/X86/TargetTest.cpp b/unittests/tools/llvm-exegesis/X86/TargetTest.cpp new file mode 100644 index 000000000000..0c2f3fbd9f9d --- /dev/null +++ b/unittests/tools/llvm-exegesis/X86/TargetTest.cpp @@ -0,0 +1,170 @@ +#include "Target.h" + +#include <cassert> +#include <memory> + +#include "MCTargetDesc/X86MCTargetDesc.h" +#include "llvm/Support/TargetRegistry.h" +#include "llvm/Support/TargetSelect.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace exegesis { + +void InitializeX86ExegesisTarget(); + +namespace { + +using testing::Gt; +using testing::NotNull; +using testing::SizeIs; + +constexpr const char kTriple[] = "x86_64-unknown-linux"; + +class X86TargetTest : public ::testing::Test { +protected: + X86TargetTest() + : ExegesisTarget_(ExegesisTarget::lookup(llvm::Triple(kTriple))) { + EXPECT_THAT(ExegesisTarget_, NotNull()); + std::string error; + Target_ = llvm::TargetRegistry::lookupTarget(kTriple, error); + EXPECT_THAT(Target_, NotNull()); + } + static void SetUpTestCase() { + LLVMInitializeX86TargetInfo(); + LLVMInitializeX86Target(); + LLVMInitializeX86TargetMC(); + InitializeX86ExegesisTarget(); + } + + const llvm::Target *Target_; + const ExegesisTarget *const ExegesisTarget_; +}; + +TEST_F(X86TargetTest, SetRegToConstantGPR) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "core2", "")); + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::X86::EAX); + EXPECT_THAT(Insts, SizeIs(1)); + EXPECT_EQ(Insts[0].getOpcode(), llvm::X86::MOV32ri); + EXPECT_EQ(Insts[0].getOperand(0).getReg(), llvm::X86::EAX); +} + +TEST_F(X86TargetTest, SetRegToConstantXMM_SSE2) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "core2", "")); + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::X86::XMM1); + EXPECT_THAT(Insts, SizeIs(7U)); + EXPECT_EQ(Insts[0].getOpcode(), llvm::X86::SUB64ri8); + EXPECT_EQ(Insts[1].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[2].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[3].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[4].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[5].getOpcode(), llvm::X86::MOVDQUrm); + EXPECT_EQ(Insts[6].getOpcode(), llvm::X86::ADD64ri8); +} + +TEST_F(X86TargetTest, SetRegToConstantXMM_AVX) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "core2", "+avx")); + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::X86::XMM1); + EXPECT_THAT(Insts, SizeIs(7U)); + EXPECT_EQ(Insts[0].getOpcode(), llvm::X86::SUB64ri8); + EXPECT_EQ(Insts[1].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[2].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[3].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[4].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[5].getOpcode(), llvm::X86::VMOVDQUrm); + EXPECT_EQ(Insts[6].getOpcode(), llvm::X86::ADD64ri8); +} + +TEST_F(X86TargetTest, SetRegToConstantXMM_AVX512) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "core2", "+avx512vl")); + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::X86::XMM1); + EXPECT_THAT(Insts, SizeIs(7U)); + EXPECT_EQ(Insts[0].getOpcode(), llvm::X86::SUB64ri8); + EXPECT_EQ(Insts[1].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[2].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[3].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[4].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[5].getOpcode(), llvm::X86::VMOVDQU32Z128rm); + EXPECT_EQ(Insts[6].getOpcode(), llvm::X86::ADD64ri8); +} + +TEST_F(X86TargetTest, SetRegToConstantMMX) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "core2", "")); + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::X86::MM1); + EXPECT_THAT(Insts, SizeIs(5U)); + EXPECT_EQ(Insts[0].getOpcode(), llvm::X86::SUB64ri8); + EXPECT_EQ(Insts[1].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[2].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[3].getOpcode(), llvm::X86::MMX_MOVQ64rm); + EXPECT_EQ(Insts[4].getOpcode(), llvm::X86::ADD64ri8); +} + +TEST_F(X86TargetTest, SetRegToConstantYMM_AVX) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "core2", "+avx")); + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::X86::YMM1); + EXPECT_THAT(Insts, SizeIs(11U)); + EXPECT_EQ(Insts[0].getOpcode(), llvm::X86::SUB64ri8); + EXPECT_EQ(Insts[1].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[2].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[3].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[4].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[5].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[6].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[7].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[8].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[9].getOpcode(), llvm::X86::VMOVDQUYrm); + EXPECT_EQ(Insts[10].getOpcode(), llvm::X86::ADD64ri8); +} + +TEST_F(X86TargetTest, SetRegToConstantYMM_AVX512) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "core2", "+avx512vl")); + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::X86::YMM1); + EXPECT_THAT(Insts, SizeIs(11U)); + EXPECT_EQ(Insts[0].getOpcode(), llvm::X86::SUB64ri8); + EXPECT_EQ(Insts[1].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[2].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[3].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[4].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[5].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[6].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[7].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[8].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[9].getOpcode(), llvm::X86::VMOVDQU32Z256rm); + EXPECT_EQ(Insts[10].getOpcode(), llvm::X86::ADD64ri8); +} + +TEST_F(X86TargetTest, SetRegToConstantZMM_AVX512) { + const std::unique_ptr<llvm::MCSubtargetInfo> STI( + Target_->createMCSubtargetInfo(kTriple, "core2", "+avx512vl")); + const auto Insts = ExegesisTarget_->setRegToConstant(*STI, llvm::X86::ZMM1); + EXPECT_THAT(Insts, SizeIs(19U)); + EXPECT_EQ(Insts[0].getOpcode(), llvm::X86::SUB64ri8); + EXPECT_EQ(Insts[1].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[2].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[3].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[4].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[5].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[6].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[7].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[8].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[9].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[10].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[11].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[12].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[13].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[14].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[15].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[16].getOpcode(), llvm::X86::MOV32mi); + EXPECT_EQ(Insts[17].getOpcode(), llvm::X86::VMOVDQU32Zrm); + EXPECT_EQ(Insts[18].getOpcode(), llvm::X86::ADD64ri8); +} + +} // namespace +} // namespace exegesis |